summaryrefslogtreecommitdiffstats
path: root/accessible/tests
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--accessible/tests/browser/.eslintrc.js28
-rw-r--r--accessible/tests/browser/Common.sys.mjs451
-rw-r--r--accessible/tests/browser/Layout.sys.mjs178
-rw-r--r--accessible/tests/browser/bounds/browser.ini27
-rw-r--r--accessible/tests/browser/bounds/browser_accessible_moved.js49
-rw-r--r--accessible/tests/browser/bounds/browser_caret_rect.js140
-rw-r--r--accessible/tests/browser/bounds/browser_position.js103
-rw-r--r--accessible/tests/browser/bounds/browser_test_display_contents.js44
-rw-r--r--accessible/tests/browser/bounds/browser_test_iframe_transform.js209
-rw-r--r--accessible/tests/browser/bounds/browser_test_resolution.js72
-rw-r--r--accessible/tests/browser/bounds/browser_test_simple_transform.js225
-rw-r--r--accessible/tests/browser/bounds/browser_test_zoom.js65
-rw-r--r--accessible/tests/browser/bounds/browser_test_zoom_text.js86
-rw-r--r--accessible/tests/browser/bounds/browser_zero_area.js118
-rw-r--r--accessible/tests/browser/bounds/head.js19
-rw-r--r--accessible/tests/browser/browser.ini39
-rw-r--r--accessible/tests/browser/browser_shutdown_acc_reference.js64
-rw-r--r--accessible/tests/browser/browser_shutdown_doc_acc_reference.js56
-rw-r--r--accessible/tests/browser/browser_shutdown_multi_acc_reference_doc.js76
-rw-r--r--accessible/tests/browser/browser_shutdown_multi_acc_reference_obj.js76
-rw-r--r--accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_doc.js90
-rw-r--r--accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_obj.js90
-rw-r--r--accessible/tests/browser/browser_shutdown_multi_reference.js58
-rw-r--r--accessible/tests/browser/browser_shutdown_parent_own_reference.js102
-rw-r--r--accessible/tests/browser/browser_shutdown_pref.js72
-rw-r--r--accessible/tests/browser/browser_shutdown_proxy_acc_reference.js76
-rw-r--r--accessible/tests/browser/browser_shutdown_proxy_doc_acc_reference.js78
-rw-r--r--accessible/tests/browser/browser_shutdown_remote_no_reference.js141
-rw-r--r--accessible/tests/browser/browser_shutdown_remote_only.js59
-rw-r--r--accessible/tests/browser/browser_shutdown_remote_own_reference.js181
-rw-r--r--accessible/tests/browser/browser_shutdown_scope_lifecycle.js28
-rw-r--r--accessible/tests/browser/browser_shutdown_start_restart.js51
-rw-r--r--accessible/tests/browser/e10s/browser.ini80
-rw-r--r--accessible/tests/browser/e10s/browser_caching_actions.js266
-rw-r--r--accessible/tests/browser/e10s/browser_caching_attributes.js717
-rw-r--r--accessible/tests/browser/e10s/browser_caching_description.js280
-rw-r--r--accessible/tests/browser/e10s/browser_caching_document_props.js80
-rw-r--r--accessible/tests/browser/e10s/browser_caching_domnodeid.js32
-rw-r--r--accessible/tests/browser/e10s/browser_caching_hyperlink.js228
-rw-r--r--accessible/tests/browser/e10s/browser_caching_innerHTML.js48
-rw-r--r--accessible/tests/browser/e10s/browser_caching_interfaces.js59
-rw-r--r--accessible/tests/browser/e10s/browser_caching_large_update.js66
-rw-r--r--accessible/tests/browser/e10s/browser_caching_name.js539
-rw-r--r--accessible/tests/browser/e10s/browser_caching_position.js194
-rw-r--r--accessible/tests/browser/e10s/browser_caching_relations.js291
-rw-r--r--accessible/tests/browser/e10s/browser_caching_relations_002.js293
-rw-r--r--accessible/tests/browser/e10s/browser_caching_states.js484
-rw-r--r--accessible/tests/browser/e10s/browser_caching_table.js532
-rw-r--r--accessible/tests/browser/e10s/browser_caching_text_bounds.js696
-rw-r--r--accessible/tests/browser/e10s/browser_caching_uniqueid.js30
-rw-r--r--accessible/tests/browser/e10s/browser_caching_value.js415
-rw-r--r--accessible/tests/browser/e10s/browser_events_announcement.js30
-rw-r--r--accessible/tests/browser/e10s/browser_events_caretmove.js22
-rw-r--r--accessible/tests/browser/e10s/browser_events_hide.js44
-rw-r--r--accessible/tests/browser/e10s/browser_events_show.js22
-rw-r--r--accessible/tests/browser/e10s/browser_events_statechange.js71
-rw-r--r--accessible/tests/browser/e10s/browser_events_textchange.js119
-rw-r--r--accessible/tests/browser/e10s/browser_events_vcchange.js87
-rw-r--r--accessible/tests/browser/e10s/browser_language.js29
-rw-r--r--accessible/tests/browser/e10s/browser_obj_group.js430
-rw-r--r--accessible/tests/browser/e10s/browser_obj_group_002.js390
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js45
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js325
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_canvas.js28
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_csscontentvisibility.js72
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js60
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_doc.js320
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_gencontent.js94
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_hidden.js32
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_image.js192
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_imagemap.js190
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_list.js52
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js48
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_listener.js38
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_move.js84
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_optgroup.js100
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_removal.js58
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js73
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_table.js48
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_textleaf.js38
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_visibility.js342
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_whitespace.js69
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html23
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html44
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_imagemap.html21
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml11
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_visibility.html78
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_whitespace.html10
-rw-r--r--accessible/tests/browser/e10s/fonts/Ahem.sjs241
-rw-r--r--accessible/tests/browser/e10s/head.js192
-rw-r--r--accessible/tests/browser/events/browser.ini24
-rw-r--r--accessible/tests/browser/events/browser_test_A11yUtils_announce.js57
-rw-r--r--accessible/tests/browser/events/browser_test_caret_move_granularity.js102
-rw-r--r--accessible/tests/browser/events/browser_test_docload.js122
-rw-r--r--accessible/tests/browser/events/browser_test_focus_browserui.js57
-rw-r--r--accessible/tests/browser/events/browser_test_focus_dialog.js76
-rw-r--r--accessible/tests/browser/events/browser_test_focus_urlbar.js438
-rw-r--r--accessible/tests/browser/events/browser_test_panel.js58
-rw-r--r--accessible/tests/browser/events/browser_test_scrolling.js113
-rw-r--r--accessible/tests/browser/events/browser_test_selection_urlbar.js61
-rw-r--r--accessible/tests/browser/events/browser_test_textcaret.js58
-rw-r--r--accessible/tests/browser/events/head.js18
-rw-r--r--accessible/tests/browser/fission/browser.ini20
-rw-r--r--accessible/tests/browser/fission/browser_content_tree.js75
-rw-r--r--accessible/tests/browser/fission/browser_hidden_iframe.js70
-rw-r--r--accessible/tests/browser/fission/browser_nested_iframe.js164
-rw-r--r--accessible/tests/browser/fission/browser_reframe_root.js95
-rw-r--r--accessible/tests/browser/fission/browser_reframe_visibility.js116
-rw-r--r--accessible/tests/browser/fission/browser_src_change.js62
-rw-r--r--accessible/tests/browser/fission/browser_take_focus.js73
-rw-r--r--accessible/tests/browser/fission/head.js18
-rw-r--r--accessible/tests/browser/general/browser.ini12
-rw-r--r--accessible/tests/browser/general/browser_test_doc_creation.js55
-rw-r--r--accessible/tests/browser/general/browser_test_urlbar.js40
-rw-r--r--accessible/tests/browser/general/head.js71
-rw-r--r--accessible/tests/browser/head.js146
-rw-r--r--accessible/tests/browser/hittest/browser.ini18
-rw-r--r--accessible/tests/browser/hittest/browser_test_browser.js68
-rw-r--r--accessible/tests/browser/hittest/browser_test_general.js339
-rw-r--r--accessible/tests/browser/hittest/browser_test_scroll_hittest.js105
-rw-r--r--accessible/tests/browser/hittest/browser_test_shadowroot.js61
-rw-r--r--accessible/tests/browser/hittest/browser_test_text.js84
-rw-r--r--accessible/tests/browser/hittest/browser_test_zoom.js38
-rw-r--r--accessible/tests/browser/hittest/browser_test_zoom_text.js64
-rw-r--r--accessible/tests/browser/hittest/head.js113
-rw-r--r--accessible/tests/browser/mac/browser.ini58
-rw-r--r--accessible/tests/browser/mac/browser_app.js351
-rw-r--r--accessible/tests/browser/mac/browser_aria_busy.js44
-rw-r--r--accessible/tests/browser/mac/browser_aria_controls_flowto.js92
-rw-r--r--accessible/tests/browser/mac/browser_aria_current.js58
-rw-r--r--accessible/tests/browser/mac/browser_aria_expanded.js45
-rw-r--r--accessible/tests/browser/mac/browser_aria_haspopup.js320
-rw-r--r--accessible/tests/browser/mac/browser_attributed_text.js144
-rw-r--r--accessible/tests/browser/mac/browser_bounds.js77
-rw-r--r--accessible/tests/browser/mac/browser_details_summary.js69
-rw-r--r--accessible/tests/browser/mac/browser_focus.js44
-rw-r--r--accessible/tests/browser/mac/browser_heading.js77
-rw-r--r--accessible/tests/browser/mac/browser_hierarchy.js75
-rw-r--r--accessible/tests/browser/mac/browser_input.js225
-rw-r--r--accessible/tests/browser/mac/browser_label_title.js111
-rw-r--r--accessible/tests/browser/mac/browser_link.js231
-rw-r--r--accessible/tests/browser/mac/browser_live_regions.js165
-rw-r--r--accessible/tests/browser/mac/browser_mathml.js151
-rw-r--r--accessible/tests/browser/mac/browser_menulist.js103
-rw-r--r--accessible/tests/browser/mac/browser_navigate.js394
-rw-r--r--accessible/tests/browser/mac/browser_outline.js566
-rw-r--r--accessible/tests/browser/mac/browser_outline_xul.js274
-rw-r--r--accessible/tests/browser/mac/browser_popupbutton.js166
-rw-r--r--accessible/tests/browser/mac/browser_radio_position.js321
-rw-r--r--accessible/tests/browser/mac/browser_range.js190
-rw-r--r--accessible/tests/browser/mac/browser_required.js175
-rw-r--r--accessible/tests/browser/mac/browser_rich_listbox.js73
-rw-r--r--accessible/tests/browser/mac/browser_roles_elements.js334
-rw-r--r--accessible/tests/browser/mac/browser_rootgroup.js246
-rw-r--r--accessible/tests/browser/mac/browser_rotor.js1752
-rw-r--r--accessible/tests/browser/mac/browser_selectables.js342
-rw-r--r--accessible/tests/browser/mac/browser_table.js629
-rw-r--r--accessible/tests/browser/mac/browser_text_basics.js380
-rw-r--r--accessible/tests/browser/mac/browser_text_input.js657
-rw-r--r--accessible/tests/browser/mac/browser_text_leaf.js83
-rw-r--r--accessible/tests/browser/mac/browser_text_selection.js187
-rw-r--r--accessible/tests/browser/mac/browser_toggle_radio_check.js304
-rw-r--r--accessible/tests/browser/mac/browser_webarea.js77
-rw-r--r--accessible/tests/browser/mac/doc_aria_tabs.html95
-rw-r--r--accessible/tests/browser/mac/doc_menulist.xhtml19
-rw-r--r--accessible/tests/browser/mac/doc_rich_listbox.xhtml22
-rw-r--r--accessible/tests/browser/mac/doc_textmarker_test.html2424
-rw-r--r--accessible/tests/browser/mac/doc_tree.xhtml59
-rw-r--r--accessible/tests/browser/mac/head.js133
-rw-r--r--accessible/tests/browser/role/browser.ini11
-rw-r--r--accessible/tests/browser/role/browser_computedARIARole.js88
-rw-r--r--accessible/tests/browser/role/head.js18
-rw-r--r--accessible/tests/browser/scroll/browser.ini15
-rw-r--r--accessible/tests/browser/scroll/browser_test_scrollTo.js36
-rw-r--r--accessible/tests/browser/scroll/browser_test_scroll_bounds.js606
-rw-r--r--accessible/tests/browser/scroll/browser_test_scroll_substring.js67
-rw-r--r--accessible/tests/browser/scroll/browser_test_zoom_text.js145
-rw-r--r--accessible/tests/browser/scroll/head.js18
-rw-r--r--accessible/tests/browser/selectable/browser.ini12
-rw-r--r--accessible/tests/browser/selectable/browser_test_aria_select.js164
-rw-r--r--accessible/tests/browser/selectable/browser_test_select.js329
-rw-r--r--accessible/tests/browser/selectable/head.js88
-rw-r--r--accessible/tests/browser/shared-head.js918
-rw-r--r--accessible/tests/browser/states/browser.ini19
-rw-r--r--accessible/tests/browser/states/browser_test_link.js44
-rw-r--r--accessible/tests/browser/states/browser_test_select_visibility.js76
-rw-r--r--accessible/tests/browser/states/browser_test_visibility.js181
-rw-r--r--accessible/tests/browser/states/browser_test_visibility_2.js131
-rw-r--r--accessible/tests/browser/states/head.js91
-rw-r--r--accessible/tests/browser/telemetry/browser.ini8
-rw-r--r--accessible/tests/browser/telemetry/browser_HCM_telemetry.js365
-rw-r--r--accessible/tests/browser/text/browser.ini18
-rw-r--r--accessible/tests/browser/text/browser_editabletext.js173
-rw-r--r--accessible/tests/browser/text/browser_text.js326
-rw-r--r--accessible/tests/browser/text/browser_text_caret.js452
-rw-r--r--accessible/tests/browser/text/browser_text_paragraph_boundary.js22
-rw-r--r--accessible/tests/browser/text/browser_text_selection.js344
-rw-r--r--accessible/tests/browser/text/browser_text_spelling.js151
-rw-r--r--accessible/tests/browser/text/browser_textleafpoint.js485
-rw-r--r--accessible/tests/browser/text/head.js276
-rw-r--r--accessible/tests/browser/tree/browser.ini19
-rw-r--r--accessible/tests/browser/tree/browser_aria_owns.js278
-rw-r--r--accessible/tests/browser/tree/browser_browser_element.js16
-rw-r--r--accessible/tests/browser/tree/browser_css_content_visibility.js121
-rw-r--r--accessible/tests/browser/tree/browser_general.js128
-rw-r--r--accessible/tests/browser/tree/browser_lazy_tabs.js43
-rw-r--r--accessible/tests/browser/tree/browser_searchbar.js84
-rw-r--r--accessible/tests/browser/tree/browser_shadowdom.js98
-rw-r--r--accessible/tests/browser/tree/browser_test_nsIAccessibleDocument_URL.js54
-rw-r--r--accessible/tests/browser/tree/head.js33
-rw-r--r--accessible/tests/crashtests/1072792.xhtml5
-rw-r--r--accessible/tests/crashtests/1380199.html15
-rw-r--r--accessible/tests/crashtests/1402999.html11
-rw-r--r--accessible/tests/crashtests/1415667.html1
-rw-r--r--accessible/tests/crashtests/1463962.html4
-rw-r--r--accessible/tests/crashtests/1472024-1.html7
-rw-r--r--accessible/tests/crashtests/1472024-2.html20
-rw-r--r--accessible/tests/crashtests/1484778.html26
-rw-r--r--accessible/tests/crashtests/1494707.html15
-rw-r--r--accessible/tests/crashtests/1503964.html4
-rw-r--r--accessible/tests/crashtests/1572811.html9
-rw-r--r--accessible/tests/crashtests/1578282.html21
-rw-r--r--accessible/tests/crashtests/1585851.html21
-rw-r--r--accessible/tests/crashtests/1655983.html6
-rw-r--r--accessible/tests/crashtests/448064.xhtml70
-rw-r--r--accessible/tests/crashtests/884202.html21
-rw-r--r--accessible/tests/crashtests/890760.html14
-rw-r--r--accessible/tests/crashtests/893515.html15
-rw-r--r--accessible/tests/crashtests/crashtests.list22
-rw-r--r--accessible/tests/crashtests/last_test_to_unload_testsuite.xhtml36
-rw-r--r--accessible/tests/mochitest/.eslintrc.js24
-rw-r--r--accessible/tests/mochitest/a11y.ini21
-rw-r--r--accessible/tests/mochitest/actions.js231
-rw-r--r--accessible/tests/mochitest/actions/a11y.ini17
-rw-r--r--accessible/tests/mochitest/actions/test_anchors.html146
-rw-r--r--accessible/tests/mochitest/actions/test_aria.html200
-rw-r--r--accessible/tests/mochitest/actions/test_controls.html107
-rw-r--r--accessible/tests/mochitest/actions/test_general.html105
-rw-r--r--accessible/tests/mochitest/actions/test_general.xhtml167
-rw-r--r--accessible/tests/mochitest/actions/test_keys.html57
-rw-r--r--accessible/tests/mochitest/actions/test_keys.xhtml124
-rw-r--r--accessible/tests/mochitest/actions/test_link.html145
-rw-r--r--accessible/tests/mochitest/actions/test_media.html130
-rw-r--r--accessible/tests/mochitest/actions/test_select.html67
-rw-r--r--accessible/tests/mochitest/actions/test_tree.xhtml127
-rw-r--r--accessible/tests/mochitest/actions/test_treegrid.xhtml190
-rw-r--r--accessible/tests/mochitest/aom/a11y.ini3
-rw-r--r--accessible/tests/mochitest/aom/test_general.html208
-rw-r--r--accessible/tests/mochitest/attributes.js516
-rw-r--r--accessible/tests/mochitest/attributes/a11y.ini14
-rw-r--r--accessible/tests/mochitest/attributes/test_dpub_aria_xml-roles.html120
-rw-r--r--accessible/tests/mochitest/attributes/test_graphics_aria_xml-roles.html48
-rw-r--r--accessible/tests/mochitest/attributes/test_listbox.html82
-rw-r--r--accessible/tests/mochitest/attributes/test_obj.html292
-rw-r--r--accessible/tests/mochitest/attributes/test_obj_css.html225
-rw-r--r--accessible/tests/mochitest/attributes/test_obj_group.html564
-rw-r--r--accessible/tests/mochitest/attributes/test_obj_group.xhtml215
-rw-r--r--accessible/tests/mochitest/attributes/test_obj_group_tree.xhtml84
-rw-r--r--accessible/tests/mochitest/attributes/test_tag.html80
-rw-r--r--accessible/tests/mochitest/attributes/test_xml-roles.html267
-rw-r--r--accessible/tests/mochitest/autocomplete.js198
-rw-r--r--accessible/tests/mochitest/bounds/a11y.ini5
-rw-r--r--accessible/tests/mochitest/bounds/test_list.html78
-rw-r--r--accessible/tests/mochitest/browser.js156
-rw-r--r--accessible/tests/mochitest/common.js1046
-rw-r--r--accessible/tests/mochitest/dumbfile.zipbin0 -> 22 bytes
-rw-r--r--accessible/tests/mochitest/elm/a11y.ini14
-rw-r--r--accessible/tests/mochitest/elm/test_HTMLSpec.html2024
-rw-r--r--accessible/tests/mochitest/elm/test_MathMLSpec.html616
-rw-r--r--accessible/tests/mochitest/elm/test_figure.html60
-rw-r--r--accessible/tests/mochitest/elm/test_listbox.xhtml73
-rw-r--r--accessible/tests/mochitest/elm/test_nsApplicationAcc.html67
-rw-r--r--accessible/tests/mochitest/elm/test_shadowroot.html35
-rw-r--r--accessible/tests/mochitest/elm/test_shadowroot_subframe.html68
-rw-r--r--accessible/tests/mochitest/events.js2660
-rw-r--r--accessible/tests/mochitest/events/a11y.ini68
-rw-r--r--accessible/tests/mochitest/events/docload/a11y.ini13
-rw-r--r--accessible/tests/mochitest/events/docload/docload_wnd.html37
-rw-r--r--accessible/tests/mochitest/events/docload/test_docload_aria.html75
-rw-r--r--accessible/tests/mochitest/events/docload/test_docload_busy.html83
-rw-r--r--accessible/tests/mochitest/events/docload/test_docload_embedded.html85
-rw-r--r--accessible/tests/mochitest/events/docload/test_docload_iframe.html99
-rw-r--r--accessible/tests/mochitest/events/docload/test_docload_root.html125
-rw-r--r--accessible/tests/mochitest/events/docload/test_docload_shutdown.html142
-rw-r--r--accessible/tests/mochitest/events/focus.html10
-rw-r--r--accessible/tests/mochitest/events/scroll.html181
-rw-r--r--accessible/tests/mochitest/events/slow_image.sjs55
-rw-r--r--accessible/tests/mochitest/events/test_announcement.html61
-rw-r--r--accessible/tests/mochitest/events/test_aria_alert.html84
-rw-r--r--accessible/tests/mochitest/events/test_aria_menu.html267
-rw-r--r--accessible/tests/mochitest/events/test_aria_objattr.html68
-rw-r--r--accessible/tests/mochitest/events/test_aria_owns.html122
-rw-r--r--accessible/tests/mochitest/events/test_aria_statechange.html231
-rw-r--r--accessible/tests/mochitest/events/test_attrchange.html107
-rw-r--r--accessible/tests/mochitest/events/test_attrs.html85
-rw-r--r--accessible/tests/mochitest/events/test_bug1322593-2.html77
-rw-r--r--accessible/tests/mochitest/events/test_bug1322593.html74
-rw-r--r--accessible/tests/mochitest/events/test_caretmove.html142
-rw-r--r--accessible/tests/mochitest/events/test_coalescence.html817
-rw-r--r--accessible/tests/mochitest/events/test_contextmenu.html131
-rw-r--r--accessible/tests/mochitest/events/test_descrchange.html142
-rw-r--r--accessible/tests/mochitest/events/test_dragndrop.html106
-rw-r--r--accessible/tests/mochitest/events/test_flush.html74
-rw-r--r--accessible/tests/mochitest/events/test_focus_aria_activedescendant.html327
-rw-r--r--accessible/tests/mochitest/events/test_focus_autocomplete.html83
-rw-r--r--accessible/tests/mochitest/events/test_focus_autocomplete.xhtml507
-rw-r--r--accessible/tests/mochitest/events/test_focus_canvas.html58
-rw-r--r--accessible/tests/mochitest/events/test_focus_contextmenu.xhtml98
-rw-r--r--accessible/tests/mochitest/events/test_focus_controls.html76
-rw-r--r--accessible/tests/mochitest/events/test_focus_doc.html92
-rw-r--r--accessible/tests/mochitest/events/test_focus_general.html176
-rw-r--r--accessible/tests/mochitest/events/test_focus_general.xhtml124
-rw-r--r--accessible/tests/mochitest/events/test_focus_listcontrols.xhtml153
-rw-r--r--accessible/tests/mochitest/events/test_focus_menu.xhtml117
-rw-r--r--accessible/tests/mochitest/events/test_focus_name.html116
-rw-r--r--accessible/tests/mochitest/events/test_focus_removal.html95
-rw-r--r--accessible/tests/mochitest/events/test_focus_selects.html173
-rw-r--r--accessible/tests/mochitest/events/test_focus_tabbox.xhtml102
-rw-r--r--accessible/tests/mochitest/events/test_focus_tree.xhtml117
-rw-r--r--accessible/tests/mochitest/events/test_focusable_statechange.html128
-rw-r--r--accessible/tests/mochitest/events/test_fromUserInput.html112
-rw-r--r--accessible/tests/mochitest/events/test_label.xhtml178
-rw-r--r--accessible/tests/mochitest/events/test_menu.xhtml200
-rw-r--r--accessible/tests/mochitest/events/test_mutation.html580
-rw-r--r--accessible/tests/mochitest/events/test_namechange.html185
-rw-r--r--accessible/tests/mochitest/events/test_namechange.xhtml90
-rw-r--r--accessible/tests/mochitest/events/test_scroll.xhtml107
-rw-r--r--accessible/tests/mochitest/events/test_scroll_caret.xhtml91
-rw-r--r--accessible/tests/mochitest/events/test_selection.html109
-rw-r--r--accessible/tests/mochitest/events/test_selection.xhtml254
-rw-r--r--accessible/tests/mochitest/events/test_selection_aria.html122
-rw-r--r--accessible/tests/mochitest/events/test_statechange.html582
-rw-r--r--accessible/tests/mochitest/events/test_statechange.xhtml117
-rw-r--r--accessible/tests/mochitest/events/test_text.html310
-rw-r--r--accessible/tests/mochitest/events/test_text_alg.html249
-rw-r--r--accessible/tests/mochitest/events/test_textattrchange.html105
-rw-r--r--accessible/tests/mochitest/events/test_textselchange.html82
-rw-r--r--accessible/tests/mochitest/events/test_tree.xhtml358
-rw-r--r--accessible/tests/mochitest/events/test_valuechange.html315
-rw-r--r--accessible/tests/mochitest/focus/a11y.ini9
-rw-r--r--accessible/tests/mochitest/focus/test_focus_radio.xhtml84
-rw-r--r--accessible/tests/mochitest/focus/test_focusedChild.html81
-rw-r--r--accessible/tests/mochitest/focus/test_takeFocus.html109
-rw-r--r--accessible/tests/mochitest/focus/test_takeFocus.xhtml104
-rw-r--r--accessible/tests/mochitest/formimage.pngbin0 -> 20105 bytes
-rw-r--r--accessible/tests/mochitest/grid.js142
-rw-r--r--accessible/tests/mochitest/hittest/a11y.ini13
-rw-r--r--accessible/tests/mochitest/hittest/test_browser.html61
-rw-r--r--accessible/tests/mochitest/hittest/test_general.html110
-rw-r--r--accessible/tests/mochitest/hittest/test_menu.xhtml133
-rw-r--r--accessible/tests/mochitest/hittest/test_shadowroot.html35
-rw-r--r--accessible/tests/mochitest/hittest/test_shadowroot_subframe.html58
-rw-r--r--accessible/tests/mochitest/hittest/test_zoom.html59
-rw-r--r--accessible/tests/mochitest/hittest/test_zoom_text.html57
-rw-r--r--accessible/tests/mochitest/hittest/test_zoom_tree.xhtml97
-rw-r--r--accessible/tests/mochitest/hittest/zoom_tree.xhtml18
-rw-r--r--accessible/tests/mochitest/hyperlink/a11y.ini7
-rw-r--r--accessible/tests/mochitest/hyperlink/hyperlink.js46
-rw-r--r--accessible/tests/mochitest/hyperlink/test_general.html279
-rw-r--r--accessible/tests/mochitest/hyperlink/test_general.xhtml98
-rw-r--r--accessible/tests/mochitest/hypertext/a11y.ini7
-rw-r--r--accessible/tests/mochitest/hypertext/test_general.html150
-rw-r--r--accessible/tests/mochitest/hypertext/test_update.html214
-rw-r--r--accessible/tests/mochitest/layout.js390
-rw-r--r--accessible/tests/mochitest/letters.gifbin0 -> 5596 bytes
-rw-r--r--accessible/tests/mochitest/longdesc_src.html8
-rw-r--r--accessible/tests/mochitest/moz.build36
-rw-r--r--accessible/tests/mochitest/moz.pngbin0 -> 1991 bytes
-rw-r--r--accessible/tests/mochitest/name.js38
-rw-r--r--accessible/tests/mochitest/name/a11y.ini18
-rw-r--r--accessible/tests/mochitest/name/markup.js425
-rw-r--r--accessible/tests/mochitest/name/markuprules.xml367
-rw-r--r--accessible/tests/mochitest/name/test_ARIACore_examples.html90
-rw-r--r--accessible/tests/mochitest/name/test_browserui.xhtml85
-rw-r--r--accessible/tests/mochitest/name/test_counterstyle.html150
-rw-r--r--accessible/tests/mochitest/name/test_general.html732
-rw-r--r--accessible/tests/mochitest/name/test_general.xhtml343
-rw-r--r--accessible/tests/mochitest/name/test_link.html87
-rw-r--r--accessible/tests/mochitest/name/test_list.html103
-rw-r--r--accessible/tests/mochitest/name/test_markup.html58
-rw-r--r--accessible/tests/mochitest/name/test_svg.html53
-rw-r--r--accessible/tests/mochitest/name/test_tree.xhtml207
-rw-r--r--accessible/tests/mochitest/pivot.js664
-rw-r--r--accessible/tests/mochitest/pivot/a11y.ini8
-rw-r--r--accessible/tests/mochitest/pivot/doc_virtualcursor.html38
-rw-r--r--accessible/tests/mochitest/pivot/doc_virtualcursor_text.html37
-rw-r--r--accessible/tests/mochitest/pivot/test_virtualcursor.html119
-rw-r--r--accessible/tests/mochitest/pivot/test_virtualcursor_text.html271
-rw-r--r--accessible/tests/mochitest/promisified-events.js328
-rw-r--r--accessible/tests/mochitest/relations.js204
-rw-r--r--accessible/tests/mochitest/relations/a11y.ini14
-rw-r--r--accessible/tests/mochitest/relations/test_embeds.xhtml128
-rw-r--r--accessible/tests/mochitest/relations/test_general.html456
-rw-r--r--accessible/tests/mochitest/relations/test_general.xhtml237
-rw-r--r--accessible/tests/mochitest/relations/test_groupInfoUpdate.html57
-rw-r--r--accessible/tests/mochitest/relations/test_shadowdom.html58
-rw-r--r--accessible/tests/mochitest/relations/test_tabbrowser.xhtml109
-rw-r--r--accessible/tests/mochitest/relations/test_tree.xhtml105
-rw-r--r--accessible/tests/mochitest/relations/test_ui_modalprompt.html111
-rw-r--r--accessible/tests/mochitest/relations/test_update.html213
-rw-r--r--accessible/tests/mochitest/role.js200
-rw-r--r--accessible/tests/mochitest/role/a11y.ini13
-rw-r--r--accessible/tests/mochitest/role/chrome_body_role_alert.xhtml6
-rw-r--r--accessible/tests/mochitest/role/test_aria.html729
-rw-r--r--accessible/tests/mochitest/role/test_aria.xhtml65
-rw-r--r--accessible/tests/mochitest/role/test_dpub_aria.html114
-rw-r--r--accessible/tests/mochitest/role/test_general.html201
-rw-r--r--accessible/tests/mochitest/role/test_general.xhtml59
-rw-r--r--accessible/tests/mochitest/role/test_graphics_aria.html42
-rw-r--r--accessible/tests/mochitest/role/test_svg.html93
-rw-r--r--accessible/tests/mochitest/scroll/a11y.ini5
-rw-r--r--accessible/tests/mochitest/scroll/test_zoom.html145
-rw-r--r--accessible/tests/mochitest/selectable.js138
-rw-r--r--accessible/tests/mochitest/selectable/a11y.ini10
-rw-r--r--accessible/tests/mochitest/selectable/test_listbox.xhtml144
-rw-r--r--accessible/tests/mochitest/selectable/test_menu.xhtml77
-rw-r--r--accessible/tests/mochitest/selectable/test_menulist.xhtml95
-rw-r--r--accessible/tests/mochitest/selectable/test_tabs.xhtml93
-rw-r--r--accessible/tests/mochitest/selectable/test_tree.xhtml171
-rw-r--r--accessible/tests/mochitest/states.js365
-rw-r--r--accessible/tests/mochitest/states/a11y.ini36
-rw-r--r--accessible/tests/mochitest/states/test_aria.html655
-rw-r--r--accessible/tests/mochitest/states/test_aria.xhtml70
-rw-r--r--accessible/tests/mochitest/states/test_aria_imgmap.html75
-rw-r--r--accessible/tests/mochitest/states/test_aria_widgetitems.html152
-rw-r--r--accessible/tests/mochitest/states/test_buttons.html83
-rw-r--r--accessible/tests/mochitest/states/test_controls.html51
-rw-r--r--accessible/tests/mochitest/states/test_controls.xhtml153
-rw-r--r--accessible/tests/mochitest/states/test_doc.html95
-rw-r--r--accessible/tests/mochitest/states/test_doc_busy.html130
-rw-r--r--accessible/tests/mochitest/states/test_docarticle.html78
-rw-r--r--accessible/tests/mochitest/states/test_editablebody.html44
-rw-r--r--accessible/tests/mochitest/states/test_expandable.xhtml112
-rw-r--r--accessible/tests/mochitest/states/test_frames.html93
-rw-r--r--accessible/tests/mochitest/states/test_inputs.html268
-rw-r--r--accessible/tests/mochitest/states/test_link.html85
-rw-r--r--accessible/tests/mochitest/states/test_popup.xhtml54
-rw-r--r--accessible/tests/mochitest/states/test_selects.html166
-rw-r--r--accessible/tests/mochitest/states/test_stale.html108
-rw-r--r--accessible/tests/mochitest/states/test_tabs.xhtml66
-rw-r--r--accessible/tests/mochitest/states/test_textbox.xhtml78
-rw-r--r--accessible/tests/mochitest/states/test_tree.xhtml146
-rw-r--r--accessible/tests/mochitest/states/test_visibility.html75
-rw-r--r--accessible/tests/mochitest/states/test_visibility.xhtml162
-rw-r--r--accessible/tests/mochitest/states/z_frames.html11
-rw-r--r--accessible/tests/mochitest/states/z_frames_article.html11
-rw-r--r--accessible/tests/mochitest/states/z_frames_checkbox.html11
-rw-r--r--accessible/tests/mochitest/states/z_frames_textbox.html11
-rw-r--r--accessible/tests/mochitest/states/z_frames_update.html21
-rw-r--r--accessible/tests/mochitest/table.js851
-rw-r--r--accessible/tests/mochitest/table/a11y.ini24
-rw-r--r--accessible/tests/mochitest/table/test_css_tables.html114
-rw-r--r--accessible/tests/mochitest/table/test_headers_ariagrid.html183
-rw-r--r--accessible/tests/mochitest/table/test_headers_ariatable.html94
-rw-r--r--accessible/tests/mochitest/table/test_headers_table.html756
-rw-r--r--accessible/tests/mochitest/table/test_headers_tree.xhtml100
-rw-r--r--accessible/tests/mochitest/table/test_indexes_ariagrid.html159
-rw-r--r--accessible/tests/mochitest/table/test_indexes_table.html481
-rw-r--r--accessible/tests/mochitest/table/test_indexes_tree.xhtml70
-rw-r--r--accessible/tests/mochitest/table/test_layoutguess.html554
-rw-r--r--accessible/tests/mochitest/table/test_mtable.html160
-rw-r--r--accessible/tests/mochitest/table/test_sels_ariagrid.html147
-rw-r--r--accessible/tests/mochitest/table/test_sels_table.html155
-rw-r--r--accessible/tests/mochitest/table/test_sels_tree.xhtml76
-rw-r--r--accessible/tests/mochitest/table/test_struct_ariagrid.html163
-rw-r--r--accessible/tests/mochitest/table/test_struct_ariatreegrid.html74
-rw-r--r--accessible/tests/mochitest/table/test_struct_table.html217
-rw-r--r--accessible/tests/mochitest/table/test_struct_tree.xhtml73
-rw-r--r--accessible/tests/mochitest/table/test_table_1.html107
-rw-r--r--accessible/tests/mochitest/table/test_table_2.html87
-rw-r--r--accessible/tests/mochitest/table/test_table_mutation.html100
-rw-r--r--accessible/tests/mochitest/test_OuterDocAccessible.html87
-rw-r--r--accessible/tests/mochitest/test_aria_token_attrs.html417
-rw-r--r--accessible/tests/mochitest/test_bug420863.html99
-rw-r--r--accessible/tests/mochitest/test_custom_element_accessibility_defaults.html383
-rw-r--r--accessible/tests/mochitest/test_descr.html134
-rw-r--r--accessible/tests/mochitest/test_nsIAccessibleDocument.html94
-rw-r--r--accessible/tests/mochitest/test_nsIAccessibleImage.html198
-rw-r--r--accessible/tests/mochitest/text.js814
-rw-r--r--accessible/tests/mochitest/text/a11y.ini19
-rw-r--r--accessible/tests/mochitest/text/doc.html9
-rw-r--r--accessible/tests/mochitest/text/test_atcaretoffset.html425
-rw-r--r--accessible/tests/mochitest/text/test_charboundary.html138
-rw-r--r--accessible/tests/mochitest/text/test_doc.html40
-rw-r--r--accessible/tests/mochitest/text/test_dynamic.html80
-rw-r--r--accessible/tests/mochitest/text/test_general.xhtml79
-rw-r--r--accessible/tests/mochitest/text/test_gettext.html135
-rw-r--r--accessible/tests/mochitest/text/test_hypertext.html150
-rw-r--r--accessible/tests/mochitest/text/test_lineboundary.html422
-rw-r--r--accessible/tests/mochitest/text/test_paragraphboundary.html148
-rw-r--r--accessible/tests/mochitest/text/test_passwords.html72
-rw-r--r--accessible/tests/mochitest/text/test_selection.html119
-rw-r--r--accessible/tests/mochitest/text/test_settext_input_event.html38
-rw-r--r--accessible/tests/mochitest/text/test_textBounds.html36
-rw-r--r--accessible/tests/mochitest/text/test_wordboundary.html361
-rw-r--r--accessible/tests/mochitest/text/test_words.html131
-rw-r--r--accessible/tests/mochitest/textattrs/a11y.ini11
-rw-r--r--accessible/tests/mochitest/textattrs/test_general.html823
-rw-r--r--accessible/tests/mochitest/textattrs/test_general.xhtml51
-rw-r--r--accessible/tests/mochitest/textattrs/test_invalid.html59
-rw-r--r--accessible/tests/mochitest/textattrs/test_mathml.html47
-rw-r--r--accessible/tests/mochitest/textattrs/test_spelling.html52
-rw-r--r--accessible/tests/mochitest/textattrs/test_svg.html52
-rw-r--r--accessible/tests/mochitest/textcaret/a11y.ini5
-rw-r--r--accessible/tests/mochitest/textcaret/test_general.html174
-rw-r--r--accessible/tests/mochitest/textrange/a11y.ini7
-rw-r--r--accessible/tests/mochitest/textrange/test_general.html106
-rw-r--r--accessible/tests/mochitest/textrange/test_selection.html144
-rw-r--r--accessible/tests/mochitest/textselection/a11y.ini6
-rw-r--r--accessible/tests/mochitest/textselection/test_general.html221
-rw-r--r--accessible/tests/mochitest/textselection/test_userinput.html76
-rw-r--r--accessible/tests/mochitest/tree/a11y.ini58
-rw-r--r--accessible/tests/mochitest/tree/dockids.html32
-rw-r--r--accessible/tests/mochitest/tree/test_applicationacc.xhtml73
-rw-r--r--accessible/tests/mochitest/tree/test_aria_display_contents.html173
-rw-r--r--accessible/tests/mochitest/tree/test_aria_globals.html127
-rw-r--r--accessible/tests/mochitest/tree/test_aria_grid.html318
-rw-r--r--accessible/tests/mochitest/tree/test_aria_imgmap.html104
-rw-r--r--accessible/tests/mochitest/tree/test_aria_list.html90
-rw-r--r--accessible/tests/mochitest/tree/test_aria_menu.html91
-rw-r--r--accessible/tests/mochitest/tree/test_aria_owns.html197
-rw-r--r--accessible/tests/mochitest/tree/test_aria_presentation.html176
-rw-r--r--accessible/tests/mochitest/tree/test_aria_table.html101
-rw-r--r--accessible/tests/mochitest/tree/test_brokencontext.html214
-rw-r--r--accessible/tests/mochitest/tree/test_button.xhtml83
-rw-r--r--accessible/tests/mochitest/tree/test_canvas.html53
-rw-r--r--accessible/tests/mochitest/tree/test_combobox.xhtml116
-rw-r--r--accessible/tests/mochitest/tree/test_cssflexbox.html78
-rw-r--r--accessible/tests/mochitest/tree/test_cssoverflow.html135
-rw-r--r--accessible/tests/mochitest/tree/test_display_contents.html92
-rw-r--r--accessible/tests/mochitest/tree/test_divs.html351
-rw-r--r--accessible/tests/mochitest/tree/test_dochierarchy.html84
-rw-r--r--accessible/tests/mochitest/tree/test_dockids.html62
-rw-r--r--accessible/tests/mochitest/tree/test_filectrl.html56
-rw-r--r--accessible/tests/mochitest/tree/test_formctrl.html125
-rw-r--r--accessible/tests/mochitest/tree/test_formctrl.xhtml129
-rw-r--r--accessible/tests/mochitest/tree/test_gencontent.html69
-rw-r--r--accessible/tests/mochitest/tree/test_groupbox.xhtml63
-rw-r--r--accessible/tests/mochitest/tree/test_html_in_mathml.html61
-rw-r--r--accessible/tests/mochitest/tree/test_iframe.html50
-rw-r--r--accessible/tests/mochitest/tree/test_image.xhtml58
-rw-r--r--accessible/tests/mochitest/tree/test_img.html84
-rw-r--r--accessible/tests/mochitest/tree/test_invalid_img.xhtml48
-rw-r--r--accessible/tests/mochitest/tree/test_invalidationlist.html56
-rw-r--r--accessible/tests/mochitest/tree/test_list.html346
-rw-r--r--accessible/tests/mochitest/tree/test_map.html81
-rw-r--r--accessible/tests/mochitest/tree/test_media.html127
-rw-r--r--accessible/tests/mochitest/tree/test_select.html121
-rw-r--r--accessible/tests/mochitest/tree/test_svg.html127
-rw-r--r--accessible/tests/mochitest/tree/test_tabbox.xhtml108
-rw-r--r--accessible/tests/mochitest/tree/test_tabbrowser.xhtml261
-rw-r--r--accessible/tests/mochitest/tree/test_table.html507
-rw-r--r--accessible/tests/mochitest/tree/test_table_2.html242
-rw-r--r--accessible/tests/mochitest/tree/test_table_3.html244
-rw-r--r--accessible/tests/mochitest/tree/test_tree.xhtml182
-rw-r--r--accessible/tests/mochitest/tree/test_txtcntr.html234
-rw-r--r--accessible/tests/mochitest/tree/test_txtctrl.html171
-rw-r--r--accessible/tests/mochitest/tree/test_txtctrl.xhtml86
-rw-r--r--accessible/tests/mochitest/tree/wnd.xhtml8
-rw-r--r--accessible/tests/mochitest/treeupdate/a11y.ini46
-rw-r--r--accessible/tests/mochitest/treeupdate/test_ariadialog.html113
-rw-r--r--accessible/tests/mochitest/treeupdate/test_ariahidden.html118
-rw-r--r--accessible/tests/mochitest/treeupdate/test_ariaowns.html851
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1040735.html40
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1175913.html95
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1189277.html82
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1276857.html131
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1276857_subframe.html33
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug852150.xhtml57
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug883708.xhtml31
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug884251.xhtml19
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug895082.html49
-rw-r--r--accessible/tests/mochitest/treeupdate/test_canvas.html87
-rw-r--r--accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml315
-rw-r--r--accessible/tests/mochitest/treeupdate/test_cssoverflow.html149
-rw-r--r--accessible/tests/mochitest/treeupdate/test_deck.xhtml154
-rw-r--r--accessible/tests/mochitest/treeupdate/test_delayed_removal.html500
-rw-r--r--accessible/tests/mochitest/treeupdate/test_doc.html415
-rw-r--r--accessible/tests/mochitest/treeupdate/test_gencontent.html187
-rw-r--r--accessible/tests/mochitest/treeupdate/test_general.html174
-rw-r--r--accessible/tests/mochitest/treeupdate/test_hidden.html125
-rw-r--r--accessible/tests/mochitest/treeupdate/test_imagemap.html402
-rw-r--r--accessible/tests/mochitest/treeupdate/test_inert.html138
-rw-r--r--accessible/tests/mochitest/treeupdate/test_inner_reorder.html148
-rw-r--r--accessible/tests/mochitest/treeupdate/test_list.html139
-rw-r--r--accessible/tests/mochitest/treeupdate/test_list_editabledoc.html100
-rw-r--r--accessible/tests/mochitest/treeupdate/test_list_style.html181
-rw-r--r--accessible/tests/mochitest/treeupdate/test_listbox.xhtml181
-rw-r--r--accessible/tests/mochitest/treeupdate/test_menu.xhtml127
-rw-r--r--accessible/tests/mochitest/treeupdate/test_menubutton.xhtml141
-rw-r--r--accessible/tests/mochitest/treeupdate/test_optgroup.html122
-rw-r--r--accessible/tests/mochitest/treeupdate/test_recreation.html93
-rw-r--r--accessible/tests/mochitest/treeupdate/test_select.html191
-rw-r--r--accessible/tests/mochitest/treeupdate/test_shadow_slots.html554
-rw-r--r--accessible/tests/mochitest/treeupdate/test_shutdown.xhtml131
-rw-r--r--accessible/tests/mochitest/treeupdate/test_table.html74
-rw-r--r--accessible/tests/mochitest/treeupdate/test_textleaf.html167
-rw-r--r--accessible/tests/mochitest/treeupdate/test_tooltip.xhtml75
-rw-r--r--accessible/tests/mochitest/treeupdate/test_visibility.html411
-rw-r--r--accessible/tests/mochitest/treeupdate/test_whitespace.html200
-rw-r--r--accessible/tests/mochitest/treeview.css15
-rw-r--r--accessible/tests/mochitest/treeview.js273
-rw-r--r--accessible/tests/mochitest/value.js52
-rw-r--r--accessible/tests/mochitest/value/a11y.ini11
-rw-r--r--accessible/tests/mochitest/value/test_ariavalue.html85
-rw-r--r--accessible/tests/mochitest/value/test_datetime.html76
-rw-r--r--accessible/tests/mochitest/value/test_general.html159
-rw-r--r--accessible/tests/mochitest/value/test_meter.html82
-rw-r--r--accessible/tests/mochitest/value/test_number.html56
-rw-r--r--accessible/tests/mochitest/value/test_progress.html61
-rw-r--r--accessible/tests/mochitest/value/test_range.html59
611 files changed, 97022 insertions, 0 deletions
diff --git a/accessible/tests/browser/.eslintrc.js b/accessible/tests/browser/.eslintrc.js
new file mode 100644
index 0000000000..528797cb91
--- /dev/null
+++ b/accessible/tests/browser/.eslintrc.js
@@ -0,0 +1,28 @@
+"use strict";
+
+module.exports = {
+ rules: {
+ "mozilla/no-aArgs": "error",
+ "mozilla/reject-importGlobalProperties": ["error", "everything"],
+ "mozilla/var-only-at-top-level": "error",
+
+ "block-scoped-var": "error",
+ camelcase: ["error", { properties: "never" }],
+ complexity: ["error", 20],
+
+ "handle-callback-err": ["error", "er"],
+ "max-nested-callbacks": ["error", 4],
+ "new-cap": ["error", { capIsNew: false }],
+ "no-fallthrough": "error",
+ "no-multi-str": "error",
+ "no-proto": "error",
+ "no-return-assign": "error",
+ "no-shadow": "error",
+ "no-unused-vars": ["error", { vars: "all", args: "none" }],
+ "one-var": ["error", "never"],
+ radix: "error",
+ strict: ["error", "global"],
+ yoda: "error",
+ "no-undef-init": "error",
+ },
+};
diff --git a/accessible/tests/browser/Common.sys.mjs b/accessible/tests/browser/Common.sys.mjs
new file mode 100644
index 0000000000..466a0d2b99
--- /dev/null
+++ b/accessible/tests/browser/Common.sys.mjs
@@ -0,0 +1,451 @@
+/* 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/. */
+
+import { Assert } from "resource://testing-common/Assert.sys.mjs";
+
+const MAX_TRIM_LENGTH = 100;
+
+export const CommonUtils = {
+ /**
+ * Constant passed to getAccessible to indicate that it shouldn't fail if
+ * there is no accessible.
+ */
+ DONOTFAIL_IF_NO_ACC: 1,
+
+ /**
+ * Constant passed to getAccessible to indicate that it shouldn't fail if it
+ * does not support an interface.
+ */
+ DONOTFAIL_IF_NO_INTERFACE: 2,
+
+ /**
+ * nsIAccessibilityService service.
+ */
+ get accService() {
+ if (!this._accService) {
+ this._accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ }
+
+ return this._accService;
+ },
+
+ clearAccService() {
+ this._accService = null;
+ Cu.forceGC();
+ },
+
+ /**
+ * Adds an observer for an 'a11y-consumers-changed' event.
+ */
+ addAccConsumersChangedObserver() {
+ const deferred = {};
+ this._accConsumersChanged = new Promise(resolve => {
+ deferred.resolve = resolve;
+ });
+ const observe = (subject, topic, data) => {
+ Services.obs.removeObserver(observe, "a11y-consumers-changed");
+ deferred.resolve(JSON.parse(data));
+ };
+ Services.obs.addObserver(observe, "a11y-consumers-changed");
+ },
+
+ /**
+ * Returns a promise that resolves when 'a11y-consumers-changed' event is
+ * fired.
+ *
+ * @return {Promise}
+ * event promise evaluating to event's data
+ */
+ observeAccConsumersChanged() {
+ return this._accConsumersChanged;
+ },
+
+ /**
+ * Adds an observer for an 'a11y-init-or-shutdown' event with a value of "1"
+ * which indicates that an accessibility service is initialized in the current
+ * process.
+ */
+ addAccServiceInitializedObserver() {
+ const deferred = {};
+ this._accServiceInitialized = new Promise((resolve, reject) => {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ });
+ const observe = (subject, topic, data) => {
+ if (data === "1") {
+ Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+ deferred.resolve();
+ } else {
+ deferred.reject("Accessibility service is shutdown unexpectedly.");
+ }
+ };
+ Services.obs.addObserver(observe, "a11y-init-or-shutdown");
+ },
+
+ /**
+ * Returns a promise that resolves when an accessibility service is
+ * initialized in the current process. Otherwise (if the service is shutdown)
+ * the promise is rejected.
+ */
+ observeAccServiceInitialized() {
+ return this._accServiceInitialized;
+ },
+
+ /**
+ * Adds an observer for an 'a11y-init-or-shutdown' event with a value of "0"
+ * which indicates that an accessibility service is shutdown in the current
+ * process.
+ */
+ addAccServiceShutdownObserver() {
+ const deferred = {};
+ this._accServiceShutdown = new Promise((resolve, reject) => {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ });
+ const observe = (subject, topic, data) => {
+ if (data === "0") {
+ Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+ deferred.resolve();
+ } else {
+ deferred.reject("Accessibility service is initialized unexpectedly.");
+ }
+ };
+ Services.obs.addObserver(observe, "a11y-init-or-shutdown");
+ },
+
+ /**
+ * Returns a promise that resolves when an accessibility service is shutdown
+ * in the current process. Otherwise (if the service is initialized) the
+ * promise is rejected.
+ */
+ observeAccServiceShutdown() {
+ return this._accServiceShutdown;
+ },
+
+ /**
+ * Extract DOMNode id from an accessible. If the accessible is in the remote
+ * process, DOMNode is not present in parent process. However, if specified by
+ * the author, DOMNode id will be attached to an accessible object.
+ *
+ * @param {nsIAccessible} accessible accessible
+ * @return {String?} DOMNode id if available
+ */
+ getAccessibleDOMNodeID(accessible) {
+ if (accessible instanceof Ci.nsIAccessibleDocument) {
+ // If accessible is a document, trying to find its document body id.
+ try {
+ return accessible.DOMNode.body.id;
+ } catch (e) {
+ /* This only works if accessible is not a proxy. */
+ }
+ }
+ try {
+ return accessible.DOMNode.id;
+ } catch (e) {
+ /* This will fail if DOMNode is in different process. */
+ }
+ try {
+ // When e10s is enabled, accessible will have an "id" property if its
+ // corresponding DOMNode has an id. If accessible is a document, its "id"
+ // property corresponds to the "id" of its body element.
+ return accessible.id;
+ } catch (e) {
+ /* This will fail if accessible is not a proxy. */
+ }
+
+ return null;
+ },
+
+ getObjAddress(obj) {
+ const exp = /native\s*@\s*(0x[a-f0-9]+)/g;
+ const match = exp.exec(obj.toString());
+ if (match) {
+ return match[1];
+ }
+
+ return obj.toString();
+ },
+
+ getNodePrettyName(node) {
+ try {
+ let tag = "";
+ if (node.nodeType == Node.DOCUMENT_NODE) {
+ tag = "document";
+ } else {
+ tag = node.localName;
+ if (node.nodeType == Node.ELEMENT_NODE && node.hasAttribute("id")) {
+ tag += `@id="${node.getAttribute("id")}"`;
+ }
+ }
+
+ return `"${tag} node", address: ${this.getObjAddress(node)}`;
+ } catch (e) {
+ return `" no node info "`;
+ }
+ },
+
+ /**
+ * Convert role to human readable string.
+ */
+ roleToString(role) {
+ return this.accService.getStringRole(role);
+ },
+
+ /**
+ * Shorten a long string if it exceeds MAX_TRIM_LENGTH.
+ *
+ * @param aString the string to shorten.
+ *
+ * @returns the shortened string.
+ */
+ shortenString(str) {
+ if (str.length <= MAX_TRIM_LENGTH) {
+ return str;
+ }
+
+ // Trim the string if its length is > MAX_TRIM_LENGTH characters.
+ const trimOffset = MAX_TRIM_LENGTH / 2;
+
+ return `${str.substring(0, trimOffset - 1)}…${str.substring(
+ str.length - trimOffset,
+ str.length
+ )}`;
+ },
+
+ normalizeAccTreeObj(obj) {
+ const key = Object.keys(obj)[0];
+ const roleName = `ROLE_${key}`;
+ if (roleName in Ci.nsIAccessibleRole) {
+ return {
+ role: Ci.nsIAccessibleRole[roleName],
+ children: obj[key],
+ };
+ }
+
+ return obj;
+ },
+
+ stringifyTree(obj) {
+ let text = this.roleToString(obj.role) + ": [ ";
+ if ("children" in obj) {
+ for (let i = 0; i < obj.children.length; i++) {
+ const c = this.normalizeAccTreeObj(obj.children[i]);
+ text += this.stringifyTree(c);
+ if (i < obj.children.length - 1) {
+ text += ", ";
+ }
+ }
+ }
+
+ return `${text}] `;
+ },
+
+ /**
+ * Return pretty name for identifier, it may be ID, DOM node or accessible.
+ */
+ prettyName(identifier) {
+ if (identifier instanceof Array) {
+ let msg = "";
+ for (let idx = 0; idx < identifier.length; idx++) {
+ if (msg != "") {
+ msg += ", ";
+ }
+
+ msg += this.prettyName(identifier[idx]);
+ }
+ return msg;
+ }
+
+ if (identifier instanceof Ci.nsIAccessible) {
+ const acc = this.getAccessible(identifier);
+ const domID = this.getAccessibleDOMNodeID(acc);
+ let msg = "[";
+ try {
+ if (Services.appinfo.browserTabsRemoteAutostart) {
+ if (domID) {
+ msg += `DOM node id: ${domID}, `;
+ }
+ } else {
+ msg += `${this.getNodePrettyName(acc.DOMNode)}, `;
+ }
+ msg += `role: ${this.roleToString(acc.role)}`;
+ if (acc.name) {
+ msg += `, name: "${this.shortenString(acc.name)}"`;
+ }
+ } catch (e) {
+ msg += "defunct";
+ }
+
+ if (acc) {
+ msg += `, address: ${this.getObjAddress(acc)}`;
+ }
+ msg += "]";
+
+ return msg;
+ }
+
+ if (Node.isInstance(identifier)) {
+ return `[ ${this.getNodePrettyName(identifier)} ]`;
+ }
+
+ if (identifier && typeof identifier === "object") {
+ const treeObj = this.normalizeAccTreeObj(identifier);
+ if ("role" in treeObj) {
+ return `{ ${this.stringifyTree(treeObj)} }`;
+ }
+
+ return JSON.stringify(identifier);
+ }
+
+ return ` "${identifier}" `;
+ },
+
+ /**
+ * Return accessible for the given identifier (may be ID attribute or DOM
+ * element or accessible object) or null.
+ *
+ * @param accOrElmOrID
+ * identifier to get an accessible implementing the given interfaces
+ * @param aInterfaces
+ * [optional] the interface or an array interfaces to query it/them
+ * from obtained accessible
+ * @param elmObj
+ * [optional] object to store DOM element which accessible is obtained
+ * for
+ * @param doNotFailIf
+ * [optional] no error for special cases (see DONOTFAIL_IF_NO_ACC,
+ * DONOTFAIL_IF_NO_INTERFACE)
+ * @param doc
+ * [optional] document for when accOrElmOrID is an ID.
+ */
+ getAccessible(accOrElmOrID, interfaces, elmObj, doNotFailIf, doc) {
+ if (!accOrElmOrID) {
+ return null;
+ }
+
+ let elm = null;
+ if (accOrElmOrID instanceof Ci.nsIAccessible) {
+ try {
+ elm = accOrElmOrID.DOMNode;
+ } catch (e) {}
+ } else if (Node.isInstance(accOrElmOrID)) {
+ elm = accOrElmOrID;
+ } else {
+ elm = doc.getElementById(accOrElmOrID);
+ if (!elm) {
+ Assert.ok(false, `Can't get DOM element for ${accOrElmOrID}`);
+ return null;
+ }
+ }
+
+ if (elmObj && typeof elmObj == "object") {
+ elmObj.value = elm;
+ }
+
+ let acc = accOrElmOrID instanceof Ci.nsIAccessible ? accOrElmOrID : null;
+ if (!acc) {
+ try {
+ acc = this.accService.getAccessibleFor(elm);
+ } catch (e) {}
+
+ if (!acc) {
+ if (!(doNotFailIf & this.DONOTFAIL_IF_NO_ACC)) {
+ Assert.ok(
+ false,
+ `Can't get accessible for ${this.prettyName(accOrElmOrID)}`
+ );
+ }
+
+ return null;
+ }
+ }
+
+ if (!interfaces) {
+ return acc;
+ }
+
+ if (!(interfaces instanceof Array)) {
+ interfaces = [interfaces];
+ }
+
+ for (let index = 0; index < interfaces.length; index++) {
+ if (acc instanceof interfaces[index]) {
+ continue;
+ }
+
+ try {
+ acc.QueryInterface(interfaces[index]);
+ } catch (e) {
+ if (!(doNotFailIf & this.DONOTFAIL_IF_NO_INTERFACE)) {
+ Assert.ok(
+ false,
+ `Can't query ${interfaces[index]} for ${accOrElmOrID}`
+ );
+ }
+
+ return null;
+ }
+ }
+
+ return acc;
+ },
+
+ /**
+ * Return the DOM node by identifier (may be accessible, DOM node or ID).
+ */
+ getNode(accOrNodeOrID, doc) {
+ if (!accOrNodeOrID) {
+ return null;
+ }
+
+ if (Node.isInstance(accOrNodeOrID)) {
+ return accOrNodeOrID;
+ }
+
+ if (accOrNodeOrID instanceof Ci.nsIAccessible) {
+ return accOrNodeOrID.DOMNode;
+ }
+
+ const node = doc.getElementById(accOrNodeOrID);
+ if (!node) {
+ Assert.ok(false, `Can't get DOM element for ${accOrNodeOrID}`);
+ return null;
+ }
+
+ return node;
+ },
+
+ /**
+ * Return root accessible.
+ *
+ * @param {DOMNode} doc
+ * Chrome document.
+ *
+ * @return {nsIAccessible}
+ * Accessible object for chrome window.
+ */
+ getRootAccessible(doc) {
+ const acc = this.getAccessible(doc);
+ return acc ? acc.rootDocument.QueryInterface(Ci.nsIAccessible) : null;
+ },
+
+ /**
+ * Analogy of SimpleTest.is function used to compare objects.
+ */
+ isObject(obj, expectedObj, msg) {
+ if (obj == expectedObj) {
+ Assert.ok(true, msg);
+ return;
+ }
+
+ Assert.ok(
+ false,
+ `${msg} - got "${this.prettyName(obj)}", expected "${this.prettyName(
+ expectedObj
+ )}"`
+ );
+ },
+};
diff --git a/accessible/tests/browser/Layout.sys.mjs b/accessible/tests/browser/Layout.sys.mjs
new file mode 100644
index 0000000000..15b0060717
--- /dev/null
+++ b/accessible/tests/browser/Layout.sys.mjs
@@ -0,0 +1,178 @@
+/* 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/. */
+
+import { Assert } from "resource://testing-common/Assert.sys.mjs";
+
+import { CommonUtils } from "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs";
+
+export const Layout = {
+ /**
+ * Zoom the given document.
+ */
+ zoomDocument(doc, zoom) {
+ const bc = BrowsingContext.getFromWindow(doc.defaultView);
+ // To mirror the behaviour of the UI, we set the zoom
+ // value on the top level browsing context. This value automatically
+ // propagates down to iframes.
+ bc.top.fullZoom = zoom;
+ },
+
+ /**
+ * Set the relative resolution of this document. This is what apz does.
+ * On non-mobile platforms you won't see a visible change.
+ */
+ setResolution(doc, zoom) {
+ const windowUtils = doc.defaultView.windowUtils;
+ windowUtils.setResolutionAndScaleTo(zoom);
+ },
+
+ /**
+ * Assert.is() function checking the expected value is within the range.
+ */
+ isWithin(expected, got, within, msg) {
+ if (Math.abs(got - expected) <= within) {
+ Assert.ok(true, `${msg} - Got ${got}`);
+ } else {
+ Assert.ok(
+ false,
+ `${msg} - Got ${got}, expected ${expected} with error of ${within}`
+ );
+ }
+ },
+
+ /**
+ * Return the accessible coordinates relative to the screen in device pixels.
+ */
+ getPos(id) {
+ const accessible = CommonUtils.getAccessible(id);
+ const x = {};
+ const y = {};
+ accessible.getBounds(x, y, {}, {});
+
+ return [x.value, y.value];
+ },
+
+ /**
+ * Return the accessible coordinates and size relative to the screen in device
+ * pixels. This methods also retrieves coordinates in CSS pixels and ensures that they
+ * match Dev pixels with a given device pixel ratio.
+ */
+ getBounds(id, dpr) {
+ const accessible = CommonUtils.getAccessible(id);
+ const x = {};
+ const y = {};
+ const width = {};
+ const height = {};
+ const xInCSS = {};
+ const yInCSS = {};
+ const widthInCSS = {};
+ const heightInCSS = {};
+ accessible.getBounds(x, y, width, height);
+ accessible.getBoundsInCSSPixels(xInCSS, yInCSS, widthInCSS, heightInCSS);
+
+ this.isWithin(
+ x.value / dpr,
+ xInCSS.value,
+ 1,
+ "X in CSS pixels is calculated correctly"
+ );
+ this.isWithin(
+ y.value / dpr,
+ yInCSS.value,
+ 1,
+ "Y in CSS pixels is calculated correctly"
+ );
+ this.isWithin(
+ width.value / dpr,
+ widthInCSS.value,
+ 1,
+ "Width in CSS pixels is calculated correctly"
+ );
+ this.isWithin(
+ height.value / dpr,
+ heightInCSS.value,
+ 1,
+ "Height in CSS pixels is calculated correctly"
+ );
+
+ return [x.value, y.value, width.value, height.value];
+ },
+
+ getRangeExtents(id, startOffset, endOffset, coordOrigin) {
+ const hyperText = CommonUtils.getAccessible(id, [Ci.nsIAccessibleText]);
+ const x = {};
+ const y = {};
+ const width = {};
+ const height = {};
+ hyperText.getRangeExtents(
+ startOffset,
+ endOffset,
+ x,
+ y,
+ width,
+ height,
+ coordOrigin
+ );
+
+ return [x.value, y.value, width.value, height.value];
+ },
+
+ CSSToDevicePixels(win, x, y, width, height) {
+ const ratio = win.devicePixelRatio;
+
+ // CSS pixels and ratio can be not integer. Device pixels are always integer.
+ // Do our best and hope it works.
+ return [
+ Math.round(x * ratio),
+ Math.round(y * ratio),
+ Math.round(width * ratio),
+ Math.round(height * ratio),
+ ];
+ },
+
+ /**
+ * Return DOM node coordinates relative the screen and its size in device
+ * pixels.
+ */
+ getBoundsForDOMElm(id, doc) {
+ let x = 0;
+ let y = 0;
+ let width = 0;
+ let height = 0;
+
+ const elm = CommonUtils.getNode(id, doc);
+ const elmWindow = elm.ownerGlobal;
+ if (elm.localName == "area") {
+ const mapName = elm.parentNode.getAttribute("name");
+ const selector = `[usemap="#${mapName}"]`;
+ const img = elm.ownerDocument.querySelector(selector);
+
+ const areaCoords = elm.coords.split(",");
+ const areaX = parseInt(areaCoords[0], 10);
+ const areaY = parseInt(areaCoords[1], 10);
+ const areaWidth = parseInt(areaCoords[2], 10) - areaX;
+ const areaHeight = parseInt(areaCoords[3], 10) - areaY;
+
+ const rect = img.getBoundingClientRect();
+ x = rect.left + areaX;
+ y = rect.top + areaY;
+ width = areaWidth;
+ height = areaHeight;
+ } else {
+ const rect = elm.getBoundingClientRect();
+ x = rect.left;
+ y = rect.top;
+ width = rect.width;
+ height = rect.height;
+ }
+
+ return this.CSSToDevicePixels(
+ elmWindow,
+ x + elmWindow.mozInnerScreenX,
+ y + elmWindow.mozInnerScreenY,
+ width,
+ height
+ );
+ },
+};
diff --git a/accessible/tests/browser/bounds/browser.ini b/accessible/tests/browser/bounds/browser.ini
new file mode 100644
index 0000000000..078659a97d
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser.ini
@@ -0,0 +1,27 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_accessible_moved.js]
+[browser_caret_rect.js]
+[browser_position.js]
+[browser_test_resolution.js]
+skip-if = os == 'win' # bug 1372296
+[browser_test_zoom.js]
+skip-if = true # Bug 1734271
+[browser_test_zoom_text.js]
+https_first_disabled = true
+skip-if = os == 'win' # bug 1372296
+[browser_zero_area.js]
+[browser_test_display_contents.js]
+[browser_test_simple_transform.js]
+[browser_test_iframe_transform.js]
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
diff --git a/accessible/tests/browser/bounds/browser_accessible_moved.js b/accessible/tests/browser/bounds/browser_accessible_moved.js
new file mode 100644
index 0000000000..307c680000
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_accessible_moved.js
@@ -0,0 +1,49 @@
+/* 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 assertBoundsNonZero(acc) {
+ // XXX We don't use getBounds because it uses BoundsInCSSPixels(), but that
+ // isn't implemented for the cache yet.
+ let x = {};
+ let y = {};
+ let width = {};
+ let height = {};
+ acc.getBounds(x, y, width, height);
+ ok(x.value > 0, "x is non-0");
+ ok(y.value > 0, "y is non-0");
+ ok(width.value > 0, "width is non-0");
+ ok(height.value > 0, "height is non-0");
+}
+
+/**
+ * Test that bounds aren't 0 after an Accessible is moved (but not re-created).
+ */
+addAccessibleTask(
+ `
+<div id="root" role="group"><div id="scrollable" role="presentation" style="height: 1px;"><button id="button">test</button></div></div>
+ `,
+ async function (browser, docAcc) {
+ let button = findAccessibleChildByID(docAcc, "button");
+ assertBoundsNonZero(button);
+
+ const root = findAccessibleChildByID(docAcc, "root");
+ let reordered = waitForEvent(EVENT_REORDER, root);
+ // scrollable wasn't in the a11y tree, but this will force it to be created.
+ // button will be moved inside it.
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("scrollable").style.overflow = "scroll";
+ });
+ await reordered;
+
+ const scrollable = findAccessibleChildByID(docAcc, "scrollable");
+ assertBoundsNonZero(scrollable);
+ // XXX button's RemoteAccessible was recreated, so we have to fetch it
+ // again. This shouldn't be necessary once bug 1739050 is fixed.
+ button = findAccessibleChildByID(docAcc, "button");
+ assertBoundsNonZero(button);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/bounds/browser_caret_rect.js b/accessible/tests/browser/bounds/browser_caret_rect.js
new file mode 100644
index 0000000000..ac0ee3aa50
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_caret_rect.js
@@ -0,0 +1,140 @@
+/* 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";
+
+async function getCaretRect(browser, id) {
+ // The caret rect can only be queried on LocalAccessible. On Windows, we do
+ // send it across processes with caret events, but this currently can't be
+ // queried outside of the event, nor with XPCOM.
+ const [x, y, w, h] = await invokeContentTask(browser, [id], contentId => {
+ const node = content.document.getElementById(contentId);
+ const contentAcc = content.CommonUtils.accService.getAccessibleFor(node);
+ contentAcc.QueryInterface(Ci.nsIAccessibleText);
+ const caretX = {};
+ const caretY = {};
+ const caretW = {};
+ const caretH = {};
+ contentAcc.getCaretRect(caretX, caretY, caretW, caretH);
+ return [caretX.value, caretY.value, caretW.value, caretH.value];
+ });
+ info(`Caret bounds: ${x}, ${y}, ${w}, ${h}`);
+ return [x, y, w, h];
+}
+
+async function testCaretRect(browser, docAcc, id, offset) {
+ const acc = findAccessibleChildByID(docAcc, id, [nsIAccessibleText]);
+ is(acc.caretOffset, offset, `Caret at offset ${offset}`);
+ const charX = {};
+ const charY = {};
+ const charW = {};
+ const charH = {};
+ const atEnd = offset == acc.characterCount;
+ const empty = offset == 0 && atEnd;
+ const queryOffset = atEnd && !empty ? offset - 1 : offset;
+ acc.getCharacterExtents(
+ queryOffset,
+ charX,
+ charY,
+ charW,
+ charH,
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ info(
+ `Character ${queryOffset} bounds: ${charX.value}, ${charY.value}, ${charW.value}, ${charH.value}`
+ );
+ const [caretX, caretY, caretW, caretH] = await getCaretRect(browser, id);
+ if (atEnd) {
+ ok(caretX > charX.value, "Caret x after last character x");
+ } else {
+ is(caretX, charX.value, "Caret x same as character x");
+ }
+ is(caretY, charY.value, "Caret y same as character y");
+ is(caretW, 1, "Caret width is 1");
+ if (!empty) {
+ is(caretH, charH.value, "Caret height same as character height");
+ }
+}
+
+function getAccBounds(acc) {
+ const x = {};
+ const y = {};
+ const w = {};
+ const h = {};
+ acc.getBounds(x, y, w, h);
+ return [x.value, y.value, w.value, h.value];
+}
+
+/**
+ * Test the caret rect in content documents.
+ */
+addAccessibleTask(
+ `
+<input id="input" value="ab">
+<input id="emptyInput">
+ `,
+ async function (browser, docAcc) {
+ async function runTests() {
+ const input = findAccessibleChildByID(docAcc, "input", [
+ nsIAccessibleText,
+ ]);
+ info("Focusing input");
+ let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
+ input.takeFocus();
+ await caretMoved;
+ await testCaretRect(browser, docAcc, "input", 0);
+ info("Setting caretOffset to 1");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
+ input.caretOffset = 1;
+ await caretMoved;
+ await testCaretRect(browser, docAcc, "input", 1);
+ info("Setting caretOffset to 2");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
+ input.caretOffset = 2;
+ await caretMoved;
+ await testCaretRect(browser, docAcc, "input", 2);
+ info("Resetting caretOffset to 0");
+ input.caretOffset = 0;
+
+ const emptyInput = findAccessibleChildByID(docAcc, "emptyInput", [
+ nsIAccessibleText,
+ ]);
+ info("Focusing emptyInput");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, emptyInput);
+ emptyInput.takeFocus();
+ await caretMoved;
+ await testCaretRect(browser, docAcc, "emptyInput", 0);
+ }
+
+ await runTests();
+
+ // Check that the caret rect is correct when the title bar is shown.
+ if (LINUX || Services.env.get("MOZ_HEADLESS")) {
+ // Disabling tabs in title bar doesn't change the bounds on Linux or in
+ // headless mode.
+ info("Skipping title bar tests");
+ return;
+ }
+ const [, origDocY] = getAccBounds(docAcc);
+ info("Showing title bar");
+ let titleBarChanged = BrowserTestUtils.waitForMutationCondition(
+ document.documentElement,
+ { attributes: true, attributeFilter: ["tabsintitlebar"] },
+ () => !document.documentElement.hasAttribute("tabsintitlebar")
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.tabs.inTitlebar", false]],
+ });
+ await titleBarChanged;
+ const [, newDocY] = getAccBounds(docAcc);
+ Assert.greater(
+ newDocY,
+ origDocY,
+ "Doc has larger y after title bar change"
+ );
+ await runTests();
+ await SpecialPowers.popPrefEnv();
+ },
+ { chrome: true, topLevel: true }
+);
diff --git a/accessible/tests/browser/bounds/browser_position.js b/accessible/tests/browser/bounds/browser_position.js
new file mode 100644
index 0000000000..18de7d8a76
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_position.js
@@ -0,0 +1,103 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Test changing the left/top CSS properties.
+ */
+addAccessibleTask(
+ `
+<div id="div" style="position: relative; left: 0px; top: 0px; width: fit-content;">
+ test
+</div>
+ `,
+ async function (browser, docAcc) {
+ await testBoundsWithContent(docAcc, "div", browser);
+ info("Changing left");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("div").style.left = "200px";
+ });
+ await waitForContentPaint(browser);
+ await testBoundsWithContent(docAcc, "div", browser);
+ info("Changing top");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("div").style.top = "200px";
+ });
+ await waitForContentPaint(browser);
+ await testBoundsWithContent(docAcc, "div", browser);
+ },
+ { chrome: true, topLevel: true, iframe: true }
+);
+
+/**
+ * Test moving one element by inserting a second element before it such that the
+ * second element doesn't reflow.
+ */
+addAccessibleTask(
+ `
+<div id="reflowContainer">
+ <p>1</p>
+ <p id="reflow2" hidden>2</p>
+ <p id="reflow3">3</p>
+</div>
+<p id="noReflow">noReflow</p>
+ `,
+ async function (browser, docAcc) {
+ for (const id of ["reflowContainer", "reflow3", "noReflow"]) {
+ await testBoundsWithContent(docAcc, id, browser);
+ }
+ // Show p2, which will reflow everything inside "reflowContainer", but just
+ // move "noReflow" down without reflowing it.
+ info("Showing p2");
+ let shown = waitForEvent(EVENT_SHOW, "reflow2");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("reflow2").hidden = false;
+ });
+ await waitForContentPaint(browser);
+ await shown;
+ for (const id of ["reflowContainer", "reflow2", "reflow3", "noReflow"]) {
+ await testBoundsWithContent(docAcc, id, browser);
+ }
+ },
+ { chrome: true, topLevel: true, remoteIframe: true }
+);
+
+/**
+ * Test bounds when an Accessible is re-parented.
+ */
+addAccessibleTask(
+ `
+<div id="container">
+ <p style="height: 300px;">1</p>
+ <div class="pParent">
+ <p id="p2">2</p>
+ </div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const paraTree = { PARAGRAPH: [{ TEXT_LEAF: [] }] };
+ const container = findAccessibleChildByID(docAcc, "container");
+ testAccessibleTree(container, { SECTION: [paraTree, paraTree] });
+ await testBoundsWithContent(docAcc, "p2", browser);
+ // Add a click listener to the div containing p2. This will cause an
+ // Accessible to be created for that div, which didn't have one before.
+ info("Adding click listener to pParent");
+ let reordered = waitForEvent(EVENT_REORDER, container);
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .querySelector(".pParent")
+ .addEventListener("click", function () {});
+ });
+ await reordered;
+ testAccessibleTree(container, {
+ SECTION: [paraTree, { SECTION: [paraTree] }],
+ });
+ await testBoundsWithContent(docAcc, "p2", browser);
+ },
+ { chrome: true, topLevel: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/bounds/browser_test_display_contents.js b/accessible/tests/browser/bounds/browser_test_display_contents.js
new file mode 100644
index 0000000000..db1bfce178
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_test_display_contents.js
@@ -0,0 +1,44 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/layout.js */
+
+async function testContentBounds(browser, acc) {
+ let [expectedX, expectedY, expectedWidth, expectedHeight] =
+ await getContentBoundsForDOMElm(browser, getAccessibleDOMNodeID(acc));
+
+ let contentDPR = await getContentDPR(browser);
+ let [x, y, width, height] = getBounds(acc, contentDPR);
+ let prettyAccName = prettyName(acc);
+ is(x, expectedX, "Wrong x coordinate of " + prettyAccName);
+ is(y, expectedY, "Wrong y coordinate of " + prettyAccName);
+ is(width, expectedWidth, "Wrong width of " + prettyAccName);
+ ok(height >= expectedHeight, "Wrong height of " + prettyAccName);
+}
+
+async function runTests(browser, accDoc) {
+ let p = findAccessibleChildByID(accDoc, "div");
+ let p2 = findAccessibleChildByID(accDoc, "p");
+
+ await testContentBounds(browser, p);
+ await testContentBounds(browser, p2);
+}
+
+/**
+ * Test accessible bounds for accs with display:contents
+ */
+addAccessibleTask(
+ `
+ <div id="div">before
+ <ul id="ul" style="display: contents;">
+ <li id="li" style="display: contents;">
+ <p id="p">item</p>
+ </li>
+ </ul>
+ </div>`,
+ runTests,
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/bounds/browser_test_iframe_transform.js b/accessible/tests/browser/bounds/browser_test_iframe_transform.js
new file mode 100644
index 0000000000..a44ab75faf
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_test_iframe_transform.js
@@ -0,0 +1,209 @@
+/* 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 TRANSLATION_OFFSET = 50;
+const ELEM_ID = "test-elem-id";
+
+// Modify the style of an iframe within the content process. This is different
+// from, e.g., invokeSetStyle, because this function doesn't rely on
+// invokeContentTask, which runs in the context of the iframe itself.
+async function invokeSetStyleIframe(browser, id, style, value) {
+ if (value) {
+ Logger.log(`Setting ${style} style to ${value} for iframe with id: ${id}`);
+ } else {
+ Logger.log(`Removing ${style} style from iframe with id: ${id}`);
+ }
+
+ // Translate the iframe itself (not content within it).
+ await SpecialPowers.spawn(
+ browser,
+ [id, style, value],
+ (iframeId, iframeStyle, iframeValue) => {
+ const elm = content.document.getElementById(iframeId);
+ if (iframeValue) {
+ elm.style[iframeStyle] = iframeValue;
+ } else {
+ delete elm.style[iframeStyle];
+ }
+ }
+ );
+}
+
+// Test the accessible's bounds, comparing them to the content bounds from DOM.
+// This function also accepts an offset, which is necessary in some cases where
+// DOM doesn't know about cross-process offsets.
+function testBoundsWithOffset(browser, iframeDocAcc, id, domElmBounds, offset) {
+ // Get the bounds as reported by the accessible.
+ const acc = findAccessibleChildByID(iframeDocAcc, id);
+ const accX = {};
+ const accY = {};
+ const accWidth = {};
+ const accHeight = {};
+ acc.getBounds(accX, accY, accWidth, accHeight);
+
+ // getContentBoundsForDOMElm's result doesn't include iframe translation
+ // for in-process iframes, but does for out-of-process iframes. To account
+ // for that here, manually add in the translation offset when examining an
+ // in-process iframe.
+ const addTranslationOffset = !gIsRemoteIframe;
+ const expectedX = addTranslationOffset
+ ? domElmBounds[0] + offset
+ : domElmBounds[0];
+ const expectedY = addTranslationOffset
+ ? domElmBounds[1] + offset
+ : domElmBounds[1];
+ const expectedWidth = domElmBounds[2];
+ const expectedHeight = domElmBounds[3];
+
+ let boundsAreEquivalent = true;
+ boundsAreEquivalent &&= accX.value == expectedX;
+ boundsAreEquivalent &&= accY.value == expectedY;
+ boundsAreEquivalent &&= accWidth.value == expectedWidth;
+ boundsAreEquivalent &&= accHeight.value == expectedHeight;
+ return boundsAreEquivalent;
+}
+
+addAccessibleTask(
+ `<div id='${ELEM_ID}'>hello world</div>`,
+ async function (browser, iframeDocAcc, contentDocAcc) {
+ ok(iframeDocAcc, "IFRAME document accessible is present");
+
+ await testBoundsWithContent(iframeDocAcc, ELEM_ID, browser);
+
+ // Translate the iframe, which should modify cross-process offset.
+ await invokeSetStyleIframe(
+ browser,
+ DEFAULT_IFRAME_ID,
+ "transform",
+ `translate(${TRANSLATION_OFFSET}px, ${TRANSLATION_OFFSET}px)`
+ );
+
+ // Allow content to advance to update DOM, then capture the DOM bounds.
+ await waitForContentPaint(browser);
+ const domElmBoundsAfterTranslate = await getContentBoundsForDOMElm(
+ browser,
+ ELEM_ID
+ );
+
+ // Ensure that there's enough time for the cache to update.
+ await untilCacheOk(() => {
+ return testBoundsWithOffset(
+ browser,
+ iframeDocAcc,
+ ELEM_ID,
+ domElmBoundsAfterTranslate,
+ TRANSLATION_OFFSET
+ );
+ }, "Accessible bounds have changed in the cache and match DOM bounds.");
+
+ // Adjust padding of the iframe, then verify bounds adjust properly.
+ // iframes already have a border by default, so we check padding here.
+ const PADDING_OFFSET = 100;
+ await invokeSetStyleIframe(
+ browser,
+ DEFAULT_IFRAME_ID,
+ "padding",
+ `${PADDING_OFFSET}px`
+ );
+
+ // Allow content to advance to update DOM, then capture the DOM bounds.
+ await waitForContentPaint(browser);
+ const domElmBoundsAfterAddingPadding = await getContentBoundsForDOMElm(
+ browser,
+ ELEM_ID
+ );
+
+ await untilCacheOk(() => {
+ return testBoundsWithOffset(
+ browser,
+ iframeDocAcc,
+ ELEM_ID,
+ domElmBoundsAfterAddingPadding,
+ TRANSLATION_OFFSET
+ );
+ }, "Accessible bounds have changed in the cache and match DOM bounds.");
+ },
+ {
+ topLevel: false,
+ iframe: true,
+ remoteIframe: true,
+ iframeAttrs: {
+ style: `height: 100px; width: 100px;`,
+ },
+ }
+);
+
+/**
+ * Test document bounds change notifications.
+ * Note: This uses iframes to change the doc container size in order
+ * to have the doc accessible's bounds change.
+ */
+addAccessibleTask(
+ `<div id="div" style="width: 30px; height: 30px"></div>`,
+ async function (browser, accDoc, foo) {
+ const docWidth = () => {
+ let width = {};
+ accDoc.getBounds({}, {}, width, {});
+ return width.value;
+ };
+
+ await untilCacheIs(docWidth, 0, "Doc width is 0");
+ await invokeSetStyleIframe(browser, DEFAULT_IFRAME_ID, "width", `300px`);
+ await untilCacheIs(docWidth, 300, "Doc width is 300");
+ },
+ {
+ chrome: false,
+ topLevel: false,
+ iframe: true,
+ remoteIframe: true,
+ iframeAttrs: { style: "width: 0;" },
+ }
+);
+
+/**
+ * Test document bounds after re-creating an iframe.
+ */
+addAccessibleTask(
+ `
+<ol id="ol">
+ <iframe id="iframe" src="data:text/html,"></iframe>
+</ol>
+ `,
+ async function (browser, docAcc) {
+ let iframeDoc = findAccessibleChildByID(docAcc, "iframe").firstChild;
+ ok(iframeDoc, "Got the iframe document");
+ const origX = {};
+ const origY = {};
+ iframeDoc.getBounds(origX, origY, {}, {});
+ let reordered = waitForEvent(EVENT_REORDER, docAcc);
+ await invokeContentTask(browser, [], () => {
+ // This will cause a bounds cache update to be queued for the iframe doc.
+ content.document.getElementById("iframe").width = "600";
+ // This will recreate the ol a11y subtree, including the iframe. The
+ // iframe document will be unbound briefly while this happens. We want to
+ // be sure processing the bounds cache update queued above doesn't assert
+ // while the document is unbound. The setTimeout is necessary to get the
+ // cache update to happen at the right time.
+ content.setTimeout(
+ () => (content.document.getElementById("ol").type = "i"),
+ 0
+ );
+ });
+ await reordered;
+ const iframe = findAccessibleChildByID(docAcc, "iframe");
+ // We don't currently fire an event when a DocAccessible is re-bound to a new OuterDoc.
+ await BrowserTestUtils.waitForCondition(() => iframe.firstChild);
+ iframeDoc = iframe.firstChild;
+ ok(iframeDoc, "Got the iframe document after re-creation");
+ const newX = {};
+ const newY = {};
+ iframeDoc.getBounds(newX, newY, {}, {});
+ ok(
+ origX.value == newX.value && origY.value == newY.value,
+ "Iframe document x and y are same after iframe re-creation"
+ );
+ }
+);
diff --git a/accessible/tests/browser/bounds/browser_test_resolution.js b/accessible/tests/browser/bounds/browser_test_resolution.js
new file mode 100644
index 0000000000..0b0b47418d
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_test_resolution.js
@@ -0,0 +1,72 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/layout.js */
+
+async function testScaledBounds(browser, accDoc, scale, id, type = "object") {
+ let acc = findAccessibleChildByID(accDoc, id);
+
+ // Get document offset
+ let [docX, docY] = getBounds(accDoc);
+
+ // Get the unscaled bounds of the accessible
+ let [x, y, width, height] =
+ type == "text"
+ ? getRangeExtents(acc, 0, -1, COORDTYPE_SCREEN_RELATIVE)
+ : getBounds(acc);
+
+ await invokeContentTask(browser, [scale], _scale => {
+ const { Layout } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs"
+ );
+ Layout.setResolution(content.document, _scale);
+ });
+
+ let [scaledX, scaledY, scaledWidth, scaledHeight] =
+ type == "text"
+ ? getRangeExtents(acc, 0, -1, COORDTYPE_SCREEN_RELATIVE)
+ : getBounds(acc);
+
+ let name = prettyName(acc);
+ isWithin(scaledWidth, width * scale, 2, "Wrong scaled width of " + name);
+ isWithin(scaledHeight, height * scale, 2, "Wrong scaled height of " + name);
+ isWithin(scaledX - docX, (x - docX) * scale, 2, "Wrong scaled x of " + name);
+ isWithin(scaledY - docY, (y - docY) * scale, 2, "Wrong scaled y of " + name);
+
+ await invokeContentTask(browser, [], () => {
+ const { Layout } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs"
+ );
+ Layout.setResolution(content.document, 1.0);
+ });
+}
+
+async function runTests(browser, accDoc) {
+ // The scrollbars get in the way of container bounds calculation.
+ await SpecialPowers.pushPrefEnv({
+ set: [["ui.useOverlayScrollbars", 1]],
+ });
+
+ await testScaledBounds(browser, accDoc, 2.0, "p1");
+ await testScaledBounds(browser, accDoc, 0.5, "p2");
+ await testScaledBounds(browser, accDoc, 3.5, "b1");
+
+ await testScaledBounds(browser, accDoc, 2.0, "p1", "text");
+ await testScaledBounds(browser, accDoc, 0.75, "p2", "text");
+}
+
+/**
+ * Test accessible boundaries when page is zoomed
+ */
+addAccessibleTask(
+ `
+<p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p>
+<p id="p2">para 2</p>
+<button id="b1">Hello</button>
+`,
+ runTests,
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/bounds/browser_test_simple_transform.js b/accessible/tests/browser/bounds/browser_test_simple_transform.js
new file mode 100644
index 0000000000..7197968b40
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_test_simple_transform.js
@@ -0,0 +1,225 @@
+/* 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";
+
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+// test basic translation
+addAccessibleTask(
+ `<p id="translate">hello world</p>`,
+ async function (browser, iframeDocAcc, contentDocAcc) {
+ ok(iframeDocAcc, "IFRAME document accessible is present");
+ await testBoundsWithContent(iframeDocAcc, "translate", browser);
+
+ await invokeContentTask(browser, [], () => {
+ let p = content.document.getElementById("translate");
+ p.style = "transform: translate(100px, 100px);";
+ });
+
+ await waitForContentPaint(browser);
+ await testBoundsWithContent(iframeDocAcc, "translate", browser);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true }
+);
+
+// Test translation with two children.
+addAccessibleTask(
+ `
+<div role="main" style="translate: 0 300px;">
+ <p id="p1">hello</p>
+ <p id="p2">world</p>
+</div>
+ `,
+ async function (browser, docAcc) {
+ await testBoundsWithContent(docAcc, "p1", browser);
+ await testBoundsWithContent(docAcc, "p2", browser);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true }
+);
+
+// test basic rotation
+addAccessibleTask(
+ `<p id="rotate">hello world</p>`,
+ async function (browser, iframeDocAcc, contentDocAcc) {
+ ok(iframeDocAcc, "IFRAME document accessible is present");
+ await testBoundsWithContent(iframeDocAcc, "rotate", browser);
+
+ await invokeContentTask(browser, [], () => {
+ let p = content.document.getElementById("rotate");
+ p.style = "transform: rotate(-40deg);";
+ });
+
+ await waitForContentPaint(browser);
+ await testBoundsWithContent(iframeDocAcc, "rotate", browser);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true }
+);
+
+// test basic scale
+addAccessibleTask(
+ `<p id="scale">hello world</p>`,
+ async function (browser, iframeDocAcc, contentDocAcc) {
+ ok(iframeDocAcc, "IFRAME document accessible is present");
+ await testBoundsWithContent(iframeDocAcc, "scale", browser);
+
+ await invokeContentTask(browser, [], () => {
+ let p = content.document.getElementById("scale");
+ p.style = "transform: scale(2);";
+ });
+
+ await waitForContentPaint(browser);
+ await testBoundsWithContent(iframeDocAcc, "scale", browser);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true }
+);
+
+// Test will-change: transform with no transform.
+addAccessibleTask(
+ `
+<div id="willChangeTop" style="will-change: transform;">
+ <p>hello</p>
+ <p id="willChangeTopP2">world</p>
+</div>
+<div role="group">
+ <div id="willChangeInner" style="will-change: transform;">
+ <p>hello</p>
+ <p id="willChangeInnerP2">world</p>
+ </div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ // Even though willChangeTop has no transform, it has
+ // will-change: transform, which means nsIFrame::IsTransformed returns
+ // true. We don't cache identity matrices, but because there is an offset
+ // to the root frame, layout includes this in the returned transform
+ // matrix. That means we get a non-identity matrix and thus we cache it.
+ // This is why we only test the identity matrix cache optimization for
+ // willChangeInner.
+ let hasTransform;
+ try {
+ const willChangeInner = findAccessibleChildByID(
+ docAcc,
+ "willChangeInner"
+ );
+ willChangeInner.cache.getStringProperty("transform");
+ hasTransform = true;
+ } catch (e) {
+ hasTransform = false;
+ }
+ ok(!hasTransform, "willChangeInner has no cached transform");
+ await testBoundsWithContent(docAcc, "willChangeTopP2", browser);
+ await testBoundsWithContent(docAcc, "willChangeInnerP2", browser);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that a transform forces creation of an accessible.
+addAccessibleTask(
+ `
+<div id="container">
+ <div style="transform:translate(100px,100px);">
+ <p>test</p>
+ </div>
+</div>
+
+<div id="div-presentational" role="presentation" style="transform:translate(100px,100px);">
+ <p>test</p>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const tree = { TEXT_CONTAINER: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }] };
+
+ const divWithTransform = findAccessibleChildByID(
+ docAcc,
+ "container"
+ ).firstChild;
+ testAccessibleTree(divWithTransform, tree);
+ // testBoundsWithContent takes an id, but divWithTransform doesn't have one,
+ // so we can't test the bounds for it.
+
+ // An accessible should still be created, even if the role is "presentation."
+ const divPresentational = findAccessibleChildByID(
+ docAcc,
+ "div-presentational"
+ );
+ testAccessibleTree(divPresentational, tree);
+ await testBoundsWithContent(docAcc, "div-presentational", browser);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that adding a transform on the fly forces creation of an accessible.
+addAccessibleTask(
+ `
+<div id="div-to-transform" role="none" style="position: absolute; width: 300px; height: 300px;">
+ <p>test</p>
+</div>
+ `,
+ async function (browser, docAcc) {
+ let divToTransform = findAccessibleChildByID(docAcc, "div-to-transform");
+ ok(!divToTransform, "There should not be a div accessible.");
+
+ // Translate the div.
+ await invokeContentTask(browser, [], () => {
+ let div = content.document.getElementById("div-to-transform");
+ div.style.transform = "translate(100%, 100%)";
+ });
+ await waitForContentPaint(browser);
+
+ // Verify that the SECTION accessible appeared after we gave it a transform.
+ divToTransform = findAccessibleChildByID(docAcc, "div-to-transform");
+ const tree = {
+ TEXT_CONTAINER: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }],
+ };
+ testAccessibleTree(divToTransform, tree);
+
+ // Verify that the bounds of the div are correctly modified.
+ await testBoundsWithContent(docAcc, "div-to-transform", browser);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true }
+);
+
+// Test translated, position: absolute Accessible in a container.
+addAccessibleTask(
+ `
+<div id="container">
+ <div id="transform" style="position: absolute; transform: translate(100px, 100px);">
+ <p id="p">test</p>
+ </div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ await testBoundsWithContent(docAcc, "transform", browser);
+ await testBoundsWithContent(docAcc, "p", browser);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true }
+);
+
+// Test bounds of a rotated element after scroll.
+addAccessibleTask(
+ `
+<div id="scrollable" style="transform: rotate(180deg); overflow: scroll; height: 500px;">
+ <p id="test">hello world</p><hr style="height: 100vh;">
+</div>
+ `,
+ async function (browser, docAcc) {
+ info(
+ "Testing that the unscrolled bounds of a transformed element are correct."
+ );
+ await testBoundsWithContent(docAcc, "test", browser);
+
+ await invokeContentTask(browser, [], () => {
+ // Scroll the scrollable region down (scrolls up due to the transform).
+ let elem = content.document.getElementById("scrollable");
+ elem.scrollTo(0, elem.scrollHeight);
+ });
+
+ info(
+ "Testing that the scrolled bounds of a transformed element are correct."
+ );
+ await testBoundsWithContent(docAcc, "test", browser);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/bounds/browser_test_zoom.js b/accessible/tests/browser/bounds/browser_test_zoom.js
new file mode 100644
index 0000000000..ac84e485a4
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_test_zoom.js
@@ -0,0 +1,65 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/layout.js */
+
+async function testContentBounds(browser, acc) {
+ let [expectedX, expectedY, expectedWidth, expectedHeight] =
+ await getContentBoundsForDOMElm(browser, getAccessibleDOMNodeID(acc));
+
+ let contentDPR = await getContentDPR(browser);
+ let [x, y, width, height] = getBounds(acc, contentDPR);
+ let prettyAccName = prettyName(acc);
+ is(x, expectedX, "Wrong x coordinate of " + prettyAccName);
+ is(y, expectedY, "Wrong y coordinate of " + prettyAccName);
+ is(width, expectedWidth, "Wrong width of " + prettyAccName);
+ ok(height >= expectedHeight, "Wrong height of " + prettyAccName);
+}
+
+async function runTests(browser, accDoc) {
+ let p1 = findAccessibleChildByID(accDoc, "p1");
+ let p2 = findAccessibleChildByID(accDoc, "p2");
+ let imgmap = findAccessibleChildByID(accDoc, "imgmap");
+ if (!imgmap.childCount) {
+ // An image map may not be available even after the doc and image load
+ // is complete. We don't recieve any DOM events for this change either,
+ // so we need to wait for a REORDER.
+ await waitForEvent(EVENT_REORDER, "imgmap");
+ }
+ let area = imgmap.firstChild;
+
+ await testContentBounds(browser, p1);
+ await testContentBounds(browser, p2);
+ await testContentBounds(browser, area);
+
+ await SpecialPowers.spawn(browser, [], () => {
+ const { Layout } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs"
+ );
+ Layout.zoomDocument(content.document, 2.0);
+ });
+
+ await testContentBounds(browser, p1);
+ await testContentBounds(browser, p2);
+ await testContentBounds(browser, area);
+}
+
+/**
+ * Test accessible boundaries when page is zoomed
+ */
+addAccessibleTask(
+ `
+<p id="p1">para 1</p><p id="p2">para 2</p>
+<map name="atoz_map" id="map">
+ <area id="area1" href="http://mozilla.org"
+ coords=17,0,30,14" alt="mozilla.org" shape="rect">
+</map>
+<img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="http://example.com/a11y/accessible/tests/mochitest/letters.gif">`,
+ runTests,
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/bounds/browser_test_zoom_text.js b/accessible/tests/browser/bounds/browser_test_zoom_text.js
new file mode 100644
index 0000000000..3f40b698bf
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_test_zoom_text.js
@@ -0,0 +1,86 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/layout.js */
+
+async function runTests(browser, accDoc) {
+ async function testTextNode(id) {
+ let hyperTextNode = findAccessibleChildByID(accDoc, id);
+ let textNode = hyperTextNode.firstChild;
+
+ let contentDPR = await getContentDPR(browser);
+ let [x, y, width, height] = getBounds(textNode, contentDPR);
+ testTextBounds(
+ hyperTextNode,
+ 0,
+ -1,
+ [x, y, width, height],
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ // A 0 range should return an empty rect.
+ testTextBounds(
+ hyperTextNode,
+ 0,
+ 0,
+ [0, 0, 0, 0],
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ }
+
+ async function testEmptyInputNode(id) {
+ let inputNode = findAccessibleChildByID(accDoc, id);
+
+ let [x, y, width, height] = getBounds(inputNode);
+ testTextBounds(
+ inputNode,
+ 0,
+ -1,
+ [x, y, width, height],
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ // A 0 range in an empty input should still return
+ // rect of input node.
+ testTextBounds(
+ inputNode,
+ 0,
+ 0,
+ [x, y, width, height],
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ }
+
+ await testTextNode("p1");
+ await testTextNode("p2");
+ await testEmptyInputNode("i1");
+
+ await SpecialPowers.spawn(browser, [], () => {
+ const { Layout } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs"
+ );
+ Layout.zoomDocument(content.document, 2.0);
+ });
+
+ await testTextNode("p1");
+
+ await SpecialPowers.spawn(browser, [], () => {
+ const { Layout } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs"
+ );
+ Layout.zoomDocument(content.document, 1.0);
+ });
+}
+
+/**
+ * Test the text range boundary when page is zoomed
+ */
+addAccessibleTask(
+ `
+ <p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p>
+ <p id='p2'>ل</p>
+ <form><input id='i1' /></form>`,
+ runTests,
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/bounds/browser_zero_area.js b/accessible/tests/browser/bounds/browser_zero_area.js
new file mode 100644
index 0000000000..c0f9db2673
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_zero_area.js
@@ -0,0 +1,118 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/layout.js */
+
+async function testContentBounds(browser, acc, expectedWidth, expectedHeight) {
+ let [expectedX, expectedY] = await getContentBoundsForDOMElm(
+ browser,
+ getAccessibleDOMNodeID(acc)
+ );
+
+ let contentDPR = await getContentDPR(browser);
+ let [x, y, width, height] = getBounds(acc, contentDPR);
+ let prettyAccName = prettyName(acc);
+ is(x, expectedX, "Wrong x coordinate of " + prettyAccName);
+ is(y, expectedY, "Wrong y coordinate of " + prettyAccName);
+ is(width, expectedWidth, "Wrong width of " + prettyAccName);
+ is(height, expectedHeight, "Wrong height of " + prettyAccName);
+}
+/**
+ * Test accessible bounds with different combinations of overflow and
+ * non-zero frame area.
+ */
+addAccessibleTask(
+ `
+ <div id="a1" style="height:100px; width:100px; background:green;"></div>
+ <div id="a2" style="height:100px; width:100px; background:green;"><div style="height:300px; max-width: 300px; background:blue;"></div></div>
+ <div id="a3" style="height:0; width:0;"><div style="height:200px; width:200px; background:green;"></div></div>
+ `,
+ async function (browser, accDoc) {
+ const a1 = findAccessibleChildByID(accDoc, "a1");
+ const a2 = findAccessibleChildByID(accDoc, "a2");
+ const a3 = findAccessibleChildByID(accDoc, "a3");
+ await testContentBounds(browser, a1, 100, 100);
+ await testContentBounds(browser, a2, 100, 100);
+ await testContentBounds(browser, a3, 200, 200);
+ }
+);
+
+/**
+ * Ensure frames with zero area have their x, y coordinates correctly reported
+ * in bounds()
+ */
+addAccessibleTask(
+ `
+<br>
+<div id="a" style="height:0; width:0;"></div>
+`,
+ async function (browser, accDoc) {
+ const a = findAccessibleChildByID(accDoc, "a");
+ await testContentBounds(browser, a, 0, 0);
+ }
+);
+
+/**
+ * Ensure accessibles have accurately signed dimensions and position when
+ * offscreen.
+ */
+addAccessibleTask(
+ `
+<input type="radio" id="radio" style="left: -671091em; position: absolute;">
+`,
+ async function (browser, accDoc) {
+ const radio = findAccessibleChildByID(accDoc, "radio");
+ const contentDPR = await getContentDPR(browser);
+ const [x, y, width, height] = getBounds(radio, contentDPR);
+ ok(x < 0, "X coordinate should be negative");
+ ok(y > 0, "Y coordinate should be positive");
+ ok(width > 0, "Width should be positive");
+ ok(height > 0, "Height should be positive");
+ // Note: the exact values of x, y, width, and height
+ // are inconsistent with the DOM element values of those
+ // fields, so we don't check our bounds against them with
+ // `testContentBounds` here. DOM reports a negative width,
+ // positive height, and a slightly different (+/- 20)
+ // x and y.
+ }
+);
+
+/**
+ * Test height: 0 with align-items: flex-end. This causes the content to
+ * overflow above the frame's main rect.
+ */
+addAccessibleTask(
+ `
+<aside style="height: 0; display: flex; align-items: flex-end;">
+ <div id="inner0">testing</div>
+</aside>
+<aside style="height: 1; display: flex; align-items: flex-end;">
+ <div id="inner1">testing</div>
+</aside>
+ `,
+ async function (browser, docAcc) {
+ await testBoundsWithContent(docAcc, "inner0", browser);
+ await testBoundsWithContent(docAcc, "inner1", browser);
+ },
+ { chrome: true, topLevel: true, remoteIframe: true }
+);
+
+/**
+ * Test a div (block) inside a span (inline). This causes the span's primary
+ * frame to have an empty rect offset from its visible content.
+ */
+addAccessibleTask(
+ `
+<span id="span" tabindex="-1">
+ <div id="div">Testing</div>
+</span>
+ `,
+ async function (browser, docAcc) {
+ await testBoundsWithContent(docAcc, "span", browser);
+ await testBoundsWithContent(docAcc, "div", browser);
+ },
+ { chrome: true, topLevel: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/bounds/head.js b/accessible/tests/browser/bounds/head.js
new file mode 100644
index 0000000000..c1882b9495
--- /dev/null
+++ b/accessible/tests/browser/bounds/head.js
@@ -0,0 +1,19 @@
+/* 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";
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as events.js.
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "layout.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR }
+);
diff --git a/accessible/tests/browser/browser.ini b/accessible/tests/browser/browser.ini
new file mode 100644
index 0000000000..c7bc851086
--- /dev/null
+++ b/accessible/tests/browser/browser.ini
@@ -0,0 +1,39 @@
+[DEFAULT]
+skip-if = a11y_checks # 1534855
+subsuite = a11y
+support-files =
+ !/accessible/tests/mochitest/*.js
+ *.sys.mjs
+ head.js
+ shared-head.js
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_shutdown_acc_reference.js]
+[browser_shutdown_doc_acc_reference.js]
+[browser_shutdown_multi_acc_reference_doc.js]
+[browser_shutdown_multi_acc_reference_obj.js]
+[browser_shutdown_multi_proxy_acc_reference_doc.js]
+skip-if =
+ os == "linux" && verify && debug
+[browser_shutdown_multi_proxy_acc_reference_obj.js]
+skip-if =
+ os == "linux" && verify && debug
+[browser_shutdown_multi_reference.js]
+[browser_shutdown_parent_own_reference.js]
+skip-if =
+ os == "win" && verify && debug
+[browser_shutdown_pref.js]
+[browser_shutdown_proxy_acc_reference.js]
+[browser_shutdown_proxy_doc_acc_reference.js]
+skip-if =
+ os == "win" && verify && debug
+[browser_shutdown_remote_no_reference.js]
+skip-if =
+ os == "win" && verify && debug
+[browser_shutdown_remote_only.js]
+[browser_shutdown_remote_own_reference.js]
+[browser_shutdown_scope_lifecycle.js]
+[browser_shutdown_start_restart.js]
+skip-if =
+ verify && debug
diff --git a/accessible/tests/browser/browser_shutdown_acc_reference.js b/accessible/tests/browser/browser_shutdown_acc_reference.js
new file mode 100644
index 0000000000..1768095f94
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_acc_reference.js
@@ -0,0 +1,64 @@
+/* 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";
+
+add_task(async function () {
+ // Create a11y service.
+ const [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+
+ await a11yInit;
+ ok(accService, "Service initialized");
+
+ // Accessible object reference will live longer than the scope of this
+ // function.
+ let acc = await new Promise(resolve => {
+ let intervalId = setInterval(() => {
+ let tabAcc = accService.getAccessibleFor(gBrowser.selectedTab);
+ if (tabAcc) {
+ clearInterval(intervalId);
+ resolve(tabAcc);
+ }
+ }, 10);
+ });
+ ok(acc, "Accessible object is created");
+
+ let canShutdown = false;
+ // This promise will resolve only if canShutdown flag is set to true. If
+ // 'a11y-init-or-shutdown' event with '0' flag comes before it can be shut
+ // down, the promise will reject.
+ const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
+ await a11yShutdownObserver;
+ const a11yShutdown = new Promise((resolve, reject) =>
+ a11yShutdownPromise.then(flag =>
+ canShutdown
+ ? resolve()
+ : reject("Accessible service was shut down incorrectly")
+ )
+ );
+
+ accService = null;
+ ok(!accService, "Service is removed");
+
+ // Force garbage collection that should not trigger shutdown because there is
+ // a reference to an accessible object.
+ forceGC();
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Now allow a11y service to shutdown.
+ canShutdown = true;
+ // Remove a reference to an accessible object.
+ acc = null;
+ ok(!acc, "Accessible object is removed");
+
+ // Force garbage collection that should now trigger shutdown.
+ forceGC();
+ await a11yShutdown;
+});
diff --git a/accessible/tests/browser/browser_shutdown_doc_acc_reference.js b/accessible/tests/browser/browser_shutdown_doc_acc_reference.js
new file mode 100644
index 0000000000..8f7bf6d423
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_doc_acc_reference.js
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(async function () {
+ // Create a11y service.
+ const [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+
+ await a11yInit;
+ ok(accService, "Service initialized");
+
+ // Accessible document reference will live longer than the scope of this
+ // function.
+ let docAcc = accService.getAccessibleFor(document);
+ ok(docAcc, "Accessible document is created");
+
+ let canShutdown = false;
+ // This promise will resolve only if canShutdown flag is set to true. If
+ // 'a11y-init-or-shutdown' event with '0' flag comes before it can be shut
+ // down, the promise will reject.
+ const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
+ await a11yShutdownObserver;
+ const a11yShutdown = new Promise((resolve, reject) =>
+ a11yShutdownPromise.then(flag =>
+ canShutdown
+ ? resolve()
+ : reject("Accessible service was shut down incorrectly")
+ )
+ );
+
+ accService = null;
+ ok(!accService, "Service is removed");
+
+ // Force garbage collection that should not trigger shutdown because there is
+ // a reference to an accessible document.
+ forceGC();
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Now allow a11y service to shutdown.
+ canShutdown = true;
+ // Remove a reference to an accessible document.
+ docAcc = null;
+ ok(!docAcc, "Accessible document is removed");
+
+ // Force garbage collection that should now trigger shutdown.
+ forceGC();
+ await a11yShutdown;
+});
diff --git a/accessible/tests/browser/browser_shutdown_multi_acc_reference_doc.js b/accessible/tests/browser/browser_shutdown_multi_acc_reference_doc.js
new file mode 100644
index 0000000000..273fc7175d
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_multi_acc_reference_doc.js
@@ -0,0 +1,76 @@
+/* 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";
+
+add_task(async function () {
+ // Create a11y service.
+ const [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+
+ await a11yInit;
+ ok(accService, "Service initialized");
+
+ let docAcc = accService.getAccessibleFor(document);
+ ok(docAcc, "Accessible document is created");
+
+ // Accessible object reference will live longer than the scope of this
+ // function.
+ let acc = await new Promise(resolve => {
+ let intervalId = setInterval(() => {
+ let tabAcc = accService.getAccessibleFor(gBrowser.selectedTab);
+ if (tabAcc) {
+ clearInterval(intervalId);
+ resolve(tabAcc);
+ }
+ }, 10);
+ });
+ ok(acc, "Accessible object is created");
+
+ let canShutdown = false;
+ // This promise will resolve only if canShutdown flag is set to true. If
+ // 'a11y-init-or-shutdown' event with '0' flag comes before it can be shut
+ // down, the promise will reject.
+ const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
+ await a11yShutdownObserver;
+ const a11yShutdown = new Promise((resolve, reject) =>
+ a11yShutdownPromise.then(flag =>
+ canShutdown
+ ? resolve()
+ : reject("Accessible service was shut down incorrectly")
+ )
+ );
+
+ accService = null;
+ ok(!accService, "Service is removed");
+
+ // Force garbage collection that should not trigger shutdown because there are
+ // references to accessible objects.
+ forceGC();
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Remove a reference to an accessible object.
+ acc = null;
+ ok(!acc, "Accessible object is removed");
+ // Force garbage collection that should not trigger shutdown because there is
+ // a reference to an accessible document.
+ forceGC();
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Now allow a11y service to shutdown.
+ canShutdown = true;
+ // Remove a reference to an accessible document.
+ docAcc = null;
+ ok(!docAcc, "Accessible document is removed");
+
+ // Force garbage collection that should now trigger shutdown.
+ forceGC();
+ await a11yShutdown;
+});
diff --git a/accessible/tests/browser/browser_shutdown_multi_acc_reference_obj.js b/accessible/tests/browser/browser_shutdown_multi_acc_reference_obj.js
new file mode 100644
index 0000000000..af21b3dc4c
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_multi_acc_reference_obj.js
@@ -0,0 +1,76 @@
+/* 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";
+
+add_task(async function () {
+ // Create a11y service.
+ const [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+
+ await a11yInit;
+ ok(accService, "Service initialized");
+
+ let docAcc = accService.getAccessibleFor(document);
+ ok(docAcc, "Accessible document is created");
+
+ // Accessible object reference will live longer than the scope of this
+ // function.
+ let acc = await new Promise(resolve => {
+ let intervalId = setInterval(() => {
+ let tabAcc = accService.getAccessibleFor(gBrowser.selectedTab);
+ if (tabAcc) {
+ clearInterval(intervalId);
+ resolve(tabAcc);
+ }
+ }, 10);
+ });
+ ok(acc, "Accessible object is created");
+
+ let canShutdown = false;
+ // This promise will resolve only if canShutdown flag is set to true. If
+ // 'a11y-init-or-shutdown' event with '0' flag comes before it can be shut
+ // down, the promise will reject.
+ const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
+ await a11yShutdownObserver;
+ const a11yShutdown = new Promise((resolve, reject) =>
+ a11yShutdownPromise.then(flag =>
+ canShutdown
+ ? resolve()
+ : reject("Accessible service was shut down incorrectly")
+ )
+ );
+
+ accService = null;
+ ok(!accService, "Service is removed");
+
+ // Force garbage collection that should not trigger shutdown because there are
+ // references to accessible objects.
+ forceGC();
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Remove a reference to an accessible document.
+ docAcc = null;
+ ok(!docAcc, "Accessible document is removed");
+ // Force garbage collection that should not trigger shutdown because there is
+ // a reference to an accessible object.
+ forceGC();
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Now allow a11y service to shutdown.
+ canShutdown = true;
+ // Remove a reference to an accessible object.
+ acc = null;
+ ok(!acc, "Accessible object is removed");
+
+ // Force garbage collection that should now trigger shutdown.
+ forceGC();
+ await a11yShutdown;
+});
diff --git a/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_doc.js b/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_doc.js
new file mode 100644
index 0000000000..c09c3f6bf1
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_doc.js
@@ -0,0 +1,90 @@
+/* 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";
+
+add_task(async function () {
+ // Making sure that the e10s is enabled on Windows for testing.
+ await setE10sPrefs();
+
+ let docLoaded = waitForEvent(
+ Ci.nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE,
+ "body"
+ );
+ const [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ ok(accService, "Service initialized");
+ await a11yInit;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body id="body"><div id="div"></div></body>
+ </html>`,
+ },
+ async function (browser) {
+ let docLoadedEvent = await docLoaded;
+ let docAcc = docLoadedEvent.accessibleDocument;
+ ok(docAcc, "Accessible document proxy is created");
+ // Remove unnecessary dangling references
+ docLoaded = null;
+ docLoadedEvent = null;
+ forceGC();
+
+ let acc = docAcc.getChildAt(0);
+ ok(acc, "Accessible proxy is created");
+
+ let canShutdown = false;
+ const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
+ await a11yShutdownObserver;
+ const a11yShutdown = new Promise((resolve, reject) =>
+ a11yShutdownPromise.then(flag =>
+ canShutdown
+ ? resolve()
+ : reject("Accessible service was shut down incorrectly")
+ )
+ );
+
+ accService = null;
+ ok(!accService, "Service is removed");
+ // Force garbage collection that should not trigger shutdown because there
+ // is a reference to an accessible proxy.
+ forceGC();
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Remove a reference to an accessible proxy.
+ acc = null;
+ ok(!acc, "Accessible proxy is removed");
+ // Force garbage collection that should not trigger shutdown because there is
+ // a reference to an accessible document proxy.
+ forceGC();
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Now allow a11y service to shutdown.
+ canShutdown = true;
+ // Remove a last reference to an accessible document proxy.
+ docAcc = null;
+ ok(!docAcc, "Accessible document proxy is removed");
+
+ // Force garbage collection that should now trigger shutdown.
+ forceGC();
+ await a11yShutdown;
+ }
+ );
+
+ // Unsetting e10s related preferences.
+ await unsetE10sPrefs();
+});
diff --git a/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_obj.js b/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_obj.js
new file mode 100644
index 0000000000..4b0b22f858
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_obj.js
@@ -0,0 +1,90 @@
+/* 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";
+
+add_task(async function () {
+ // Making sure that the e10s is enabled on Windows for testing.
+ await setE10sPrefs();
+
+ let docLoaded = waitForEvent(
+ Ci.nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE,
+ "body"
+ );
+ const [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ ok(accService, "Service initialized");
+ await a11yInit;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body id="body"><div id="div"></div></body>
+ </html>`,
+ },
+ async function (browser) {
+ let docLoadedEvent = await docLoaded;
+ let docAcc = docLoadedEvent.accessibleDocument;
+ ok(docAcc, "Accessible document proxy is created");
+ // Remove unnecessary dangling references
+ docLoaded = null;
+ docLoadedEvent = null;
+ forceGC();
+
+ let acc = docAcc.getChildAt(0);
+ ok(acc, "Accessible proxy is created");
+
+ let canShutdown = false;
+ const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
+ await a11yShutdownObserver;
+ const a11yShutdown = new Promise((resolve, reject) =>
+ a11yShutdownPromise.then(flag =>
+ canShutdown
+ ? resolve()
+ : reject("Accessible service was shut down incorrectly")
+ )
+ );
+
+ accService = null;
+ ok(!accService, "Service is removed");
+ // Force garbage collection that should not trigger shutdown because there
+ // is a reference to an accessible proxy.
+ forceGC();
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Remove a reference to an accessible document proxy.
+ docAcc = null;
+ ok(!docAcc, "Accessible document proxy is removed");
+ // Force garbage collection that should not trigger shutdown because there is
+ // a reference to an accessible proxy.
+ forceGC();
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Now allow a11y service to shutdown.
+ canShutdown = true;
+ // Remove a last reference to an accessible proxy.
+ acc = null;
+ ok(!acc, "Accessible proxy is removed");
+
+ // Force garbage collection that should now trigger shutdown.
+ forceGC();
+ await a11yShutdown;
+ }
+ );
+
+ // Unsetting e10s related preferences.
+ await unsetE10sPrefs();
+});
diff --git a/accessible/tests/browser/browser_shutdown_multi_reference.js b/accessible/tests/browser/browser_shutdown_multi_reference.js
new file mode 100644
index 0000000000..a92f6faf61
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_multi_reference.js
@@ -0,0 +1,58 @@
+/* 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";
+
+add_task(async function () {
+ info("Creating a service");
+ // Create a11y service.
+ const [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ let accService1 = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ await a11yInit;
+ ok(accService1, "Service initialized");
+
+ // Add another reference to a11y service. This will not trigger
+ // 'a11y-init-or-shutdown' event
+ let accService2 = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ ok(accService2, "Service initialized");
+
+ info("Removing all service references");
+ let canShutdown = false;
+ // This promise will resolve only if canShutdown flag is set to true. If
+ // 'a11y-init-or-shutdown' event with '0' flag comes before it can be shut
+ // down, the promise will reject.
+ const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
+ await a11yShutdownObserver;
+ const a11yShutdown = new Promise((resolve, reject) =>
+ a11yShutdownPromise.then(flag =>
+ canShutdown
+ ? resolve()
+ : reject("Accessible service was shut down incorrectly")
+ )
+ );
+ // Remove first a11y service reference.
+ accService1 = null;
+ ok(!accService1, "Service is removed");
+ // Force garbage collection that should not trigger shutdown because there is
+ // another reference.
+ forceGC();
+
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Now allow a11y service to shutdown.
+ canShutdown = true;
+ // Remove last a11y service reference.
+ accService2 = null;
+ ok(!accService2, "Service is removed");
+ // Force garbage collection that should trigger shutdown.
+ forceGC();
+ await a11yShutdown;
+});
diff --git a/accessible/tests/browser/browser_shutdown_parent_own_reference.js b/accessible/tests/browser/browser_shutdown_parent_own_reference.js
new file mode 100644
index 0000000000..472d5c9c95
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_parent_own_reference.js
@@ -0,0 +1,102 @@
+/* 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";
+
+add_task(async function () {
+ // Making sure that the e10s is enabled on Windows for testing.
+ await setE10sPrefs();
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body></body>
+ </html>`,
+ },
+ async function (browser) {
+ info(
+ "Creating a service in parent and waiting for service to be created " +
+ "in content"
+ );
+ await loadContentScripts(browser, {
+ script: "Common.sys.mjs",
+ symbol: "CommonUtils",
+ });
+ // Create a11y service in the main process. This will trigger creating of
+ // the a11y service in parent as well.
+ const [parentA11yInitObserver, parentA11yInit] = initAccService();
+ const [contentA11yInitObserver, contentA11yInit] =
+ initAccService(browser);
+
+ await Promise.all([parentA11yInitObserver, contentA11yInitObserver]);
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ ok(accService, "Service initialized in parent");
+ await Promise.all([parentA11yInit, contentA11yInit]);
+
+ info(
+ "Adding additional reference to accessibility service in content " +
+ "process"
+ );
+ // Add a new reference to the a11y service inside the content process.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.CommonUtils.accService;
+ });
+
+ info(
+ "Trying to shut down a service in content and making sure it stays " +
+ "alive as it was started by parent"
+ );
+ let contentCanShutdown = false;
+ // This promise will resolve only if contentCanShutdown flag is set to true.
+ // If 'a11y-init-or-shutdown' event with '0' flag (in content) comes before
+ // it can be shut down, the promise will reject.
+ const [contentA11yShutdownObserver, contentA11yShutdownPromise] =
+ shutdownAccService(browser);
+ await contentA11yShutdownObserver;
+ const contentA11yShutdown = new Promise((resolve, reject) =>
+ contentA11yShutdownPromise.then(flag =>
+ contentCanShutdown
+ ? resolve()
+ : reject("Accessible service was shut down incorrectly")
+ )
+ );
+ // Remove a11y service reference in content and force garbage collection.
+ // This should not trigger shutdown since a11y was originally initialized by
+ // the main process.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.CommonUtils.clearAccService();
+ });
+
+ // Have some breathing room between a11y service shutdowns.
+ await TestUtils.waitForTick();
+
+ info("Removing a service in parent");
+ // Now allow a11y service to shutdown in content.
+ contentCanShutdown = true;
+ // Remove the a11y service reference in the main process.
+ const [parentA11yShutdownObserver, parentA11yShutdown] =
+ shutdownAccService();
+ await parentA11yShutdownObserver;
+
+ accService = null;
+ ok(!accService, "Service is removed in parent");
+ // Force garbage collection that should trigger shutdown in both parent and
+ // content.
+ forceGC();
+ await Promise.all([parentA11yShutdown, contentA11yShutdown]);
+
+ // Unsetting e10s related preferences.
+ await unsetE10sPrefs();
+ }
+ );
+});
diff --git a/accessible/tests/browser/browser_shutdown_pref.js b/accessible/tests/browser/browser_shutdown_pref.js
new file mode 100644
index 0000000000..74cef28b03
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_pref.js
@@ -0,0 +1,72 @@
+/* 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_ACCESSIBILITY_FORCE_DISABLED = "accessibility.force_disabled";
+
+add_task(async function testForceDisable() {
+ ok(
+ !Services.appinfo.accessibilityEnabled,
+ "Accessibility is disabled by default"
+ );
+
+ info("Reset force disabled preference");
+ Services.prefs.clearUserPref(PREF_ACCESSIBILITY_FORCE_DISABLED);
+
+ info("Enable accessibility service via XPCOM");
+ let [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ await a11yInit;
+ ok(Services.appinfo.accessibilityEnabled, "Accessibility is enabled");
+
+ info("Force disable a11y service via preference");
+ let [a11yShutdownObserver, a11yShutdown] = shutdownAccService();
+ await a11yShutdownObserver;
+
+ Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, 1);
+ await a11yShutdown;
+ ok(!Services.appinfo.accessibilityEnabled, "Accessibility is disabled");
+
+ info("Attempt to get an instance of a11y service and call its method.");
+ accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ try {
+ accService.getAccesssibleFor(document);
+ ok(false, "getAccesssibleFor should've triggered an exception.");
+ } catch (e) {
+ ok(
+ true,
+ "getAccesssibleFor triggers an exception as a11y service is shutdown."
+ );
+ }
+ ok(!Services.appinfo.accessibilityEnabled, "Accessibility is disabled");
+
+ info("Reset force disabled preference");
+ Services.prefs.clearUserPref(PREF_ACCESSIBILITY_FORCE_DISABLED);
+
+ info("Create a11y service again");
+ [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ await a11yInit;
+ ok(Services.appinfo.accessibilityEnabled, "Accessibility is enabled");
+
+ info("Remove all references to a11y service");
+ [a11yShutdownObserver, a11yShutdown] = shutdownAccService();
+ await a11yShutdownObserver;
+
+ accService = null;
+ forceGC();
+ await a11yShutdown;
+ ok(!Services.appinfo.accessibilityEnabled, "Accessibility is disabled");
+});
diff --git a/accessible/tests/browser/browser_shutdown_proxy_acc_reference.js b/accessible/tests/browser/browser_shutdown_proxy_acc_reference.js
new file mode 100644
index 0000000000..f0e93cc188
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_proxy_acc_reference.js
@@ -0,0 +1,76 @@
+/* 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";
+
+add_task(async function () {
+ // Making sure that the e10s is enabled on Windows for testing.
+ await setE10sPrefs();
+
+ const [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ ok(accService, "Service initialized");
+ await a11yInit;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body><div id="div" style="visibility: hidden;"></div></body>
+ </html>`,
+ },
+ async function (browser) {
+ let onShow = waitForEvent(Ci.nsIAccessibleEvent.EVENT_SHOW, "div");
+ await invokeSetStyle(browser, "div", "visibility", "visible");
+ let showEvent = await onShow;
+ let divAcc = showEvent.accessible;
+ ok(divAcc, "Accessible proxy is created");
+ // Remove unnecessary dangling references
+ onShow = null;
+ showEvent = null;
+ forceGC();
+
+ let canShutdown = false;
+ const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
+ await a11yShutdownObserver;
+ const a11yShutdown = new Promise((resolve, reject) =>
+ a11yShutdownPromise.then(flag =>
+ canShutdown
+ ? resolve()
+ : reject("Accessible service was shut down incorrectly")
+ )
+ );
+
+ accService = null;
+ ok(!accService, "Service is removed");
+ // Force garbage collection that should not trigger shutdown because there
+ // is a reference to an accessible proxy.
+ forceGC();
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Now allow a11y service to shutdown.
+ canShutdown = true;
+ // Remove a last reference to an accessible proxy.
+ divAcc = null;
+ ok(!divAcc, "Accessible proxy is removed");
+
+ // Force garbage collection that should now trigger shutdown.
+ forceGC();
+ await a11yShutdown;
+ }
+ );
+
+ // Unsetting e10s related preferences.
+ await unsetE10sPrefs();
+});
diff --git a/accessible/tests/browser/browser_shutdown_proxy_doc_acc_reference.js b/accessible/tests/browser/browser_shutdown_proxy_doc_acc_reference.js
new file mode 100644
index 0000000000..6d417f71eb
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_proxy_doc_acc_reference.js
@@ -0,0 +1,78 @@
+/* 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";
+
+add_task(async function () {
+ // Making sure that the e10s is enabled on Windows for testing.
+ await setE10sPrefs();
+
+ let docLoaded = waitForEvent(
+ Ci.nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE,
+ "body"
+ );
+ const [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ ok(accService, "Service initialized");
+ await a11yInit;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body id="body"></body>
+ </html>`,
+ },
+ async function (browser) {
+ let docLoadedEvent = await docLoaded;
+ let docAcc = docLoadedEvent.accessibleDocument;
+ ok(docAcc, "Accessible document proxy is created");
+ // Remove unnecessary dangling references
+ docLoaded = null;
+ docLoadedEvent = null;
+ forceGC();
+
+ let canShutdown = false;
+ const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
+ await a11yShutdownObserver;
+ const a11yShutdown = new Promise((resolve, reject) =>
+ a11yShutdownPromise.then(flag =>
+ canShutdown
+ ? resolve()
+ : reject("Accessible service was shut down incorrectly")
+ )
+ );
+
+ accService = null;
+ ok(!accService, "Service is removed");
+ // Force garbage collection that should not trigger shutdown because there
+ // is a reference to an accessible proxy.
+ forceGC();
+ // Have some breathing room when removing a11y service references.
+ await TestUtils.waitForTick();
+
+ // Now allow a11y service to shutdown.
+ canShutdown = true;
+ // Remove a last reference to an accessible document proxy.
+ docAcc = null;
+ ok(!docAcc, "Accessible document proxy is removed");
+
+ // Force garbage collection that should now trigger shutdown.
+ forceGC();
+ await a11yShutdown;
+ }
+ );
+
+ // Unsetting e10s related preferences.
+ await unsetE10sPrefs();
+});
diff --git a/accessible/tests/browser/browser_shutdown_remote_no_reference.js b/accessible/tests/browser/browser_shutdown_remote_no_reference.js
new file mode 100644
index 0000000000..dc2a7e33e9
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_remote_no_reference.js
@@ -0,0 +1,141 @@
+/* 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";
+
+add_task(async function () {
+ // Making sure that the e10s is enabled on Windows for testing.
+ await setE10sPrefs();
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body></body>
+ </html>`,
+ },
+ async function (browser) {
+ info(
+ "Creating a service in parent and waiting for service to be created " +
+ "in content"
+ );
+ await loadContentScripts(browser, {
+ script: "Common.sys.mjs",
+ symbol: "CommonUtils",
+ });
+ // Create a11y service in the main process. This will trigger creating of
+ // the a11y service in parent as well.
+ const [parentA11yInitObserver, parentA11yInit] = initAccService();
+ const [contentA11yInitObserver, contentA11yInit] =
+ initAccService(browser);
+ let [parentConsumersChangedObserver, parentConsumersChanged] =
+ accConsumersChanged();
+ let [contentConsumersChangedObserver, contentConsumersChanged] =
+ accConsumersChanged(browser);
+
+ await Promise.all([
+ parentA11yInitObserver,
+ contentA11yInitObserver,
+ parentConsumersChangedObserver,
+ contentConsumersChangedObserver,
+ ]);
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ ok(accService, "Service initialized in parent");
+ await Promise.all([parentA11yInit, contentA11yInit]);
+ await parentConsumersChanged.then(data =>
+ Assert.deepEqual(
+ data,
+ {
+ XPCOM: true,
+ MainProcess: false,
+ PlatformAPI: false,
+ },
+ "Accessibility service consumers in parent are correct."
+ )
+ );
+ await contentConsumersChanged.then(data =>
+ Assert.deepEqual(
+ data,
+ {
+ XPCOM: false,
+ MainProcess: true,
+ PlatformAPI: false,
+ },
+ "Accessibility service consumers in content are correct."
+ )
+ );
+
+ Assert.deepEqual(
+ JSON.parse(accService.getConsumers()),
+ {
+ XPCOM: true,
+ MainProcess: false,
+ PlatformAPI: false,
+ },
+ "Accessibility service consumers in parent are correct."
+ );
+
+ info(
+ "Removing a service in parent and waiting for service to be shut " +
+ "down in content"
+ );
+ // Remove a11y service reference in the main process.
+ const [parentA11yShutdownObserver, parentA11yShutdown] =
+ shutdownAccService();
+ const [contentA11yShutdownObserver, contentA11yShutdown] =
+ shutdownAccService(browser);
+ [parentConsumersChangedObserver, parentConsumersChanged] =
+ accConsumersChanged();
+ [contentConsumersChangedObserver, contentConsumersChanged] =
+ accConsumersChanged(browser);
+
+ await Promise.all([
+ parentA11yShutdownObserver,
+ contentA11yShutdownObserver,
+ parentConsumersChangedObserver,
+ contentConsumersChangedObserver,
+ ]);
+
+ accService = null;
+ ok(!accService, "Service is removed in parent");
+ // Force garbage collection that should trigger shutdown in both main and
+ // content process.
+ forceGC();
+ await Promise.all([parentA11yShutdown, contentA11yShutdown]);
+ await parentConsumersChanged.then(data =>
+ Assert.deepEqual(
+ data,
+ {
+ XPCOM: false,
+ MainProcess: false,
+ PlatformAPI: false,
+ },
+ "Accessibility service consumers are correct."
+ )
+ );
+ await contentConsumersChanged.then(data =>
+ Assert.deepEqual(
+ data,
+ {
+ XPCOM: false,
+ MainProcess: false,
+ PlatformAPI: false,
+ },
+ "Accessibility service consumers are correct."
+ )
+ );
+ }
+ );
+
+ // Unsetting e10s related preferences.
+ await unsetE10sPrefs();
+});
diff --git a/accessible/tests/browser/browser_shutdown_remote_only.js b/accessible/tests/browser/browser_shutdown_remote_only.js
new file mode 100644
index 0000000000..678031aac0
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_remote_only.js
@@ -0,0 +1,59 @@
+/* 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";
+
+add_task(async function () {
+ // Making sure that the e10s is enabled on Windows for testing.
+ await setE10sPrefs();
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body></body>
+ </html>`,
+ },
+ async function (browser) {
+ info("Creating a service in content");
+ await loadContentScripts(browser, {
+ script: "Common.sys.mjs",
+ symbol: "CommonUtils",
+ });
+ // Create a11y service in the content process.
+ const [a11yInitObserver, a11yInit] = initAccService(browser);
+ await a11yInitObserver;
+ await SpecialPowers.spawn(browser, [], () => {
+ content.CommonUtils.accService;
+ });
+ await a11yInit;
+ ok(
+ true,
+ "Accessibility service is started in content process correctly."
+ );
+
+ info("Removing a service in content");
+ // Remove a11y service reference from the content process.
+ const [a11yShutdownObserver, a11yShutdown] = shutdownAccService(browser);
+ await a11yShutdownObserver;
+ // Force garbage collection that should trigger shutdown.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.CommonUtils.clearAccService();
+ });
+ await a11yShutdown;
+ ok(
+ true,
+ "Accessibility service is shutdown in content process correctly."
+ );
+
+ // Unsetting e10s related preferences.
+ await unsetE10sPrefs();
+ }
+ );
+});
diff --git a/accessible/tests/browser/browser_shutdown_remote_own_reference.js b/accessible/tests/browser/browser_shutdown_remote_own_reference.js
new file mode 100644
index 0000000000..34fdeee3ab
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_remote_own_reference.js
@@ -0,0 +1,181 @@
+/* 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";
+
+add_task(async function () {
+ // Making sure that the e10s is enabled on Windows for testing.
+ await setE10sPrefs();
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body></body>
+ </html>`,
+ },
+ async function (browser) {
+ info(
+ "Creating a service in parent and waiting for service to be created " +
+ "in content"
+ );
+ await loadContentScripts(browser, {
+ script: "Common.sys.mjs",
+ symbol: "CommonUtils",
+ });
+ // Create a11y service in the main process. This will trigger creating of
+ // the a11y service in parent as well.
+ const [parentA11yInitObserver, parentA11yInit] = initAccService();
+ const [contentA11yInitObserver, contentA11yInit] =
+ initAccService(browser);
+ let [contentConsumersChangedObserver, contentConsumersChanged] =
+ accConsumersChanged(browser);
+
+ await Promise.all([
+ parentA11yInitObserver,
+ contentA11yInitObserver,
+ contentConsumersChangedObserver,
+ ]);
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ ok(accService, "Service initialized in parent");
+ await Promise.all([parentA11yInit, contentA11yInit]);
+ await contentConsumersChanged.then(data =>
+ Assert.deepEqual(
+ data,
+ {
+ XPCOM: false,
+ MainProcess: true,
+ PlatformAPI: false,
+ },
+ "Accessibility service consumers in content are correct."
+ )
+ );
+
+ info(
+ "Adding additional reference to accessibility service in content " +
+ "process"
+ );
+ [contentConsumersChangedObserver, contentConsumersChanged] =
+ accConsumersChanged(browser);
+ await contentConsumersChangedObserver;
+ // Add a new reference to the a11y service inside the content process.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.CommonUtils.accService;
+ });
+ await contentConsumersChanged.then(data =>
+ Assert.deepEqual(
+ data,
+ {
+ XPCOM: true,
+ MainProcess: true,
+ PlatformAPI: false,
+ },
+ "Accessibility service consumers in content are correct."
+ )
+ );
+
+ const contentConsumers = await SpecialPowers.spawn(browser, [], () =>
+ content.CommonUtils.accService.getConsumers()
+ );
+ Assert.deepEqual(
+ JSON.parse(contentConsumers),
+ {
+ XPCOM: true,
+ MainProcess: true,
+ PlatformAPI: false,
+ },
+ "Accessibility service consumers in parent are correct."
+ );
+
+ info(
+ "Shutting down a service in parent and making sure the one in " +
+ "content stays alive"
+ );
+ let contentCanShutdown = false;
+ const [parentA11yShutdownObserver, parentA11yShutdown] =
+ shutdownAccService();
+ [contentConsumersChangedObserver, contentConsumersChanged] =
+ accConsumersChanged(browser);
+ // This promise will resolve only if contentCanShutdown flag is set to true.
+ // If 'a11y-init-or-shutdown' event with '0' flag (in content) comes before
+ // it can be shut down, the promise will reject.
+ const [contentA11yShutdownObserver, contentA11yShutdownPromise] =
+ shutdownAccService(browser);
+ const contentA11yShutdown = new Promise((resolve, reject) =>
+ contentA11yShutdownPromise.then(flag =>
+ contentCanShutdown
+ ? resolve()
+ : reject("Accessible service was shut down incorrectly")
+ )
+ );
+
+ await Promise.all([
+ parentA11yShutdownObserver,
+ contentA11yShutdownObserver,
+ contentConsumersChangedObserver,
+ ]);
+ // Remove a11y service reference in the main process and force garbage
+ // collection. This should not trigger shutdown in content since a11y
+ // service is used by XPCOM.
+ accService = null;
+ ok(!accService, "Service is removed in parent");
+ // Force garbage collection that should not trigger shutdown because there
+ // is a reference in a content process.
+ forceGC();
+ await SpecialPowers.spawn(browser, [], () => {
+ SpecialPowers.Cu.forceGC();
+ });
+ await parentA11yShutdown;
+ await contentConsumersChanged.then(data =>
+ Assert.deepEqual(
+ data,
+ {
+ XPCOM: true,
+ MainProcess: false,
+ PlatformAPI: false,
+ },
+ "Accessibility service consumers in content are correct."
+ )
+ );
+
+ // Have some breathing room between a11y service shutdowns.
+ await TestUtils.waitForTick();
+
+ info("Removing a service in content");
+ // Now allow a11y service to shutdown in content.
+ contentCanShutdown = true;
+ [contentConsumersChangedObserver, contentConsumersChanged] =
+ accConsumersChanged(browser);
+ await contentConsumersChangedObserver;
+ // Remove last reference to a11y service in content and force garbage
+ // collection that should trigger shutdown.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.CommonUtils.clearAccService();
+ });
+ await contentA11yShutdown;
+ await contentConsumersChanged.then(data =>
+ Assert.deepEqual(
+ data,
+ {
+ XPCOM: false,
+ MainProcess: false,
+ PlatformAPI: false,
+ },
+ "Accessibility service consumers in content are correct."
+ )
+ );
+
+ // Unsetting e10s related preferences.
+ await unsetE10sPrefs();
+ }
+ );
+});
diff --git a/accessible/tests/browser/browser_shutdown_scope_lifecycle.js b/accessible/tests/browser/browser_shutdown_scope_lifecycle.js
new file mode 100644
index 0000000000..fafa59bb50
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_scope_lifecycle.js
@@ -0,0 +1,28 @@
+/* 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";
+
+add_task(async function () {
+ // Create a11y service inside of the function scope. Its reference should be
+ // released once the anonimous function is called.
+ const [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+ const a11yInitThenShutdown = a11yInit.then(async () => {
+ const [a11yShutdownObserver, a11yShutdown] = shutdownAccService();
+ await a11yShutdownObserver;
+ return a11yShutdown;
+ });
+
+ (function () {
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ ok(accService, "Service initialized");
+ })();
+
+ // Force garbage collection that should trigger shutdown.
+ forceGC();
+ await a11yInitThenShutdown;
+});
diff --git a/accessible/tests/browser/browser_shutdown_start_restart.js b/accessible/tests/browser/browser_shutdown_start_restart.js
new file mode 100644
index 0000000000..92d2823388
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_start_restart.js
@@ -0,0 +1,51 @@
+/* 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";
+
+add_task(async function () {
+ info("Creating a service");
+ // Create a11y service.
+ let [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ await a11yInit;
+ ok(accService, "Service initialized");
+
+ info("Removing a service");
+ // Remove the only reference to an a11y service.
+ let [a11yShutdownObserver, a11yShutdown] = shutdownAccService();
+ await a11yShutdownObserver;
+
+ accService = null;
+ ok(!accService, "Service is removed");
+ // Force garbage collection that should trigger shutdown.
+ forceGC();
+ await a11yShutdown;
+
+ info("Recreating a service");
+ // Re-create a11y service.
+ [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+
+ accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ await a11yInit;
+ ok(accService, "Service initialized again");
+
+ info("Removing a service again");
+ // Remove the only reference to an a11y service again.
+ [a11yShutdownObserver, a11yShutdown] = shutdownAccService();
+ await a11yShutdownObserver;
+
+ accService = null;
+ ok(!accService, "Service is removed again");
+ // Force garbage collection that should trigger shutdown.
+ forceGC();
+ await a11yShutdown;
+});
diff --git a/accessible/tests/browser/e10s/browser.ini b/accessible/tests/browser/e10s/browser.ini
new file mode 100644
index 0000000000..b9a511bd35
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser.ini
@@ -0,0 +1,80 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ head.js
+ doc_treeupdate_ariadialog.html
+ doc_treeupdate_ariaowns.html
+ doc_treeupdate_imagemap.html
+ doc_treeupdate_removal.xhtml
+ doc_treeupdate_visibility.html
+ doc_treeupdate_whitespace.html
+ fonts/Ahem.sjs
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/events/slow_image.sjs
+ !/accessible/tests/mochitest/letters.gif
+ !/accessible/tests/mochitest/moz.png
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+# Caching tests
+[browser_caching_actions.js]
+[browser_caching_attributes.js]
+[browser_caching_description.js]
+[browser_caching_document_props.js]
+[browser_caching_domnodeid.js]
+[browser_caching_hyperlink.js]
+[browser_caching_innerHTML.js]
+skip-if = os != 'win'
+[browser_caching_interfaces.js]
+[browser_caching_large_update.js]
+[browser_caching_name.js]
+[browser_caching_position.js]
+[browser_caching_relations.js]
+[browser_caching_relations_002.js]
+[browser_caching_states.js]
+[browser_caching_table.js]
+[browser_caching_text_bounds.js]
+[browser_caching_uniqueid.js]
+[browser_caching_value.js]
+
+# Events tests
+[browser_events_announcement.js]
+skip-if = os == 'win' # Bug 1288839
+[browser_events_caretmove.js]
+[browser_events_hide.js]
+[browser_events_show.js]
+[browser_events_statechange.js]
+[browser_events_textchange.js]
+[browser_events_vcchange.js]
+
+[browser_language.js]
+
+[browser_obj_group.js]
+[browser_obj_group_002.js]
+
+# Tree update tests
+[browser_treeupdate_ariadialog.js]
+[browser_treeupdate_ariaowns.js]
+[browser_treeupdate_canvas.js]
+[browser_treeupdate_csscontentvisibility.js]
+[browser_treeupdate_cssoverflow.js]
+[browser_treeupdate_doc.js]
+[browser_treeupdate_gencontent.js]
+[browser_treeupdate_hidden.js]
+[browser_treeupdate_image.js]
+[browser_treeupdate_imagemap.js]
+[browser_treeupdate_list.js]
+[browser_treeupdate_list_editabledoc.js]
+[browser_treeupdate_listener.js]
+[browser_treeupdate_move.js]
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_treeupdate_optgroup.js]
+[browser_treeupdate_removal.js]
+[browser_treeupdate_select_dropdown.js]
+[browser_treeupdate_table.js]
+[browser_treeupdate_textleaf.js]
+[browser_treeupdate_visibility.js]
+[browser_treeupdate_whitespace.js]
diff --git a/accessible/tests/browser/e10s/browser_caching_actions.js b/accessible/tests/browser/e10s/browser_caching_actions.js
new file mode 100644
index 0000000000..8bf3542a03
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_actions.js
@@ -0,0 +1,266 @@
+/* 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 gClickEvents = ["mousedown", "mouseup", "click"];
+
+const gActionDescrMap = {
+ jump: "Jump",
+ press: "Press",
+ check: "Check",
+ uncheck: "Uncheck",
+ select: "Select",
+ open: "Open",
+ close: "Close",
+ switch: "Switch",
+ click: "Click",
+ collapse: "Collapse",
+ expand: "Expand",
+ activate: "Activate",
+ cycle: "Cycle",
+ "click ancestor": "Click ancestor",
+};
+
+async function testActions(browser, docAcc, id, expectedActions, domEvents) {
+ const acc = findAccessibleChildByID(docAcc, id);
+ is(acc.actionCount, expectedActions.length, "Correct action count");
+
+ let actionNames = [];
+ let actionDescriptions = [];
+ for (let i = 0; i < acc.actionCount; i++) {
+ actionNames.push(acc.getActionName(i));
+ actionDescriptions.push(acc.getActionDescription(i));
+ }
+
+ is(actionNames.join(","), expectedActions.join(","), "Correct action names");
+ is(
+ actionDescriptions.join(","),
+ expectedActions.map(a => gActionDescrMap[a]).join(","),
+ "Correct action descriptions"
+ );
+
+ if (!domEvents) {
+ return;
+ }
+
+ // We need to set up the listener, and wait for the promise in two separate
+ // content tasks.
+ await invokeContentTask(browser, [id, domEvents], (_id, _domEvents) => {
+ let promises = _domEvents.map(
+ evtName =>
+ new Promise(resolve => {
+ const listener = e => {
+ if (e.target.id == _id) {
+ content.removeEventListener(evtName, listener);
+ content.evtPromise = null;
+ resolve(42);
+ }
+ };
+ content.addEventListener(evtName, listener);
+ })
+ );
+ content.evtPromise = Promise.all(promises);
+ });
+
+ acc.doAction(0);
+
+ let eventFired = await invokeContentTask(browser, [], async () => {
+ await content.evtPromise;
+ return true;
+ });
+
+ ok(eventFired, `DOM events fired '${domEvents}'`);
+}
+
+addAccessibleTask(
+ `<ul>
+ <li id="li_clickable1" onclick="">Clickable list item</li>
+ <li id="li_clickable2" onmousedown="">Clickable list item</li>
+ <li id="li_clickable3" onmouseup="">Clickable list item</li>
+ </ul>
+
+ <img id="onclick_img" onclick=""
+ src="http://example.com/a11y/accessible/tests/mochitest/moz.png">
+
+ <a id="link1" href="#">linkable textleaf accessible</a>
+ <div id="link2" onclick="">linkable textleaf accessible</div>
+
+ <a id="link3" href="#">
+ <img id="link3img" alt="image in link"
+ src="http://example.com/a11y/accessible/tests/mochitest/moz.png">
+ </a>
+
+ <div>
+ <label for="TextBox_t2" id="label1">
+ <span>Explicit</span>
+ </label>
+ <input name="in2" id="TextBox_t2" type="text" maxlength="17">
+ </div>
+
+ <div onclick=""><p id="p_in_clickable_div">p in clickable div</p></div>
+ `,
+ async function (browser, docAcc) {
+ is(docAcc.actionCount, 0, "Doc should not have any actions");
+
+ const _testActions = async (id, expectedActions, domEvents) => {
+ await testActions(browser, docAcc, id, expectedActions, domEvents);
+ };
+
+ await _testActions("li_clickable1", ["click"], gClickEvents);
+ await _testActions("li_clickable2", ["click"], gClickEvents);
+ await _testActions("li_clickable3", ["click"], gClickEvents);
+
+ await _testActions("onclick_img", ["click"], gClickEvents);
+ await _testActions("link1", ["jump"], gClickEvents);
+ await _testActions("link2", ["click"], gClickEvents);
+ await _testActions("link3", ["jump"], gClickEvents);
+ await _testActions("link3img", ["click ancestor"], gClickEvents);
+ await _testActions("label1", ["click"], gClickEvents);
+ await _testActions("p_in_clickable_div", ["click ancestor"], gClickEvents);
+
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("li_clickable1")
+ .removeAttribute("onclick");
+ });
+
+ let acc = findAccessibleChildByID(docAcc, "li_clickable1");
+ await untilCacheIs(() => acc.actionCount, 0, "li has no actions");
+ let thrown = false;
+ try {
+ acc.doAction(0);
+ } catch (e) {
+ thrown = true;
+ }
+ ok(thrown, "doAction should throw exception");
+
+ // Remove 'for' from label
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("label1").removeAttribute("for");
+ });
+ acc = findAccessibleChildByID(docAcc, "label1");
+ await untilCacheIs(() => acc.actionCount, 0, "label has no actions");
+ thrown = false;
+ try {
+ acc.doAction(0);
+ ok(false, "doAction should throw exception");
+ } catch (e) {
+ thrown = true;
+ }
+ ok(thrown, "doAction should throw exception");
+
+ // Add 'longdesc' to image
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("onclick_img")
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ .setAttribute("longdesc", "http://example.com");
+ });
+ acc = findAccessibleChildByID(docAcc, "onclick_img");
+ await untilCacheIs(() => acc.actionCount, 2, "img has 2 actions");
+ await _testActions("onclick_img", ["click", "showlongdesc"]);
+
+ // Remove 'onclick' from image with 'longdesc'
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("onclick_img").removeAttribute("onclick");
+ });
+ acc = findAccessibleChildByID(docAcc, "onclick_img");
+ await untilCacheIs(() => acc.actionCount, 1, "img has 1 actions");
+ await _testActions("onclick_img", ["showlongdesc"]);
+
+ // Remove 'href' from link and test linkable child
+ const link1Acc = findAccessibleChildByID(docAcc, "link1");
+ is(
+ link1Acc.firstChild.getActionName(0),
+ "click ancestor",
+ "linkable child has click ancestor action"
+ );
+ await invokeContentTask(browser, [], () => {
+ let link1 = content.document.getElementById("link1");
+ link1.removeAttribute("href");
+ });
+ await untilCacheIs(() => link1Acc.actionCount, 0, "link has no actions");
+ is(link1Acc.firstChild.actionCount, 0, "linkable child's actions removed");
+
+ // Add a click handler to the body. Ensure it propagates to descendants.
+ await invokeContentTask(browser, [], () => {
+ content.document.body.onclick = () => {};
+ });
+ await untilCacheIs(() => docAcc.actionCount, 1, "Doc has 1 action");
+ await _testActions("link1", ["click ancestor"]);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.body.onclick = null;
+ });
+ await untilCacheIs(() => docAcc.actionCount, 0, "Doc has no actions");
+ is(link1Acc.actionCount, 0, "link has no actions");
+
+ // Add a click handler to the root element. Ensure it propagates to
+ // descendants.
+ await invokeContentTask(browser, [], () => {
+ content.document.documentElement.onclick = () => {};
+ });
+ await untilCacheIs(() => docAcc.actionCount, 1, "Doc has 1 action");
+ await _testActions("link1", ["click ancestor"]);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test access key.
+ */
+addAccessibleTask(
+ `
+<button id="noKey">noKey</button>
+<button id="key" accesskey="a">key</button>
+ `,
+ async function (browser, docAcc) {
+ const noKey = findAccessibleChildByID(docAcc, "noKey");
+ is(noKey.accessKey, "", "noKey has no accesskey");
+ const key = findAccessibleChildByID(docAcc, "key");
+ is(key.accessKey, MAC ? "⌃⌥a" : "Alt+Shift+a", "key has correct accesskey");
+
+ info("Changing accesskey");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("key").accessKey = "b";
+ });
+ await untilCacheIs(
+ () => key.accessKey,
+ MAC ? "⌃⌥b" : "Alt+Shift+b",
+ "Correct accesskey after change"
+ );
+
+ info("Removing accesskey");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("key").removeAttribute("accesskey");
+ });
+ await untilCacheIs(
+ () => key.accessKey,
+ "",
+ "Empty accesskey after removal"
+ );
+
+ info("Adding accesskey");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("key").accessKey = "c";
+ });
+ await untilCacheIs(
+ () => key.accessKey,
+ MAC ? "⌃⌥c" : "Alt+Shift+c",
+ "Correct accesskey after addition"
+ );
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: false, // Bug 1796846
+ remoteIframe: false, // Bug 1796846
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_attributes.js b/accessible/tests/browser/e10s/browser_caching_attributes.js
new file mode 100644
index 0000000000..7b20cff49f
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_attributes.js
@@ -0,0 +1,717 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/attributes.js */
+loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Default textbox accessible attributes.
+ */
+const defaultAttributes = {
+ "margin-top": "0px",
+ "margin-right": "0px",
+ "margin-bottom": "0px",
+ "margin-left": "0px",
+ "text-align": "start",
+ "text-indent": "0px",
+ id: "textbox",
+ tag: "input",
+ display: "inline-block",
+};
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * expected {Object} expected attributes for given accessibles
+ * unexpected {Object} unexpected attributes for given accessibles
+ *
+ * action {?AsyncFunction} an optional action that awaits a change in
+ * attributes
+ * attrs {?Array} an optional list of attributes to update
+ * waitFor {?Number} an optional event to wait for
+ * }
+ */
+const attributesTests = [
+ {
+ desc: "Initiall accessible attributes",
+ expected: defaultAttributes,
+ unexpected: {
+ "line-number": "1",
+ "explicit-name": "true",
+ "container-live": "polite",
+ live: "polite",
+ },
+ },
+ {
+ desc: "@line-number attribute is present when textbox is focused",
+ async action(browser) {
+ await invokeFocus(browser, "textbox");
+ },
+ waitFor: EVENT_FOCUS,
+ expected: Object.assign({}, defaultAttributes, { "line-number": "1" }),
+ unexpected: {
+ "explicit-name": "true",
+ "container-live": "polite",
+ live: "polite",
+ },
+ },
+ {
+ desc: "@aria-live sets container-live and live attributes",
+ attrs: [
+ {
+ attr: "aria-live",
+ value: "polite",
+ },
+ ],
+ expected: Object.assign({}, defaultAttributes, {
+ "line-number": "1",
+ "container-live": "polite",
+ live: "polite",
+ }),
+ unexpected: {
+ "explicit-name": "true",
+ },
+ },
+ {
+ desc: "@title attribute sets explicit-name attribute to true",
+ attrs: [
+ {
+ attr: "title",
+ value: "textbox",
+ },
+ ],
+ expected: Object.assign({}, defaultAttributes, {
+ "line-number": "1",
+ "explicit-name": "true",
+ "container-live": "polite",
+ live: "polite",
+ }),
+ unexpected: {},
+ },
+];
+
+/**
+ * Test caching of accessible object attributes
+ */
+addAccessibleTask(
+ `
+ <input id="textbox" value="hello">`,
+ async function (browser, accDoc) {
+ let textbox = findAccessibleChildByID(accDoc, "textbox");
+ for (let {
+ desc,
+ action,
+ attrs,
+ expected,
+ waitFor,
+ unexpected,
+ } of attributesTests) {
+ info(desc);
+ let onUpdate;
+
+ if (waitFor) {
+ onUpdate = waitForEvent(waitFor, "textbox");
+ }
+
+ if (action) {
+ await action(browser);
+ } else if (attrs) {
+ for (let { attr, value } of attrs) {
+ await invokeSetAttribute(browser, "textbox", attr, value);
+ }
+ }
+
+ await onUpdate;
+ testAttrs(textbox, expected);
+ testAbsentAttrs(textbox, unexpected);
+ }
+ },
+ {
+ // These tests don't work yet with the parent process cache.
+ topLevel: false,
+ iframe: false,
+ remoteIframe: false,
+ }
+);
+
+/**
+ * Test caching of the tag attribute.
+ */
+addAccessibleTask(
+ `
+<p id="p">text</p>
+<textarea id="textarea"></textarea>
+ `,
+ async function (browser, docAcc) {
+ testAttrs(docAcc, { tag: "body" }, true);
+ const p = findAccessibleChildByID(docAcc, "p");
+ testAttrs(p, { tag: "p" }, true);
+ const textLeaf = p.firstChild;
+ testAbsentAttrs(textLeaf, { tag: "" });
+ const textarea = findAccessibleChildByID(docAcc, "textarea");
+ testAttrs(textarea, { tag: "textarea" }, true);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of the text-input-type attribute.
+ */
+addAccessibleTask(
+ `
+ <input id="default">
+ <input id="email" type="email">
+ <input id="password" type="password">
+ <input id="text" type="text">
+ <input id="date" type="date">
+ <input id="time" type="time">
+ <input id="checkbox" type="checkbox">
+ <input id="radio" type="radio">
+ `,
+ async function (browser, docAcc) {
+ function testInputType(id, inputType) {
+ if (inputType == undefined) {
+ testAbsentAttrs(findAccessibleChildByID(docAcc, id), {
+ "text-input-type": "",
+ });
+ } else {
+ testAttrs(
+ findAccessibleChildByID(docAcc, id),
+ { "text-input-type": inputType },
+ true
+ );
+ }
+ }
+
+ testInputType("default");
+ testInputType("email", "email");
+ testInputType("password", "password");
+ testInputType("text", "text");
+ testInputType("date", "date");
+ testInputType("time", "time");
+ testInputType("checkbox");
+ testInputType("radio");
+ },
+ { chrome: true, topLevel: true, iframe: false, remoteIframe: false }
+);
+
+/**
+ * Test caching of the display attribute.
+ */
+addAccessibleTask(
+ `
+<div id="div">
+ <ins id="ins">a</ins>
+ <button id="button">b</button>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const div = findAccessibleChildByID(docAcc, "div");
+ testAttrs(div, { display: "block" }, true);
+ const ins = findAccessibleChildByID(docAcc, "ins");
+ testAttrs(ins, { display: "inline" }, true);
+ const textLeaf = ins.firstChild;
+ testAbsentAttrs(textLeaf, { display: "" });
+ const button = findAccessibleChildByID(docAcc, "button");
+ testAttrs(button, { display: "inline-block" }, true);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("ins").style.display = "block";
+ content.document.body.offsetTop; // Flush layout.
+ });
+ await untilCacheIs(
+ () => ins.attributes.getStringProperty("display"),
+ "block",
+ "ins display attribute changed to block"
+ );
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test that there is no display attribute on image map areas.
+ */
+addAccessibleTask(
+ `
+<map name="normalMap">
+ <area id="normalArea" shape="default">
+</map>
+<img src="http://example.com/a11y/accessible/tests/mochitest/moz.png" usemap="#normalMap">
+<audio>
+ <map name="unslottedMap">
+ <area id="unslottedArea" shape="default">
+ </map>
+</audio>
+<img src="http://example.com/a11y/accessible/tests/mochitest/moz.png" usemap="#unslottedMap">
+ `,
+ async function (browser, docAcc) {
+ const normalArea = findAccessibleChildByID(docAcc, "normalArea");
+ testAbsentAttrs(normalArea, { display: "" });
+ const unslottedArea = findAccessibleChildByID(docAcc, "unslottedArea");
+ testAbsentAttrs(unslottedArea, { display: "" });
+ },
+ { topLevel: true }
+);
+
+/**
+ * Test caching of the explicit-name attribute.
+ */
+addAccessibleTask(
+ `
+<h1 id="h1">content</h1>
+<button id="buttonContent">content</button>
+<button id="buttonLabel" aria-label="label">content</button>
+<button id="buttonEmpty"></button>
+<button id="buttonSummary"><details><summary>test</summary></details></button>
+<div id="div"></div>
+ `,
+ async function (browser, docAcc) {
+ const h1 = findAccessibleChildByID(docAcc, "h1");
+ testAbsentAttrs(h1, { "explicit-name": "" });
+ const buttonContent = findAccessibleChildByID(docAcc, "buttonContent");
+ testAbsentAttrs(buttonContent, { "explicit-name": "" });
+ const buttonLabel = findAccessibleChildByID(docAcc, "buttonLabel");
+ testAttrs(buttonLabel, { "explicit-name": "true" }, true);
+ const buttonEmpty = findAccessibleChildByID(docAcc, "buttonEmpty");
+ testAbsentAttrs(buttonEmpty, { "explicit-name": "" });
+ const buttonSummary = findAccessibleChildByID(docAcc, "buttonSummary");
+ testAbsentAttrs(buttonSummary, { "explicit-name": "" });
+ const div = findAccessibleChildByID(docAcc, "div");
+ testAbsentAttrs(div, { "explicit-name": "" });
+
+ info("Setting aria-label on h1");
+ let nameChanged = waitForEvent(EVENT_NAME_CHANGE, h1);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("h1").setAttribute("aria-label", "label");
+ });
+ await nameChanged;
+ testAttrs(h1, { "explicit-name": "true" }, true);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of ARIA attributes that are exposed via object attributes.
+ */
+addAccessibleTask(
+ `
+<div id="currentTrue" aria-current="true">currentTrue</div>
+<div id="currentFalse" aria-current="false">currentFalse</div>
+<div id="currentPage" aria-current="page">currentPage</div>
+<div id="currentBlah" aria-current="blah">currentBlah</div>
+<div id="haspopupMenu" aria-haspopup="menu">haspopup</div>
+<div id="rowColCountPositive" role="table" aria-rowcount="1000" aria-colcount="1000">
+ <div role="row">
+ <div id="rowColIndexPositive" role="cell" aria-rowindex="100" aria-colindex="100">positive</div>
+ </div>
+</div>
+<div id="rowColCountNegative" role="table" aria-rowcount="-1" aria-colcount="-1">
+ <div role="row">
+ <div id="rowColIndexNegative" role="cell" aria-rowindex="-1" aria-colindex="-1">negative</div>
+ </div>
+</div>
+<div id="rowColCountInvalid" role="table" aria-rowcount="z" aria-colcount="z">
+ <div role="row">
+ <div id="rowColIndexInvalid" role="cell" aria-rowindex="z" aria-colindex="z">invalid</div>
+ </div>
+</div>
+<div id="foo" aria-foo="bar">foo</div>
+<div id="mutate" aria-current="true">mutate</div>
+ `,
+ async function (browser, docAcc) {
+ const currentTrue = findAccessibleChildByID(docAcc, "currentTrue");
+ testAttrs(currentTrue, { current: "true" }, true);
+ const currentFalse = findAccessibleChildByID(docAcc, "currentFalse");
+ testAbsentAttrs(currentFalse, { current: "" });
+ const currentPage = findAccessibleChildByID(docAcc, "currentPage");
+ testAttrs(currentPage, { current: "page" }, true);
+ // Test that token normalization works.
+ const currentBlah = findAccessibleChildByID(docAcc, "currentBlah");
+ testAttrs(currentBlah, { current: "true" }, true);
+ const haspopupMenu = findAccessibleChildByID(docAcc, "haspopupMenu");
+ testAttrs(haspopupMenu, { haspopup: "menu" }, true);
+
+ // Test normalization of integer values.
+ const rowColCountPositive = findAccessibleChildByID(
+ docAcc,
+ "rowColCountPositive"
+ );
+ testAttrs(
+ rowColCountPositive,
+ { rowcount: "1000", colcount: "1000" },
+ true
+ );
+ const rowColIndexPositive = findAccessibleChildByID(
+ docAcc,
+ "rowColIndexPositive"
+ );
+ testAttrs(rowColIndexPositive, { rowindex: "100", colindex: "100" }, true);
+ const rowColCountNegative = findAccessibleChildByID(
+ docAcc,
+ "rowColCountNegative"
+ );
+ testAttrs(rowColCountNegative, { rowcount: "-1", colcount: "-1" }, true);
+ const rowColIndexNegative = findAccessibleChildByID(
+ docAcc,
+ "rowColIndexNegative"
+ );
+ testAbsentAttrs(rowColIndexNegative, { rowindex: "", colindex: "" });
+ const rowColCountInvalid = findAccessibleChildByID(
+ docAcc,
+ "rowColCountInvalid"
+ );
+ testAbsentAttrs(rowColCountInvalid, { rowcount: "", colcount: "" });
+ const rowColIndexInvalid = findAccessibleChildByID(
+ docAcc,
+ "rowColIndexInvalid"
+ );
+ testAbsentAttrs(rowColIndexInvalid, { rowindex: "", colindex: "" });
+
+ // Test that unknown aria- attributes get exposed.
+ const foo = findAccessibleChildByID(docAcc, "foo");
+ testAttrs(foo, { foo: "bar" }, true);
+
+ const mutate = findAccessibleChildByID(docAcc, "mutate");
+ testAttrs(mutate, { current: "true" }, true);
+ info("mutate: Removing aria-current");
+ let changed = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, mutate);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").removeAttribute("aria-current");
+ });
+ await changed;
+ testAbsentAttrs(mutate, { current: "" });
+ info("mutate: Adding aria-current");
+ changed = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, mutate);
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("mutate")
+ .setAttribute("aria-current", "page");
+ });
+ await changed;
+ testAttrs(mutate, { current: "page" }, true);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test support for the xml-roles attribute.
+ */
+addAccessibleTask(
+ `
+<div id="knownRole" role="main">knownRole</div>
+<div id="emptyRole" role="">emptyRole</div>
+<div id="unknownRole" role="foo">unknownRole</div>
+<div id="multiRole" role="foo main">multiRole</div>
+<main id="landmarkMarkup">landmarkMarkup</main>
+<main id="landmarkMarkupWithRole" role="banner">landmarkMarkupWithRole</main>
+<main id="landmarkMarkupWithEmptyRole" role="">landmarkMarkupWithEmptyRole</main>
+<article id="markup">markup</article>
+<article id="markupWithRole" role="banner">markupWithRole</article>
+<article id="markupWithEmptyRole" role="">markupWithEmptyRole</article>
+ `,
+ async function (browser, docAcc) {
+ const knownRole = findAccessibleChildByID(docAcc, "knownRole");
+ testAttrs(knownRole, { "xml-roles": "main" }, true);
+ const emptyRole = findAccessibleChildByID(docAcc, "emptyRole");
+ testAbsentAttrs(emptyRole, { "xml-roles": "" });
+ const unknownRole = findAccessibleChildByID(docAcc, "unknownRole");
+ testAttrs(unknownRole, { "xml-roles": "foo" }, true);
+ const multiRole = findAccessibleChildByID(docAcc, "multiRole");
+ testAttrs(multiRole, { "xml-roles": "foo main" }, true);
+ const landmarkMarkup = findAccessibleChildByID(docAcc, "landmarkMarkup");
+ testAttrs(landmarkMarkup, { "xml-roles": "main" }, true);
+ const landmarkMarkupWithRole = findAccessibleChildByID(
+ docAcc,
+ "landmarkMarkupWithRole"
+ );
+ testAttrs(landmarkMarkupWithRole, { "xml-roles": "banner" }, true);
+ const landmarkMarkupWithEmptyRole = findAccessibleChildByID(
+ docAcc,
+ "landmarkMarkupWithEmptyRole"
+ );
+ testAttrs(landmarkMarkupWithEmptyRole, { "xml-roles": "main" }, true);
+ const markup = findAccessibleChildByID(docAcc, "markup");
+ testAttrs(markup, { "xml-roles": "article" }, true);
+ const markupWithRole = findAccessibleChildByID(docAcc, "markupWithRole");
+ testAttrs(markupWithRole, { "xml-roles": "banner" }, true);
+ const markupWithEmptyRole = findAccessibleChildByID(
+ docAcc,
+ "markupWithEmptyRole"
+ );
+ testAttrs(markupWithEmptyRole, { "xml-roles": "article" }, true);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test lie region attributes.
+ */
+addAccessibleTask(
+ `
+<div id="noLive"><p>noLive</p></div>
+<output id="liveMarkup"><p>liveMarkup</p></output>
+<div id="ariaLive" aria-live="polite"><p>ariaLive</p></div>
+<div id="liveRole" role="log"><p>liveRole</p></div>
+<div id="nonLiveRole" role="group"><p>nonLiveRole</p></div>
+<div id="other" aria-atomic="true" aria-busy="true" aria-relevant="additions"><p>other</p></div>
+ `,
+ async function (browser, docAcc) {
+ const noLive = findAccessibleChildByID(docAcc, "noLive");
+ for (const acc of [noLive, noLive.firstChild]) {
+ testAbsentAttrs(acc, {
+ live: "",
+ "container-live": "",
+ "container-live-role": "",
+ atomic: "",
+ "container-atomic": "",
+ busy: "",
+ "container-busy": "",
+ relevant: "",
+ "container-relevant": "",
+ });
+ }
+ const liveMarkup = findAccessibleChildByID(docAcc, "liveMarkup");
+ testAttrs(liveMarkup, { live: "polite" }, true);
+ testAttrs(liveMarkup.firstChild, { "container-live": "polite" }, true);
+ const ariaLive = findAccessibleChildByID(docAcc, "ariaLive");
+ testAttrs(ariaLive, { live: "polite" }, true);
+ testAttrs(ariaLive.firstChild, { "container-live": "polite" }, true);
+ const liveRole = findAccessibleChildByID(docAcc, "liveRole");
+ testAttrs(liveRole, { live: "polite" }, true);
+ testAttrs(
+ liveRole.firstChild,
+ { "container-live": "polite", "container-live-role": "log" },
+ true
+ );
+ const nonLiveRole = findAccessibleChildByID(docAcc, "nonLiveRole");
+ testAbsentAttrs(nonLiveRole, { live: "" });
+ testAbsentAttrs(nonLiveRole.firstChild, {
+ "container-live": "",
+ "container-live-role": "",
+ });
+ const other = findAccessibleChildByID(docAcc, "other");
+ testAttrs(
+ other,
+ { atomic: "true", busy: "true", relevant: "additions" },
+ true
+ );
+ testAttrs(
+ other.firstChild,
+ {
+ "container-atomic": "true",
+ "container-busy": "true",
+ "container-relevant": "additions",
+ },
+ true
+ );
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test the id attribute.
+ */
+addAccessibleTask(
+ `
+<p id="withId">withId</p>
+<div id="noIdParent"><p>noId</p></div>
+ `,
+ async function (browser, docAcc) {
+ const withId = findAccessibleChildByID(docAcc, "withId");
+ testAttrs(withId, { id: "withId" }, true);
+ const noId = findAccessibleChildByID(docAcc, "noIdParent").firstChild;
+ testAbsentAttrs(noId, { id: "" });
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test the valuetext attribute.
+ */
+addAccessibleTask(
+ `
+<div id="valuenow" role="slider" aria-valuenow="1"></div>
+<div id="valuetext" role="slider" aria-valuetext="text"></div>
+<div id="noValue" role="button"></div>
+ `,
+ async function (browser, docAcc) {
+ const valuenow = findAccessibleChildByID(docAcc, "valuenow");
+ testAttrs(valuenow, { valuetext: "1" }, true);
+ const valuetext = findAccessibleChildByID(docAcc, "valuetext");
+ testAttrs(valuetext, { valuetext: "text" }, true);
+ const noValue = findAccessibleChildByID(docAcc, "noValue");
+ testAbsentAttrs(noValue, { valuetext: "valuetext" });
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+function untilCacheAttrIs(acc, attr, val, msg) {
+ return untilCacheOk(() => {
+ try {
+ return acc.attributes.getStringProperty(attr) == val;
+ } catch (e) {
+ return false;
+ }
+ }, msg);
+}
+
+function untilCacheAttrAbsent(acc, attr, msg) {
+ return untilCacheOk(() => {
+ try {
+ acc.attributes.getStringProperty(attr);
+ } catch (e) {
+ return true;
+ }
+ return false;
+ }, msg);
+}
+
+/**
+ * Test the class attribute.
+ */
+addAccessibleTask(
+ `
+<div id="oneClass" class="c1">oneClass</div>
+<div id="multiClass" class="c1 c2">multiClass</div>
+<div id="noClass">noClass</div>
+<div id="mutate">mutate</div>
+ `,
+ async function (browser, docAcc) {
+ const oneClass = findAccessibleChildByID(docAcc, "oneClass");
+ testAttrs(oneClass, { class: "c1" }, true);
+ const multiClass = findAccessibleChildByID(docAcc, "multiClass");
+ testAttrs(multiClass, { class: "c1 c2" }, true);
+ const noClass = findAccessibleChildByID(docAcc, "noClass");
+ testAbsentAttrs(noClass, { class: "" });
+
+ const mutate = findAccessibleChildByID(docAcc, "mutate");
+ testAbsentAttrs(mutate, { class: "" });
+ info("Adding class to mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").className = "c1 c2";
+ });
+ await untilCacheAttrIs(mutate, "class", "c1 c2", "mutate class correct");
+ info("Removing class from mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").removeAttribute("class");
+ });
+ await untilCacheAttrAbsent(mutate, "class", "mutate class not present");
+ },
+ { chrome: true, topLevel: true }
+);
+
+/**
+ * Test the src attribute.
+ */
+const kImgUrl = "https://example.com/a11y/accessible/tests/mochitest/moz.png";
+addAccessibleTask(
+ `
+<img id="noAlt" src="${kImgUrl}">
+<img id="alt" alt="alt" src="${kImgUrl}">
+<img id="mutate">
+ `,
+ async function (browser, docAcc) {
+ const noAlt = findAccessibleChildByID(docAcc, "noAlt");
+ testAttrs(noAlt, { src: kImgUrl }, true);
+ if (browser.isRemoteBrowser) {
+ // To avoid wasting memory, we don't cache src if there's a name.
+ const alt = findAccessibleChildByID(docAcc, "alt");
+ testAbsentAttrs(alt, { src: "" });
+ }
+
+ const mutate = findAccessibleChildByID(docAcc, "mutate");
+ testAbsentAttrs(mutate, { src: "" });
+ info("Adding src to mutate");
+ await invokeContentTask(browser, [kImgUrl], url => {
+ content.document.getElementById("mutate").src = url;
+ });
+ await untilCacheAttrIs(mutate, "src", kImgUrl, "mutate src correct");
+ info("Removing src from mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").removeAttribute("src");
+ });
+ await untilCacheAttrAbsent(mutate, "src", "mutate src not present");
+ },
+ { chrome: true, topLevel: true }
+);
+
+/**
+ * Test the placeholder attribute.
+ */
+addAccessibleTask(
+ `
+<input id="htmlWithLabel" aria-label="label" placeholder="HTML">
+<input id="htmlNoLabel" placeholder="HTML">
+<input id="ariaWithLabel" aria-label="label" aria-placeholder="ARIA">
+<input id="ariaNoLabel" aria-placeholder="ARIA">
+<input id="both" aria-label="label" placeholder="HTML" aria-placeholder="ARIA">
+<input id="mutate" placeholder="HTML">
+ `,
+ async function (browser, docAcc) {
+ const htmlWithLabel = findAccessibleChildByID(docAcc, "htmlWithLabel");
+ testAttrs(htmlWithLabel, { placeholder: "HTML" }, true);
+ const htmlNoLabel = findAccessibleChildByID(docAcc, "htmlNoLabel");
+ // placeholder is used as name, so not exposed as attribute.
+ testAbsentAttrs(htmlNoLabel, { placeholder: "" });
+ const ariaWithLabel = findAccessibleChildByID(docAcc, "ariaWithLabel");
+ testAttrs(ariaWithLabel, { placeholder: "ARIA" }, true);
+ const ariaNoLabel = findAccessibleChildByID(docAcc, "ariaNoLabel");
+ // No label doesn't impact aria-placeholder.
+ testAttrs(ariaNoLabel, { placeholder: "ARIA" }, true);
+ const both = findAccessibleChildByID(docAcc, "both");
+ testAttrs(both, { placeholder: "HTML" }, true);
+
+ const mutate = findAccessibleChildByID(docAcc, "mutate");
+ testAbsentAttrs(mutate, { placeholder: "" });
+ info("Adding label to mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("mutate")
+ .setAttribute("aria-label", "label");
+ });
+ await untilCacheAttrIs(
+ mutate,
+ "placeholder",
+ "HTML",
+ "mutate placeholder correct"
+ );
+ info("Removing mutate placeholder");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").removeAttribute("placeholder");
+ });
+ await untilCacheAttrAbsent(
+ mutate,
+ "placeholder",
+ "mutate placeholder not present"
+ );
+ info("Setting mutate aria-placeholder");
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("mutate")
+ .setAttribute("aria-placeholder", "ARIA");
+ });
+ await untilCacheAttrIs(
+ mutate,
+ "placeholder",
+ "ARIA",
+ "mutate placeholder correct"
+ );
+ info("Setting mutate placeholder");
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("mutate")
+ .setAttribute("placeholder", "HTML");
+ });
+ await untilCacheAttrIs(
+ mutate,
+ "placeholder",
+ "HTML",
+ "mutate placeholder correct"
+ );
+ },
+ { chrome: true, topLevel: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_description.js b/accessible/tests/browser/e10s/browser_caching_description.js
new file mode 100644
index 0000000000..d489620e16
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_description.js
@@ -0,0 +1,280 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/name.js */
+loadScripts({ name: "name.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * expected {String} expected description value for a given accessible
+ * attrs {?Array} an optional list of attributes to update
+ * waitFor {?Array} an optional list of accessible events to wait for when
+ * attributes are updated
+ * }
+ */
+const tests = [
+ {
+ desc: "No description when there are no @alt, @title and @aria-describedby",
+ expected: "",
+ },
+ {
+ desc: "Description from @aria-describedby attribute",
+ attrs: [
+ {
+ attr: "aria-describedby",
+ value: "description",
+ },
+ ],
+ waitFor: [[EVENT_DESCRIPTION_CHANGE, "image"]],
+ expected: "aria description",
+ },
+ {
+ desc:
+ "No description from @aria-describedby since it is the same as the " +
+ "@alt attribute which is used as the name",
+ attrs: [
+ {
+ attr: "alt",
+ value: "aria description",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "",
+ },
+ {
+ desc:
+ "Description from @aria-describedby attribute when @alt and " +
+ "@aria-describedby are not the same",
+ attrs: [
+ {
+ attr: "aria-describedby",
+ value: "description2",
+ },
+ ],
+ waitFor: [[EVENT_DESCRIPTION_CHANGE, "image"]],
+ expected: "another description",
+ },
+ {
+ desc: "No description change when @alt is dropped but @aria-describedby remains",
+ attrs: [
+ {
+ attr: "alt",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "another description",
+ },
+ {
+ desc:
+ "Description from @aria-describedby attribute when @title (used for " +
+ "name) and @aria-describedby are not the same",
+ attrs: [
+ {
+ attr: "title",
+ value: "title",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "another description",
+ },
+ {
+ desc:
+ "No description from @aria-describedby since it is the same as the " +
+ "@title attribute which is used as the name",
+ attrs: [
+ {
+ attr: "title",
+ value: "another description",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "",
+ },
+ {
+ desc: "No description with only @title attribute which is used as the name",
+ attrs: [
+ {
+ attr: "aria-describedby",
+ },
+ ],
+ waitFor: [[EVENT_DESCRIPTION_CHANGE, "image"]],
+ expected: "",
+ },
+ {
+ desc:
+ "Description from @title attribute when @alt and @atitle are not the " +
+ "same",
+ attrs: [
+ {
+ attr: "alt",
+ value: "aria description",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "another description",
+ },
+ {
+ desc:
+ "No description from @title since it is the same as the @alt " +
+ "attribute which is used as the name",
+ attrs: [
+ {
+ attr: "alt",
+ value: "another description",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "",
+ },
+ {
+ desc:
+ "No description from @aria-describedby since it is the same as the " +
+ "@alt (used for name) and @title attributes",
+ attrs: [
+ {
+ attr: "aria-describedby",
+ value: "description2",
+ },
+ ],
+ waitFor: [[EVENT_DESCRIPTION_CHANGE, "image"]],
+ expected: "",
+ },
+ {
+ desc:
+ "Description from @aria-describedby attribute when it is different " +
+ "from @alt (used for name) and @title attributes",
+ attrs: [
+ {
+ attr: "aria-describedby",
+ value: "description",
+ },
+ ],
+ waitFor: [[EVENT_DESCRIPTION_CHANGE, "image"]],
+ expected: "aria description",
+ },
+ {
+ desc:
+ "No description from @aria-describedby since it is the same as the " +
+ "@alt attribute (used for name) but different from title",
+ attrs: [
+ {
+ attr: "alt",
+ value: "aria description",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "",
+ },
+ {
+ desc:
+ "Description from @aria-describedby attribute when @alt (used for " +
+ "name) and @aria-describedby are not the same but @title and " +
+ "aria-describedby are",
+ attrs: [
+ {
+ attr: "aria-describedby",
+ value: "description2",
+ },
+ ],
+ waitFor: [[EVENT_DESCRIPTION_CHANGE, "image"]],
+ expected: "another description",
+ },
+];
+
+/**
+ * Test caching of accessible object description
+ */
+addAccessibleTask(
+ `
+ <p id="description">aria description</p>
+ <p id="description2">another description</p>
+ <img id="image" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" />`,
+ async function (browser, accDoc) {
+ let imgAcc = findAccessibleChildByID(accDoc, "image");
+
+ for (let { desc, waitFor, attrs, expected } of tests) {
+ info(desc);
+ let onUpdate;
+ if (waitFor) {
+ onUpdate = waitForOrderedEvents(waitFor);
+ }
+ if (attrs) {
+ for (let { attr, value } of attrs) {
+ await invokeSetAttribute(browser, "image", attr, value);
+ }
+ }
+ await onUpdate;
+ // When attribute change (alt) triggers reorder event, accessible will
+ // become defunct.
+ if (isDefunct(imgAcc)) {
+ imgAcc = findAccessibleChildByID(accDoc, "image");
+ }
+ testDescr(imgAcc, expected);
+ }
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test that the description is updated when the content of a hidden aria-describedby
+ * subtree changes.
+ */
+addAccessibleTask(
+ `
+<button id="button" aria-describedby="desc">
+<div id="desc" hidden>a</div>
+ `,
+ async function (browser, docAcc) {
+ const button = findAccessibleChildByID(docAcc, "button");
+ testDescr(button, "a");
+ info("Changing desc textContent");
+ let descChanged = waitForEvent(EVENT_DESCRIPTION_CHANGE, button);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("desc").textContent = "c";
+ });
+ await descChanged;
+ testDescr(button, "c");
+ info("Prepending text node to desc");
+ descChanged = waitForEvent(EVENT_DESCRIPTION_CHANGE, button);
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("desc")
+ .prepend(content.document.createTextNode("b"));
+ });
+ await descChanged;
+ testDescr(button, "bc");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test aria-description, including mutations.
+ */
+addAccessibleTask(
+ `<button id="button" aria-description="a">button</button>`,
+ async function (browser, docAcc) {
+ const button = findAccessibleChildByID(docAcc, "button");
+ testDescr(button, "a");
+ info("Changing aria-description");
+ let changed = waitForEvent(EVENT_DESCRIPTION_CHANGE, button);
+ await invokeSetAttribute(browser, "button", "aria-description", "b");
+ await changed;
+ testDescr(button, "b");
+ info("Removing aria-description");
+ changed = waitForEvent(EVENT_DESCRIPTION_CHANGE, button);
+ await invokeSetAttribute(browser, "button", "aria-description");
+ await changed;
+ testDescr(button, "");
+ info("Setting aria-description");
+ changed = waitForEvent(EVENT_DESCRIPTION_CHANGE, button);
+ await invokeSetAttribute(browser, "button", "aria-description", "c");
+ await changed;
+ testDescr(button, "c");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_document_props.js b/accessible/tests/browser/e10s/browser_caching_document_props.js
new file mode 100644
index 0000000000..787e979045
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_document_props.js
@@ -0,0 +1,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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ "e10s/doc_treeupdate_whitespace.html",
+ async function (browser, docAcc) {
+ info("Testing top level doc");
+ queryInterfaces(docAcc, [nsIAccessibleDocument]);
+ const topUrl =
+ (browser.isRemoteBrowser ? CURRENT_CONTENT_DIR : CURRENT_DIR) +
+ "e10s/doc_treeupdate_whitespace.html";
+ is(docAcc.URL, topUrl, "Initial URL correct");
+ is(docAcc.mimeType, "text/html", "Mime type is correct");
+ info("Changing URL");
+ await invokeContentTask(browser, [], () => {
+ content.history.pushState(
+ null,
+ "",
+ content.document.location.href + "/after"
+ );
+ });
+ is(docAcc.URL, topUrl + "/after", "URL correct after change");
+
+ // We can't use the harness to manage iframes for us because it uses data
+ // URIs for in-process iframes, but data URIs don't support
+ // history.pushState.
+
+ async function testIframe() {
+ queryInterfaces(iframeDocAcc, [nsIAccessibleDocument]);
+ is(iframeDocAcc.URL, src, "Initial URL correct");
+ is(iframeDocAcc.mimeType, "text/html", "Mime type is correct");
+ info("Changing URL");
+ await invokeContentTask(browser, [], async () => {
+ await SpecialPowers.spawn(content.iframe, [], () => {
+ content.history.pushState(
+ null,
+ "",
+ content.document.location.href + "/after"
+ );
+ });
+ });
+ is(iframeDocAcc.URL, src + "/after", "URL correct after change");
+ }
+
+ info("Testing same origin (in-process) iframe");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ let src = "http://example.com/initial.html";
+ let loaded = waitForEvent(
+ EVENT_DOCUMENT_LOAD_COMPLETE,
+ evt => evt.accessible.parent.parent == docAcc
+ );
+ await invokeContentTask(browser, [src], cSrc => {
+ content.iframe = content.document.createElement("iframe");
+ content.iframe.src = cSrc;
+ content.document.body.append(content.iframe);
+ });
+ let iframeDocAcc = (await loaded).accessible;
+ await testIframe();
+
+ info("Testing different origin (out-of-process) iframe");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ src = "http://example.net/initial.html";
+ loaded = waitForEvent(
+ EVENT_DOCUMENT_LOAD_COMPLETE,
+ evt => evt.accessible.parent.parent == docAcc
+ );
+ await invokeContentTask(browser, [src], cSrc => {
+ content.iframe.src = cSrc;
+ });
+ iframeDocAcc = (await await loaded).accessible;
+ await testIframe();
+ },
+ { chrome: true, topLevel: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_domnodeid.js b/accessible/tests/browser/e10s/browser_caching_domnodeid.js
new file mode 100644
index 0000000000..722cc9a970
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_domnodeid.js
@@ -0,0 +1,32 @@
+/* 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";
+
+/**
+ * Test DOM ID caching on remotes.
+ */
+addAccessibleTask(
+ '<div id="div"></div>',
+ async function (browser, accDoc) {
+ const div = findAccessibleChildByID(accDoc, "div");
+ ok(div, "Got accessible with 'div' ID.");
+
+ let contentPromise = invokeContentTask(browser, [], () => {
+ content.document.getElementById("div").id = "foo";
+ });
+ // We don't await for content task to return because we want to exercise the
+ // untilCacheIs function and demonstrate that it can await for a passing
+ // `is` test.
+ await untilCacheIs(
+ () => div.id,
+ "foo",
+ "ID is correct and updated in cache"
+ );
+
+ // Don't leave test without the content task promise resolved.
+ await contentPromise;
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_hyperlink.js b/accessible/tests/browser/e10s/browser_caching_hyperlink.js
new file mode 100644
index 0000000000..4b3f8a1bda
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_hyperlink.js
@@ -0,0 +1,228 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function testLinkIndexAtOffset(id, offset, index) {
+ let htAcc = getAccessible(id, [nsIAccessibleHyperText]);
+ is(
+ htAcc.getLinkIndexAtOffset(offset),
+ index,
+ "Wrong link index at offset " + offset + " for ID " + id + "!"
+ );
+}
+
+function testThis(
+ paragraph,
+ docURI,
+ id,
+ charIndex,
+ expectedLinkIndex,
+ expectedAnchors,
+ expectedURIs,
+ valid = true
+) {
+ testLinkIndexAtOffset(paragraph, charIndex, expectedLinkIndex);
+
+ let linkAcc = paragraph.getLinkAt(expectedLinkIndex);
+ ok(linkAcc, "No accessible for link " + id + "!");
+
+ is(linkAcc.valid, valid, `${id} is valid.`);
+
+ let linkIndex = paragraph.getLinkIndex(linkAcc);
+ is(linkIndex, expectedLinkIndex, "Wrong link index for " + id + "!");
+
+ is(linkAcc.anchorCount, expectedAnchors.length, "Correct number of anchors");
+ for (let i = 0; i < expectedAnchors.length; i++) {
+ let uri = linkAcc.getURI(i);
+ is(
+ (uri ? uri.spec : "").replace(docURI, ""),
+ expectedURIs[i],
+ `Wrong anchor URI at ${i} for "${id}"`
+ );
+ is(
+ getAccessibleDOMNodeID(linkAcc.getAnchor(i)),
+ expectedAnchors[i],
+ `Wrong anchor at ${i} for "${id}"`
+ );
+ }
+}
+
+/**
+ * Test hyperlinks
+ */
+addAccessibleTask(
+ `
+ <p id="testParagraph"><br
+ >Simple link:<br
+ ><a id="NormalHyperlink" href="https://www.mozilla.org">Mozilla Foundation</a><br
+ >ARIA link:<br
+ ><span id="AriaHyperlink" role="link"
+ onclick="window.open('https://www.mozilla.org/');"
+ tabindex="0">Mozilla Foundation Home</span><br
+ >Invalid, non-focusable hyperlink:<br
+ ><span id="InvalidAriaHyperlink" role="link" aria-invalid="true"
+ onclick="window.open('https:/www.mozilla.org/');">Invalid link</span><br
+ >Image map:<br
+ ><map name="atoz_map"><area href="https://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14"
+ id="b"
+ shape="rect"></area
+ ><area href="https://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,13,14"
+ id="a"
+ shape="rect"></area></map
+ ><img width="447" id="imgmap"
+ height="15"
+ usemap="#atoz_map"
+ src="../letters.gif"></img><br
+ >Empty link:<br
+ ><a id="emptyLink" href=""><img src=""></img></a><br
+ >Link with embedded span<br
+ ><a id="LinkWithSpan" href="https://www.heise.de/"><span lang="de">Heise Online</span></a><br
+ >Named anchor, must not have "linked" state for it to be exposed correctly:<br
+ ><a id="namedAnchor" name="named_anchor">This should never be of state_linked</a>
+ </p>
+ `,
+ function (browser, accDoc) {
+ const paragraph = findAccessibleChildByID(accDoc, "testParagraph", [
+ nsIAccessibleHyperText,
+ ]);
+ is(paragraph.linkCount, 7, "Wrong link count for paragraph!");
+
+ const docURI = accDoc.URL;
+ // normal hyperlink
+ testThis(
+ paragraph,
+ docURI,
+ "NormalHyperlink",
+ 14,
+ 0,
+ ["NormalHyperlink"],
+ ["https://www.mozilla.org/"]
+ );
+
+ // ARIA hyperlink
+ testThis(
+ paragraph,
+ docURI,
+ "AriaHyperlink",
+ 27,
+ 1,
+ ["AriaHyperlink"],
+ [""]
+ );
+
+ // ARIA hyperlink with status invalid
+ testThis(
+ paragraph,
+ docURI,
+ "InvalidAriaHyperlink",
+ 63,
+ 2,
+ ["InvalidAriaHyperlink"],
+ [""],
+ false
+ );
+
+ // image map, but not its link children. They are not part of hypertext.
+ testThis(
+ paragraph,
+ docURI,
+ "imgmap",
+ 76,
+ 3,
+ ["b", "a"],
+ [
+ "https://www.bbc.co.uk/radio4/atoz/index.shtml#b",
+ "https://www.bbc.co.uk/radio4/atoz/index.shtml#a",
+ ]
+ );
+
+ // empty hyperlink
+ testThis(paragraph, docURI, "emptyLink", 90, 4, ["emptyLink"], [""]);
+
+ // normal hyperlink with embedded span
+ testThis(
+ paragraph,
+ docURI,
+ "LinkWithSpan",
+ 116,
+ 5,
+ ["LinkWithSpan"],
+ ["https://www.heise.de/"]
+ );
+
+ // Named anchor
+ testThis(paragraph, docURI, "namedAnchor", 193, 6, ["namedAnchor"], [""]);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test paragraph with link
+ */
+addAccessibleTask(
+ `
+ <p id="p"><a href="http://mozilla.org">mozilla.org</a></p>
+ `,
+ function (browser, accDoc) {
+ // Paragraph with link
+ const p = findAccessibleChildByID(accDoc, "p", [nsIAccessibleHyperText]);
+ const link = p.getLinkAt(0);
+ is(link, p.getChildAt(0), "Wrong link for p2");
+ is(p.linkCount, 1, "Wrong link count for p2");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test paragraph with link
+ */
+addAccessibleTask(
+ `
+ <p id="p"><a href="www">mozilla</a><a href="www">mozilla</a><span> te</span><span>xt </span><a href="www">mozilla</a></p>
+ `,
+ function (browser, accDoc) {
+ // Paragraph with link
+ const p = findAccessibleChildByID(accDoc, "p", [nsIAccessibleHyperText]);
+
+ // getLinkIndexAtOffset, causes the offsets to be cached;
+ testLinkIndexAtOffset(p, 0, 0); // 1st 'mozilla' link
+ testLinkIndexAtOffset(p, 1, 1); // 2nd 'mozilla' link
+ testLinkIndexAtOffset(p, 2, -1); // ' ' of ' te' text node
+ testLinkIndexAtOffset(p, 3, -1); // 't' of ' te' text node
+ testLinkIndexAtOffset(p, 5, -1); // 'x' of 'xt ' text node
+ testLinkIndexAtOffset(p, 7, -1); // ' ' of 'xt ' text node
+ testLinkIndexAtOffset(p, 8, 2); // 3d 'mozilla' link
+ testLinkIndexAtOffset(p, 9, 2); // the end, latest link
+
+ // the second pass to make sure link indexes are calculated propertly from
+ // cached offsets.
+ testLinkIndexAtOffset(p, 0, 0); // 1st 'mozilla' link
+ testLinkIndexAtOffset(p, 1, 1); // 2nd 'mozilla' link
+ testLinkIndexAtOffset(p, 2, -1); // ' ' of ' te' text node
+ testLinkIndexAtOffset(p, 3, -1); // 't' of ' te' text node
+ testLinkIndexAtOffset(p, 5, -1); // 'x' of 'xt ' text node
+ testLinkIndexAtOffset(p, 7, -1); // ' ' of 'xt ' text node
+ testLinkIndexAtOffset(p, 8, 2); // 3d 'mozilla' link
+ testLinkIndexAtOffset(p, 9, 2); // the end, latest link
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_innerHTML.js b/accessible/tests/browser/e10s/browser_caching_innerHTML.js
new file mode 100644
index 0000000000..7baee32e26
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_innerHTML.js
@@ -0,0 +1,48 @@
+/* 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";
+
+/**
+ * Test caching of innerHTML on math elements for Windows clients.
+ */
+addAccessibleTask(
+ `
+<p id="p">test</p>
+<math id="math"><mfrac><mi>x</mi><mi>y</mi></mfrac></math>
+ `,
+ async function (browser, docAcc) {
+ const p = findAccessibleChildByID(docAcc, "p");
+ let hasHtml;
+ try {
+ p.cache.getStringProperty("html");
+ hasHtml = true;
+ } catch (e) {
+ hasHtml = false;
+ }
+ ok(!hasHtml, "p doesn't have cached html");
+
+ const math = findAccessibleChildByID(docAcc, "math");
+ is(
+ math.cache.getStringProperty("html"),
+ "<mfrac><mi>x</mi><mi>y</mi></mfrac>",
+ "math cached html is correct"
+ );
+
+ info("Mutating math");
+ await invokeContentTask(browser, [], () => {
+ content.document.querySelectorAll("mi")[1].textContent = "z";
+ });
+ await untilCacheIs(
+ () => math.cache.getStringProperty("html"),
+ "<mfrac><mi>x</mi><mi>z</mi></mfrac>",
+ "math cached html is correct after mutation"
+ );
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_interfaces.js b/accessible/tests/browser/e10s/browser_caching_interfaces.js
new file mode 100644
index 0000000000..c83d486bc6
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_interfaces.js
@@ -0,0 +1,59 @@
+/* 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";
+
+/**
+ * Test caching of accessible interfaces
+ */
+addAccessibleTask(
+ `
+ <img id="img" src="http://example.com/a11y/accessible/tests/mochitest/moz.png">
+ <select id="select" multiple></select>
+ <input id="number-input" type="number">
+ <table id="table">
+ <tr><td id="cell"><a id="link" href="#">hello</a></td></tr>
+ </table>
+ `,
+ async function (browser, accDoc) {
+ ok(
+ accDoc instanceof nsIAccessibleDocument,
+ "Document has Document interface"
+ );
+ ok(
+ accDoc instanceof nsIAccessibleHyperText,
+ "Document has HyperText interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "img") instanceof nsIAccessibleImage,
+ "img has Image interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "select") instanceof
+ nsIAccessibleSelectable,
+ "select has Selectable interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "number-input") instanceof
+ nsIAccessibleValue,
+ "number-input has Value interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "table") instanceof nsIAccessibleTable,
+ "table has Table interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "cell") instanceof nsIAccessibleTableCell,
+ "cell has TableCell interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "link") instanceof nsIAccessibleHyperLink,
+ "link has HyperLink interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "link") instanceof nsIAccessibleHyperText,
+ "link has HyperText interface"
+ );
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_large_update.js b/accessible/tests/browser/e10s/browser_caching_large_update.js
new file mode 100644
index 0000000000..ccf8a86921
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_large_update.js
@@ -0,0 +1,66 @@
+/* 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";
+
+/**
+ * Test a large update which adds many thousands of Accessibles with a
+ * lot of content in each.
+ */
+addAccessibleTask(
+ `<main id="main" hidden></main>`,
+ async function (browser, docAcc) {
+ let shown = waitForEvent(EVENT_SHOW, "main");
+ await invokeContentTask(browser, [], () => {
+ // Make a long string.
+ let text = "";
+ for (let i = 0; i < 100; ++i) {
+ text += "a";
+ }
+ // Create lots of nodes which include the long string.
+ const contMain = content.document.getElementById("main");
+ // 15000 children of main.
+ for (let w = 0; w < 15000; ++w) {
+ // Each of those goes 9 deep.
+ let parent = contMain;
+ for (let d = 0; d < 10; ++d) {
+ const div = content.document.createElement("div");
+ div.setAttribute("aria-label", `${w} ${d} ${text}`);
+ parent.append(div);
+ parent = div;
+ }
+ }
+ contMain.hidden = false;
+ });
+ const main = (await shown).accessible;
+ is(main.childCount, 15000, "main has correct number of children");
+
+ // We don't want to output passes for every check, since that would output
+ // hundreds of thousands of lines, which slows the test to a crawl. Instead,
+ // output any failures and keep track of overall success/failure.
+ let treeOk = true;
+ function check(val, msg) {
+ if (!val) {
+ ok(false, msg);
+ treeOk = false;
+ }
+ }
+
+ info("Checking tree");
+ for (let w = 0; w < 15000; ++w) {
+ let acc = main.getChildAt(w);
+ let parent = main;
+ for (let d = 0; d < 10; ++d) {
+ check(acc, `Got child ${w} depth ${d}`);
+ const name = `${w} ${d}`;
+ check(acc.name.startsWith(name + " "), `${name}: correct name`);
+ check(acc.parent == parent, `${name}: correct parent`);
+ parent = acc;
+ acc = acc.firstChild;
+ }
+ }
+ // check() sets treeOk to false for any failure.
+ ok(treeOk, "Tree is correct");
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_name.js b/accessible/tests/browser/e10s/browser_caching_name.js
new file mode 100644
index 0000000000..0c1f419b97
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_name.js
@@ -0,0 +1,539 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/name.js */
+loadScripts({ name: "name.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Rules for name tests that are inspired by
+ * accessible/tests/mochitest/name/markuprules.xul
+ *
+ * Each element in the list of rules represents a name calculation rule for a
+ * particular test case.
+ *
+ * The rules have the following format:
+ * { attr } - calculated from attribute
+ * { elm } - calculated from another element
+ * { fromsubtree } - calculated from element's subtree
+ *
+ */
+const ARIARule = [{ attr: "aria-labelledby" }, { attr: "aria-label" }];
+const HTMLControlHeadRule = [...ARIARule, { elm: "label" }];
+const rules = {
+ CSSContent: [{ elm: "style" }, { fromsubtree: true }],
+ HTMLARIAGridCell: [...ARIARule, { fromsubtree: true }, { attr: "title" }],
+ HTMLControl: [
+ ...HTMLControlHeadRule,
+ { fromsubtree: true },
+ { attr: "title" },
+ ],
+ HTMLElm: [...ARIARule, { attr: "title" }],
+ HTMLImg: [...ARIARule, { attr: "alt" }, { attr: "title" }],
+ HTMLImgEmptyAlt: [...ARIARule, { attr: "title" }, { attr: "alt" }],
+ HTMLInputButton: [
+ ...HTMLControlHeadRule,
+ { attr: "value" },
+ { attr: "title" },
+ ],
+ HTMLInputImage: [
+ ...HTMLControlHeadRule,
+ { attr: "alt" },
+ { attr: "value" },
+ { attr: "title" },
+ ],
+ HTMLInputImageNoValidSrc: [
+ ...HTMLControlHeadRule,
+ { attr: "alt" },
+ { attr: "value" },
+ ],
+ HTMLInputReset: [...HTMLControlHeadRule, { attr: "value" }],
+ HTMLInputSubmit: [...HTMLControlHeadRule, { attr: "value" }],
+ HTMLLink: [...ARIARule, { fromsubtree: true }, { attr: "title" }],
+ HTMLLinkImage: [...ARIARule, { fromsubtree: true }, { attr: "title" }],
+ HTMLOption: [
+ ...ARIARule,
+ { attr: "label" },
+ { fromsubtree: true },
+ { attr: "title" },
+ ],
+ HTMLTable: [
+ ...ARIARule,
+ { elm: "caption" },
+ { attr: "summary" },
+ { attr: "title" },
+ ],
+};
+
+const markupTests = [
+ {
+ id: "btn",
+ ruleset: "HTMLControl",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn">test4</label>
+ <button id="btn"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test5">press me</button>`,
+ expected: ["test2 test3", "test1", "test4", "press me", "test5"],
+ },
+ {
+ id: "btn",
+ ruleset: "HTMLInputButton",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn">test4</label>
+ <input id="btn"
+ type="button"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from al"
+ src="no name from src"
+ data="no name from data"
+ title="name from title"/>`,
+ expected: [
+ "test2 test3",
+ "test1",
+ "test4",
+ "name from value",
+ "name from title",
+ ],
+ },
+ {
+ id: "btn-submit",
+ ruleset: "HTMLInputSubmit",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn-submit">test4</label>
+ <input id="btn-submit"
+ type="submit"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from atl"
+ src="no name from src"
+ data="no name from data"
+ title="no name from title"/>`,
+ expected: ["test2 test3", "test1", "test4", "name from value"],
+ },
+ {
+ id: "btn-reset",
+ ruleset: "HTMLInputReset",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn-reset">test4</label>
+ <input id="btn-reset"
+ type="reset"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from alt"
+ src="no name from src"
+ data="no name from data"
+ title="no name from title"/>`,
+ expected: ["test2 test3", "test1", "test4", "name from value"],
+ },
+ {
+ id: "btn-image",
+ ruleset: "HTMLInputImage",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn-image">test4</label>
+ <input id="btn-image"
+ type="image"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ alt="name from alt"
+ value="name from value"
+ src="http://example.com/a11y/accessible/tests/mochitest/moz.png"
+ data="no name from data"
+ title="name from title"/>`,
+ expected: [
+ "test2 test3",
+ "test1",
+ "test4",
+ "name from alt",
+ "name from value",
+ "name from title",
+ ],
+ },
+ {
+ id: "btn-image",
+ ruleset: "HTMLInputImageNoValidSrc",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn-image">test4</label>
+ <input id="btn-image"
+ type="image"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ alt="name from alt"
+ value="name from value"
+ data="no name from data"
+ title="no name from title"/>`,
+ expected: [
+ "test2 test3",
+ "test1",
+ "test4",
+ "name from alt",
+ "name from value",
+ ],
+ },
+ {
+ id: "opt",
+ ruleset: "HTMLOption",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <select>
+ <option id="opt"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ label="test4"
+ title="test5">option1</option>
+ <option>option2</option>
+ </select>`,
+ expected: ["test2 test3", "test1", "test4", "option1", "test5"],
+ },
+ {
+ id: "img",
+ ruleset: "HTMLImg",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <img id="img"
+ aria-label="Logo of Mozilla"
+ aria-labelledby="l1 l2"
+ alt="Mozilla logo"
+ title="This is a logo"
+ src="http://example.com/a11y/accessible/tests/mochitest/moz.png"/>`,
+ expected: [
+ "test2 test3",
+ "Logo of Mozilla",
+ "Mozilla logo",
+ "This is a logo",
+ ],
+ },
+ {
+ id: "tc",
+ ruleset: "HTMLElm",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="tc">test4</label>
+ <table>
+ <tr>
+ <td id="tc"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test5">
+ <p>This is a paragraph</p>
+ <a href="#">This is a link</a>
+ <ul>
+ <li>This is a list</li>
+ </ul>
+ </td>
+ </tr>
+ </table>`,
+ expected: ["test2 test3", "test1", "test5"],
+ },
+ {
+ id: "gc",
+ ruleset: "HTMLARIAGridCell",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="gc">test4</label>
+ <table>
+ <tr>
+ <td id="gc"
+ role="gridcell"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="This is a paragraph This is a link This is a list">
+ <p>This is a paragraph</p>
+ <a href="#">This is a link</a>
+ <ul>
+ <li>Listitem1</li>
+ <li>Listitem2</li>
+ </ul>
+ </td>
+ </tr>
+ </table>`,
+ expected: [
+ "test2 test3",
+ "test1",
+ "This is a paragraph This is a link \u2022 Listitem1 \u2022 Listitem2",
+ "This is a paragraph This is a link This is a list",
+ ],
+ },
+ {
+ id: "t",
+ ruleset: "HTMLTable",
+ markup: `
+ <span id="l1">lby_tst6_1</span>
+ <span id="l2">lby_tst6_2</span>
+ <label for="t">label_tst6</label>
+ <table id="t"
+ aria-label="arialabel_tst6"
+ aria-labelledby="l1 l2"
+ summary="summary_tst6"
+ title="title_tst6">
+ <caption>caption_tst6</caption>
+ <tr>
+ <td>cell1</td>
+ <td>cell2</td>
+ </tr>
+ </table>`,
+ expected: [
+ "lby_tst6_1 lby_tst6_2",
+ "arialabel_tst6",
+ "caption_tst6",
+ "summary_tst6",
+ "title_tst6",
+ ],
+ },
+ {
+ id: "btn",
+ ruleset: "CSSContent",
+ markup: `
+ <div role="main">
+ <style>
+ button::before {
+ content: "do not ";
+ }
+ </style>
+ <button id="btn">press me</button>
+ </div>`,
+ expected: ["do not press me", "press me"],
+ },
+ {
+ // TODO: uncomment when Bug-1256382 is resoved.
+ // id: 'li',
+ // ruleset: 'CSSContent',
+ // markup: `
+ // <style>
+ // ul {
+ // list-style-type: decimal;
+ // }
+ // </style>
+ // <ul id="ul">
+ // <li id="li">Listitem</li>
+ // </ul>`,
+ // expected: ['1. Listitem', `${String.fromCharCode(0x2022)} Listitem`]
+ // }, {
+ id: "a",
+ ruleset: "HTMLLink",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <a id="a"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test4">test5</a>`,
+ expected: ["test2 test3", "test1", "test5", "test4"],
+ },
+ {
+ id: "a-img",
+ ruleset: "HTMLLinkImage",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <a id="a-img"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test4"><img alt="test5"/></a>`,
+ expected: ["test2 test3", "test1", "test5", "test4"],
+ },
+];
+
+/**
+ * Test accessible name that is calculated from an attribute, remove the
+ * attribute before proceeding to the next name test. If attribute removal
+ * results in a reorder or text inserted event - wait for it. If accessible
+ * becomes defunct, update its reference using the one that is attached to one
+ * of the above events.
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Object} target { acc, id } structure that contains an
+ * accessible and its content element
+ * id.
+ * @param {Object} rule current attr rule for name calculation
+ * @param {[type]} expected expected name value
+ */
+async function testAttrRule(browser, target, rule, expected) {
+ let { id, acc } = target;
+ let { attr } = rule;
+
+ testName(acc, expected);
+
+ let nameChange = waitForEvent(EVENT_NAME_CHANGE, id);
+ await invokeContentTask(browser, [id, attr], (contentId, contentAttr) => {
+ content.document.getElementById(contentId).removeAttribute(contentAttr);
+ });
+ let event = await nameChange;
+
+ // Update accessible just in case it is now defunct.
+ target.acc = findAccessibleChildByID(event.accessible, id);
+}
+
+/**
+ * Test accessible name that is calculated from an element name, remove the
+ * element before proceeding to the next name test. If element removal results
+ * in a reorder event - wait for it. If accessible becomes defunct, update its
+ * reference using the one that is attached to a possible reorder event.
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Object} target { acc, id } structure that contains an
+ * accessible and its content element
+ * id.
+ * @param {Object} rule current elm rule for name calculation
+ * @param {[type]} expected expected name value
+ */
+async function testElmRule(browser, target, rule, expected) {
+ let { id, acc } = target;
+ let { elm } = rule;
+
+ testName(acc, expected);
+ let nameChange = waitForEvent(EVENT_NAME_CHANGE, id);
+
+ await invokeContentTask(browser, [elm], contentElm => {
+ content.document.querySelector(`${contentElm}`).remove();
+ });
+ let event = await nameChange;
+
+ // Update accessible just in case it is now defunct.
+ target.acc = findAccessibleChildByID(event.accessible, id);
+}
+
+/**
+ * Test accessible name that is calculated from its subtree, remove the subtree
+ * and wait for a reorder event before proceeding to the next name test. If
+ * accessible becomes defunct, update its reference using the one that is
+ * attached to a reorder event.
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Object} target { acc, id } structure that contains an
+ * accessible and its content element
+ * id.
+ * @param {Object} rule current subtree rule for name calculation
+ * @param {[type]} expected expected name value
+ */
+async function testSubtreeRule(browser, target, rule, expected) {
+ let { id, acc } = target;
+
+ testName(acc, expected);
+ let nameChange = waitForEvent(EVENT_NAME_CHANGE, id);
+
+ await invokeContentTask(browser, [id], contentId => {
+ let elm = content.document.getElementById(contentId);
+ while (elm.firstChild) {
+ elm.firstChild.remove();
+ }
+ });
+ let event = await nameChange;
+
+ // Update accessible just in case it is now defunct.
+ target.acc = findAccessibleChildByID(event.accessible, id);
+}
+
+/**
+ * Iterate over a list of rules and test accessible names for each one of the
+ * rules.
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Object} target { acc, id } structure that contains an
+ * accessible and its content element
+ * id.
+ * @param {Array} ruleset A list of rules to test a target with
+ * @param {Array} expected A list of expected name value for each rule
+ */
+async function testNameRule(browser, target, ruleset, expected) {
+ for (let i = 0; i < ruleset.length; ++i) {
+ let rule = ruleset[i];
+ let testFn;
+ if (rule.attr) {
+ testFn = testAttrRule;
+ } else if (rule.elm) {
+ testFn = testElmRule;
+ } else if (rule.fromsubtree) {
+ testFn = testSubtreeRule;
+ }
+ await testFn(browser, target, rule, expected[i]);
+ }
+}
+
+markupTests.forEach(({ id, ruleset, markup, expected }) =>
+ addAccessibleTask(
+ markup,
+ async function (browser, accDoc) {
+ const observer = {
+ observe(subject, topic, data) {
+ const event = subject.QueryInterface(nsIAccessibleEvent);
+ console.log(eventToString(event));
+ },
+ };
+ Services.obs.addObserver(observer, "accessible-event");
+ // Find a target accessible from an accessible subtree.
+ let acc = findAccessibleChildByID(accDoc, id);
+ let target = { id, acc };
+ await testNameRule(browser, target, rules[ruleset], expected);
+ Services.obs.removeObserver(observer, "accessible-event");
+ },
+ { iframe: true, remoteIframe: true }
+ )
+);
+
+/**
+ * Test caching of the document title.
+ */
+addAccessibleTask(
+ ``,
+ async function (browser, docAcc) {
+ let nameChanged = waitForEvent(EVENT_NAME_CHANGE, docAcc);
+ await invokeContentTask(browser, [], () => {
+ content.document.title = "new title";
+ });
+ await nameChanged;
+ testName(docAcc, "new title");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test that the name is updated when the content of a hidden aria-labelledby
+ * subtree changes.
+ */
+addAccessibleTask(
+ `
+<button id="button" aria-labelledby="label">
+<div id="label" hidden>a</div>
+ `,
+ async function (browser, docAcc) {
+ const button = findAccessibleChildByID(docAcc, "button");
+ testName(button, "a");
+ info("Changing label textContent");
+ let nameChanged = waitForEvent(EVENT_NAME_CHANGE, button);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("label").textContent = "c";
+ });
+ await nameChanged;
+ testName(button, "c");
+ info("Prepending text node to label");
+ nameChanged = waitForEvent(EVENT_NAME_CHANGE, button);
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("label")
+ .prepend(content.document.createTextNode("b"));
+ });
+ await nameChanged;
+ testName(button, "bc");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_position.js b/accessible/tests/browser/e10s/browser_caching_position.js
new file mode 100644
index 0000000000..5de246bb91
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_position.js
@@ -0,0 +1,194 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/layout.js */
+loadScripts({ name: "layout.js", dir: MOCHITESTS_DIR });
+
+function getCachedBounds(acc) {
+ let cachedBounds = "";
+ try {
+ cachedBounds = acc.cache.getStringProperty("relative-bounds");
+ } catch (e) {
+ ok(false, "Unable to fetch cached bounds from cache!");
+ }
+ return cachedBounds;
+}
+
+async function testCoordinates(accDoc, id, expectedWidthPx, expectedHeightPx) {
+ let acc = findAccessibleChildByID(accDoc, id, [Ci.nsIAccessibleImage]);
+ if (!acc) {
+ return;
+ }
+
+ let screenX = {};
+ let screenY = {};
+ let windowX = {};
+ let windowY = {};
+ let parentX = {};
+ let parentY = {};
+
+ // get screen coordinates.
+ acc.getImagePosition(
+ nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE,
+ screenX,
+ screenY
+ );
+ // get window coordinates.
+ acc.getImagePosition(
+ nsIAccessibleCoordinateType.COORDTYPE_WINDOW_RELATIVE,
+ windowX,
+ windowY
+ );
+ // get parent related coordinates.
+ acc.getImagePosition(
+ nsIAccessibleCoordinateType.COORDTYPE_PARENT_RELATIVE,
+ parentX,
+ parentY
+ );
+ // XXX For linked images, a negative parentY value is returned, and the
+ // screenY coordinate is the link's screenY coordinate minus 1.
+ // Until this is fixed, set parentY to -1 if it's negative.
+ if (parentY.value < 0) {
+ parentY.value = -1;
+ }
+
+ // See if asking image for child at image's screen coordinates gives
+ // correct accessible. getChildAtPoint operates on screen coordinates.
+ let tempAcc = null;
+ try {
+ tempAcc = acc.getChildAtPoint(screenX.value, screenY.value);
+ } catch (e) {}
+ is(tempAcc, acc, "Wrong accessible returned for position of " + id + "!");
+
+ // get image's parent.
+ let imageParentAcc = null;
+ try {
+ imageParentAcc = acc.parent;
+ } catch (e) {}
+ ok(imageParentAcc, "no parent accessible for " + id + "!");
+
+ if (imageParentAcc) {
+ // See if parent's screen coordinates plus image's parent relative
+ // coordinates equal to image's screen coordinates.
+ let parentAccX = {};
+ let parentAccY = {};
+ let parentAccWidth = {};
+ let parentAccHeight = {};
+ imageParentAcc.getBounds(
+ parentAccX,
+ parentAccY,
+ parentAccWidth,
+ parentAccHeight
+ );
+ is(
+ parentAccX.value + parentX.value,
+ screenX.value,
+ "Wrong screen x coordinate for " + id + "!"
+ );
+ // XXX see bug 456344
+ // is(
+ // parentAccY.value + parentY.value,
+ // screenY.value,
+ // "Wrong screen y coordinate for " + id + "!"
+ // );
+ }
+
+ let [expectedW, expectedH] = CSSToDevicePixels(
+ window,
+ expectedWidthPx,
+ expectedHeightPx
+ );
+ let width = {};
+ let height = {};
+ acc.getImageSize(width, height);
+ is(width.value, expectedW, "Wrong width for " + id + "!");
+ is(height.value, expectedH, "wrong height for " + id + "!");
+}
+
+addAccessibleTask(
+ `
+ <br>Simple image:<br>
+ <img id="nonLinkedImage" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"/>
+ <br>Linked image:<br>
+ <a href="http://www.mozilla.org"><img id="linkedImage" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"></a>
+ <br>Image with longdesc:<br>
+ <img id="longdesc" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" longdesc="longdesc_src.html"
+ alt="Image of Mozilla logo"/>
+ <br>Image with invalid url in longdesc:<br>
+ <img id="invalidLongdesc" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" longdesc="longdesc src.html"
+ alt="Image of Mozilla logo"/>
+ <br>Image with click and longdesc:<br>
+ <img id="clickAndLongdesc" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" longdesc="longdesc_src.html"
+ alt="Another image of Mozilla logo" onclick="alert('Clicked!');"/>
+
+ <br>image described by a link to be treated as longdesc<br>
+ <img id="longdesc2" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" aria-describedby="describing_link"
+ alt="Second Image of Mozilla logo"/>
+ <a id="describing_link" href="longdesc_src.html">link to description of image</a>
+
+ <br>Image described by a link to be treated as longdesc with whitespaces<br>
+ <img id="longdesc3" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" aria-describedby="describing_link2"
+ alt="Second Image of Mozilla logo"/>
+ <a id="describing_link2" href="longdesc src.html">link to description of image</a>
+
+ <br>Image with click:<br>
+ <img id="click" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"
+ alt="A third image of Mozilla logo" onclick="alert('Clicked, too!');"/>
+ `,
+ async function (browser, docAcc) {
+ // Test non-linked image
+ await testCoordinates(docAcc, "nonLinkedImage", 89, 38);
+
+ // Test linked image
+ await testCoordinates(docAcc, "linkedImage", 89, 38);
+
+ // Image with long desc
+ await testCoordinates(docAcc, "longdesc", 89, 38);
+
+ // Image with invalid url in long desc
+ await testCoordinates(docAcc, "invalidLongdesc", 89, 38);
+
+ // Image with click and long desc
+ await testCoordinates(docAcc, "clickAndLongdesc", 89, 38);
+
+ // Image with click
+ await testCoordinates(docAcc, "click", 89, 38);
+
+ // Image with long desc
+ await testCoordinates(docAcc, "longdesc2", 89, 38);
+
+ // Image described by HTML:a@href with whitespaces
+ await testCoordinates(docAcc, "longdesc3", 89, 38);
+ }
+);
+
+addAccessibleTask(
+ `
+ <br>Linked image:<br>
+ <a href="http://www.mozilla.org"><img id="linkedImage" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"></a>
+ `,
+ async function (browser, docAcc) {
+ const imgAcc = findAccessibleChildByID(docAcc, "linkedImage", [
+ Ci.nsIAccessibleImage,
+ ]);
+ const origCachedBounds = getCachedBounds(imgAcc);
+
+ await invokeContentTask(browser, [], () => {
+ const imgNode = content.document.getElementById("linkedImage");
+ imgNode.style = "margin-left: 1000px; margin-top: 500px;";
+ });
+
+ await untilCacheOk(() => {
+ return origCachedBounds != getCachedBounds(imgAcc);
+ }, "Cached bounds update after mutation");
+ },
+ {
+ // We can only access the `cache` attribute of an accessible when
+ // the cache is enabled and we're in a remote browser.
+ topLevel: true,
+ iframe: true,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_relations.js b/accessible/tests/browser/e10s/browser_caching_relations.js
new file mode 100644
index 0000000000..cc83930830
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_relations.js
@@ -0,0 +1,291 @@
+/* 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";
+requestLongerTimeout(2);
+
+/**
+ * A test specification that has the following format:
+ * [
+ * attr relevant aria attribute
+ * hostRelation corresponding host relation type
+ * dependantRelation corresponding dependant relation type
+ * ]
+ */
+const attrRelationsSpec = [
+ ["aria-labelledby", RELATION_LABELLED_BY, RELATION_LABEL_FOR],
+ ["aria-describedby", RELATION_DESCRIBED_BY, RELATION_DESCRIPTION_FOR],
+ ["aria-controls", RELATION_CONTROLLER_FOR, RELATION_CONTROLLED_BY],
+ ["aria-flowto", RELATION_FLOWS_TO, RELATION_FLOWS_FROM],
+ ["aria-details", RELATION_DETAILS, RELATION_DETAILS_FOR],
+ ["aria-errormessage", RELATION_ERRORMSG, RELATION_ERRORMSG_FOR],
+];
+
+/**
+ * Test caching of relations between accessible objects.
+ */
+addAccessibleTask(
+ `
+ <div id="dependant1">label</div>
+ <div id="dependant2">label2</div>
+ <div role="checkbox" id="host"></div>`,
+ async function (browser, accDoc) {
+ for (let spec of attrRelationsSpec) {
+ await testRelated(browser, accDoc, ...spec);
+ }
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of relations with respect to label objects and their "for" attr.
+ */
+addAccessibleTask(
+ `
+ <input type="checkbox" id="dependant1">
+ <input type="checkbox" id="dependant2">
+ <label id="host">label</label>`,
+ async function (browser, accDoc) {
+ await testRelated(
+ browser,
+ accDoc,
+ "for",
+ RELATION_LABEL_FOR,
+ RELATION_LABELLED_BY
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test rel caching for element with existing relation attribute.
+ */
+addAccessibleTask(
+ `<div id="label">label</div><button id="button" aria-labelledby="label">`,
+ async function (browser, accDoc) {
+ const button = findAccessibleChildByID(accDoc, "button");
+ const label = findAccessibleChildByID(accDoc, "label");
+
+ await testCachedRelation(button, RELATION_LABELLED_BY, label);
+ await testCachedRelation(label, RELATION_LABEL_FOR, button);
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of relations with respect to output objects and their "for" attr.
+ */
+addAccessibleTask(
+ `
+ <form oninput="host.value=parseInt(dependant1.value)+parseInt(dependant2.value)">
+ <input type="number" id="dependant1" value="50"> +
+ <input type="number" id="dependant2" value="25"> =
+ <output name="host" id="host"></output>
+ </form>`,
+ async function (browser, accDoc) {
+ await testRelated(
+ browser,
+ accDoc,
+ "for",
+ RELATION_CONTROLLED_BY,
+ RELATION_CONTROLLER_FOR
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test rel caching for <label> element with existing "for" attribute.
+ */
+addAccessibleTask(
+ `data:text/html,<label id="label" for="input">label</label><input id="input">`,
+ async function (browser, accDoc) {
+ const input = findAccessibleChildByID(accDoc, "input");
+ const label = findAccessibleChildByID(accDoc, "label");
+ await testCachedRelation(input, RELATION_LABELLED_BY, label);
+ await testCachedRelation(label, RELATION_LABEL_FOR, input);
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/*
+ * Test caching of relations with respect to label objects that are ancestors of
+ * their target.
+ */
+addAccessibleTask(
+ `
+ <label id="host">
+ <input type="checkbox" id="dependant1">
+ </label>`,
+ async function (browser, accDoc) {
+ const input = findAccessibleChildByID(accDoc, "dependant1");
+ const label = findAccessibleChildByID(accDoc, "host");
+
+ await testCachedRelation(input, RELATION_LABELLED_BY, label);
+ await testCachedRelation(label, RELATION_LABEL_FOR, input);
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/*
+ * Test EMBEDS on root accessible.
+ */
+addAccessibleTask(
+ `hello world`,
+ async function (browser, primaryDocAcc, secondaryDocAcc) {
+ // The root accessible should EMBED the top level
+ // content document. If this test runs in an iframe,
+ // the test harness will pass in doc accs for both the
+ // iframe (primaryDocAcc) and the top level remote
+ // browser (secondaryDocAcc). We should use the second
+ // one.
+ // If this is not in an iframe, we'll only get
+ // a single docAcc (primaryDocAcc) which refers to
+ // the top level content doc.
+ const topLevelDoc = secondaryDocAcc ? secondaryDocAcc : primaryDocAcc;
+ await testRelation(
+ getRootAccessible(document),
+ RELATION_EMBEDS,
+ topLevelDoc
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test CONTAINING_TAB_PANE
+ */
+addAccessibleTask(
+ `<p id="p">hello world</p>`,
+ async function (browser, primaryDocAcc, secondaryDocAcc) {
+ // The CONTAINING_TAB_PANE of any acc should be the top level
+ // content document. If this test runs in an iframe,
+ // the test harness will pass in doc accs for both the
+ // iframe (primaryDocAcc) and the top level remote
+ // browser (secondaryDocAcc). We should use the second
+ // one.
+ // If this is not in an iframe, we'll only get
+ // a single docAcc (primaryDocAcc) which refers to
+ // the top level content doc.
+ const topLevelDoc = secondaryDocAcc ? secondaryDocAcc : primaryDocAcc;
+ await testCachedRelation(
+ findAccessibleChildByID(primaryDocAcc, "p"),
+ RELATION_CONTAINING_TAB_PANE,
+ topLevelDoc
+ );
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/*
+ * Test relation caching on link
+ */
+addAccessibleTask(
+ `
+ <a id="link" href="#item">a</a>
+ <div id="item">hello</div>
+ <div id="item2">world</div>
+ <a id="link2" href="#anchor">b</a>
+ <a id="namedLink" name="anchor">c</a>`,
+ async function (browser, accDoc) {
+ const link = findAccessibleChildByID(accDoc, "link");
+ const link2 = findAccessibleChildByID(accDoc, "link2");
+ const namedLink = findAccessibleChildByID(accDoc, "namedLink");
+ const item = findAccessibleChildByID(accDoc, "item");
+ const item2 = findAccessibleChildByID(accDoc, "item2");
+
+ await testCachedRelation(link, RELATION_LINKS_TO, item);
+ await testCachedRelation(link2, RELATION_LINKS_TO, namedLink);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("link").href = "";
+ content.document.getElementById("namedLink").name = "newName";
+ });
+
+ await testCachedRelation(link, RELATION_LINKS_TO, null);
+ await testCachedRelation(link2, RELATION_LINKS_TO, null);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("link").href = "#item2";
+ });
+
+ await testCachedRelation(link, RELATION_LINKS_TO, item2);
+ },
+ {
+ chrome: true,
+ // IA2 doesn't have a LINKS_TO relation and Windows non-cached
+ // RemoteAccessible uses IA2, so we can't run these tests in this case.
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/*
+ * Test relation caching for NODE_CHILD_OF and NODE_PARENT_OF with aria trees.
+ */
+addAccessibleTask(
+ `
+ <div role="tree" id="tree">
+ <div role="treeitem" id="treeitem">test</div>
+ <div role="treeitem" id="treeitem2">test</div>
+ </div>`,
+ async function (browser, accDoc) {
+ const tree = findAccessibleChildByID(accDoc, "tree");
+ const treeItem = findAccessibleChildByID(accDoc, "treeitem");
+ const treeItem2 = findAccessibleChildByID(accDoc, "treeitem2");
+
+ await testCachedRelation(tree, RELATION_NODE_PARENT_OF, [
+ treeItem,
+ treeItem2,
+ ]);
+ await testCachedRelation(treeItem, RELATION_NODE_CHILD_OF, tree);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/*
+ * Test relation caching for NODE_CHILD_OF and NODE_PARENT_OF with aria lists.
+ */
+addAccessibleTask(
+ `
+ <div id="l1" role="list">
+ <div id="l1i1" role="listitem" aria-level="1">a</div>
+ <div id="l1i2" role="listitem" aria-level="2">b</div>
+ <div id="l1i3" role="listitem" aria-level="1">c</div>
+ </div>`,
+ async function (browser, accDoc) {
+ const list = findAccessibleChildByID(accDoc, "l1");
+ const listItem1 = findAccessibleChildByID(accDoc, "l1i1");
+ const listItem2 = findAccessibleChildByID(accDoc, "l1i2");
+ const listItem3 = findAccessibleChildByID(accDoc, "l1i3");
+
+ await testCachedRelation(list, RELATION_NODE_PARENT_OF, [
+ listItem1,
+ listItem3,
+ ]);
+ await testCachedRelation(listItem1, RELATION_NODE_CHILD_OF, list);
+ await testCachedRelation(listItem3, RELATION_NODE_CHILD_OF, list);
+
+ await testCachedRelation(listItem1, RELATION_NODE_PARENT_OF, listItem2);
+ await testCachedRelation(listItem2, RELATION_NODE_CHILD_OF, listItem1);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/*
+ * Test NODE_CHILD_OF relation caching for JAWS window emulation special case.
+ */
+addAccessibleTask(
+ ``,
+ async function (browser, accDoc) {
+ await testCachedRelation(accDoc, RELATION_NODE_CHILD_OF, accDoc.parent);
+ },
+ { topLevel: true, chrome: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_relations_002.js b/accessible/tests/browser/e10s/browser_caching_relations_002.js
new file mode 100644
index 0000000000..9b7acda96a
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_relations_002.js
@@ -0,0 +1,293 @@
+/* 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";
+requestLongerTimeout(2);
+
+/**
+ * Test MEMBER_OF relation caching on HTML radio buttons
+ */
+addAccessibleTask(
+ `
+ <input type="radio" id="r1">I have no name<br>
+ <input type="radio" id="r2">I also have no name<br>
+ <input type="radio" id="r3" name="n">I have a name<br>
+ <input type="radio" id="r4" name="a">I have a different name<br>
+ <fieldset role="radiogroup">
+ <input type="radio" id="r5" name="n">I have an already used name
+ and am in a different part of the tree
+ <input type="radio" id="r6" name="r">I have a different name but am
+ in the same group
+ </fieldset>`,
+ async function (browser, accDoc) {
+ const r1 = findAccessibleChildByID(accDoc, "r1");
+ const r2 = findAccessibleChildByID(accDoc, "r2");
+ const r3 = findAccessibleChildByID(accDoc, "r3");
+ const r4 = findAccessibleChildByID(accDoc, "r4");
+ const r5 = findAccessibleChildByID(accDoc, "r5");
+ const r6 = findAccessibleChildByID(accDoc, "r6");
+
+ await testCachedRelation(r1, RELATION_MEMBER_OF, null);
+ await testCachedRelation(r2, RELATION_MEMBER_OF, null);
+ await testCachedRelation(r3, RELATION_MEMBER_OF, [r3, r5]);
+ await testCachedRelation(r4, RELATION_MEMBER_OF, r4);
+ await testCachedRelation(r5, RELATION_MEMBER_OF, [r3, r5]);
+ await testCachedRelation(r6, RELATION_MEMBER_OF, r6);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("r5").name = "a";
+ });
+
+ await testCachedRelation(r3, RELATION_MEMBER_OF, r3);
+ await testCachedRelation(r4, RELATION_MEMBER_OF, [r5, r4]);
+ await testCachedRelation(r5, RELATION_MEMBER_OF, [r5, r4]);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/*
+ * Test MEMBER_OF relation caching on aria radio buttons
+ */
+addAccessibleTask(
+ `
+ <div role="radio" id="r1">I have no radio group</div><br>
+ <fieldset role="radiogroup" id="fs">
+ <div role="radio" id="r2">hello</div><br>
+ <div role="radio" id="r3">world</div><br>
+ </fieldset>`,
+ async function (browser, accDoc) {
+ const r1 = findAccessibleChildByID(accDoc, "r1");
+ const r2 = findAccessibleChildByID(accDoc, "r2");
+ let r3 = findAccessibleChildByID(accDoc, "r3");
+
+ await testCachedRelation(r1, RELATION_MEMBER_OF, null);
+ await testCachedRelation(r2, RELATION_MEMBER_OF, [r2, r3]);
+ await testCachedRelation(r3, RELATION_MEMBER_OF, [r2, r3]);
+ const r = waitForEvent(EVENT_INNER_REORDER, "fs");
+ await invokeContentTask(browser, [], () => {
+ let innerRadio = content.document.getElementById("r3");
+ content.document.body.appendChild(innerRadio);
+ });
+ await r;
+
+ r3 = findAccessibleChildByID(accDoc, "r3");
+ await testCachedRelation(r1, RELATION_MEMBER_OF, null);
+ await testCachedRelation(r2, RELATION_MEMBER_OF, r2);
+ await testCachedRelation(r3, RELATION_MEMBER_OF, null);
+ },
+ {
+ chrome: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/*
+ * Test mutation of LABEL relations via accessible shutdown.
+ */
+addAccessibleTask(
+ `
+ <div id="d"></div>
+ <label id="l">
+ <select id="s">
+ `,
+ async function (browser, accDoc) {
+ const label = findAccessibleChildByID(accDoc, "l");
+ const select = findAccessibleChildByID(accDoc, "s");
+ const div = findAccessibleChildByID(accDoc, "d");
+
+ await testCachedRelation(label, RELATION_LABEL_FOR, select);
+ await testCachedRelation(select, RELATION_LABELLED_BY, label);
+ await testCachedRelation(div, RELATION_LABELLED_BY, null);
+
+ const r = waitForEvent(EVENT_REORDER, "l");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("s").remove();
+ });
+ await r;
+ await invokeContentTask(browser, [], () => {
+ const l = content.document.getElementById("l");
+ l.htmlFor = "d";
+ });
+ await testCachedRelation(label, RELATION_LABEL_FOR, div);
+ await testCachedRelation(div, RELATION_LABELLED_BY, label);
+ },
+ {
+ chrome: false,
+ iframe: true,
+ remoteIframe: true,
+ topLevel: true,
+ }
+);
+
+/*
+ * Test mutation of LABEL relations via DOM ID reuse.
+ */
+addAccessibleTask(
+ `
+ <div id="label">before</div><input id="input" aria-labelledby="label">
+ `,
+ async function (browser, accDoc) {
+ let label = findAccessibleChildByID(accDoc, "label");
+ const input = findAccessibleChildByID(accDoc, "input");
+
+ await testCachedRelation(label, RELATION_LABEL_FOR, input);
+ await testCachedRelation(input, RELATION_LABELLED_BY, label);
+
+ const r = waitForEvent(EVENT_REORDER, accDoc);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("label").remove();
+ let l = content.document.createElement("div");
+ l.id = "label";
+ l.textContent = "after";
+ content.document.body.insertBefore(
+ l,
+ content.document.getElementById("input")
+ );
+ });
+ await r;
+ label = findAccessibleChildByID(accDoc, "label");
+ await testCachedRelation(label, RELATION_LABEL_FOR, input);
+ await testCachedRelation(input, RELATION_LABELLED_BY, label);
+ },
+ {
+ chrome: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/*
+ * Test LINKS_TO relation caching an anchor with multiple hashes
+ */
+addAccessibleTask(
+ `
+ <a id="link" href="#foo#bar">Origin</a><br>
+ <a id="anchor" name="foo#bar">Destination`,
+ async function (browser, accDoc) {
+ const link = findAccessibleChildByID(accDoc, "link");
+ const anchor = findAccessibleChildByID(accDoc, "anchor");
+
+ await testCachedRelation(link, RELATION_LINKS_TO, anchor);
+ },
+ {
+ chrome: true,
+ // IA2 doesn't have a LINKS_TO relation and Windows non-cached
+ // RemoteAccessible uses IA2, so we can't run these tests in this case.
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/*
+ * Test mutation of LABEL relations via accessible shutdown.
+ */
+addAccessibleTask(
+ `
+ <div id="d"></div>
+ <label id="l">
+ <select id="s">
+ `,
+ async function (browser, accDoc) {
+ const label = findAccessibleChildByID(accDoc, "l");
+ const select = findAccessibleChildByID(accDoc, "s");
+ const div = findAccessibleChildByID(accDoc, "d");
+
+ await testCachedRelation(label, RELATION_LABEL_FOR, select);
+ await testCachedRelation(select, RELATION_LABELLED_BY, label);
+ await testCachedRelation(div, RELATION_LABELLED_BY, null);
+ await untilCacheOk(() => {
+ try {
+ // We should get an acc ID back from this, but we don't have a way of
+ // verifying its correctness -- it should be the ID of the select.
+ return label.cache.getStringProperty("for");
+ } catch (e) {
+ ok(false, "Exception thrown while trying to read from the cache");
+ return false;
+ }
+ }, "Label for relation exists");
+
+ const r = waitForEvent(EVENT_REORDER, "l");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("s").remove();
+ });
+ await r;
+ await untilCacheOk(() => {
+ try {
+ label.cache.getStringProperty("for");
+ } catch (e) {
+ // This property should no longer exist in the cache, so we should
+ // get an exception if we try to fetch it.
+ return true;
+ }
+ return false;
+ }, "Label for relation exists");
+
+ await invokeContentTask(browser, [], () => {
+ const l = content.document.getElementById("l");
+ l.htmlFor = "d";
+ });
+ await testCachedRelation(label, RELATION_LABEL_FOR, div);
+ await testCachedRelation(div, RELATION_LABELLED_BY, label);
+ },
+ {
+ /**
+ * This functionality is broken in our LocalAcccessible implementation,
+ * so we avoid running this test in chrome or when the cache is off.
+ */
+ chrome: false,
+ iframe: true,
+ remoteIframe: true,
+ topLevel: true,
+ }
+);
+
+/**
+ * Test label relations on HTML figure/figcaption.
+ */
+addAccessibleTask(
+ `
+<figure id="figure1">
+ before
+ <figcaption id="caption1">caption1</figcaption>
+ after
+</figure>
+<figure id="figure2" aria-labelledby="label">
+ <figcaption id="caption2">caption2</figure>
+</figure>
+<div id="label">label</div>
+ `,
+ async function (browser, docAcc) {
+ const figure1 = findAccessibleChildByID(docAcc, "figure1");
+ let caption1 = findAccessibleChildByID(docAcc, "caption1");
+ await testCachedRelation(figure1, RELATION_LABELLED_BY, caption1);
+ await testCachedRelation(caption1, RELATION_LABEL_FOR, figure1);
+
+ info("Hiding caption1");
+ let mutated = waitForEvent(EVENT_HIDE, caption1);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("caption1").hidden = true;
+ });
+ await mutated;
+ await testCachedRelation(figure1, RELATION_LABELLED_BY, null);
+
+ info("Showing caption1");
+ mutated = waitForEvent(EVENT_SHOW, "caption1");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("caption1").hidden = false;
+ });
+ caption1 = (await mutated).accessible;
+ await testCachedRelation(figure1, RELATION_LABELLED_BY, caption1);
+ await testCachedRelation(caption1, RELATION_LABEL_FOR, figure1);
+
+ const figure2 = findAccessibleChildByID(docAcc, "figure2");
+ const caption2 = findAccessibleChildByID(docAcc, "caption2");
+ const label = findAccessibleChildByID(docAcc, "label");
+ await testCachedRelation(figure2, RELATION_LABELLED_BY, [label, caption2]);
+ await testCachedRelation(caption2, RELATION_LABEL_FOR, figure2);
+ await testCachedRelation(label, RELATION_LABEL_FOR, figure2);
+ },
+ { chrome: true, topLevel: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_states.js b/accessible/tests/browser/e10s/browser_caching_states.js
new file mode 100644
index 0000000000..2471f9774a
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_states.js
@@ -0,0 +1,484 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * expected {Array} expected states for a given accessible that have the
+ * following format:
+ * [
+ * expected state,
+ * expected extra state,
+ * absent state,
+ * absent extra state
+ * ]
+ * attrs {?Array} an optional list of attributes to update
+ * }
+ */
+
+// State caching tests for attribute changes
+const attributeTests = [
+ {
+ desc:
+ "Checkbox with @checked attribute set to true should have checked " +
+ "state",
+ attrs: [
+ {
+ attr: "checked",
+ value: "true",
+ },
+ ],
+ expected: [STATE_CHECKED, 0],
+ },
+ {
+ desc: "Checkbox with no @checked attribute should not have checked state",
+ attrs: [
+ {
+ attr: "checked",
+ },
+ ],
+ expected: [0, 0, STATE_CHECKED],
+ },
+];
+
+// State caching tests for ARIA changes
+const ariaTests = [
+ {
+ desc: "File input has busy state when @aria-busy attribute is set to true",
+ attrs: [
+ {
+ attr: "aria-busy",
+ value: "true",
+ },
+ ],
+ expected: [STATE_BUSY, 0, STATE_REQUIRED | STATE_INVALID],
+ },
+ {
+ desc:
+ "File input has required state when @aria-required attribute is set " +
+ "to true",
+ attrs: [
+ {
+ attr: "aria-required",
+ value: "true",
+ },
+ ],
+ expected: [STATE_REQUIRED, 0, STATE_INVALID],
+ },
+ {
+ desc:
+ "File input has invalid state when @aria-invalid attribute is set to " +
+ "true",
+ attrs: [
+ {
+ attr: "aria-invalid",
+ value: "true",
+ },
+ ],
+ expected: [STATE_INVALID, 0],
+ },
+];
+
+// Extra state caching tests
+const extraStateTests = [
+ {
+ desc:
+ "Input has no extra enabled state when aria and native disabled " +
+ "attributes are set at once",
+ attrs: [
+ {
+ attr: "aria-disabled",
+ value: "true",
+ },
+ {
+ attr: "disabled",
+ value: "true",
+ },
+ ],
+ expected: [0, 0, 0, EXT_STATE_ENABLED],
+ },
+ {
+ desc:
+ "Input has an extra enabled state when aria and native disabled " +
+ "attributes are unset at once",
+ attrs: [
+ {
+ attr: "aria-disabled",
+ },
+ {
+ attr: "disabled",
+ },
+ ],
+ expected: [0, EXT_STATE_ENABLED],
+ },
+];
+
+async function runStateTests(browser, accDoc, id, tests) {
+ let acc = findAccessibleChildByID(accDoc, id);
+ for (let { desc, attrs, expected } of tests) {
+ const [expState, expExtState, absState, absExtState] = expected;
+ info(desc);
+ let onUpdate = waitForEvent(EVENT_STATE_CHANGE, evt => {
+ if (getAccessibleDOMNodeID(evt.accessible) != id) {
+ return false;
+ }
+ // Events can be fired for states other than the ones we're interested
+ // in. If this happens, the states we're expecting might not be exposed
+ // yet.
+ const scEvt = evt.QueryInterface(nsIAccessibleStateChangeEvent);
+ if (scEvt.isExtraState) {
+ if (scEvt.state & expExtState || scEvt.state & absExtState) {
+ return true;
+ }
+ return false;
+ }
+ return scEvt.state & expState || scEvt.state & absState;
+ });
+ for (let { attr, value } of attrs) {
+ await invokeSetAttribute(browser, id, attr, value);
+ }
+ await onUpdate;
+ testStates(acc, ...expected);
+ }
+}
+
+/**
+ * Test caching of accessible object states
+ */
+addAccessibleTask(
+ `
+ <input id="checkbox" type="checkbox">
+ <input id="file" type="file">
+ <input id="text">`,
+ async function (browser, accDoc) {
+ await runStateTests(browser, accDoc, "checkbox", attributeTests);
+ await runStateTests(browser, accDoc, "file", ariaTests);
+ await runStateTests(browser, accDoc, "text", extraStateTests);
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of the focused state.
+ */
+addAccessibleTask(
+ `
+ <button id="b1">b1</button>
+ <button id="b2">b2</button>
+ `,
+ async function (browser, docAcc) {
+ const b1 = findAccessibleChildByID(docAcc, "b1");
+ const b2 = findAccessibleChildByID(docAcc, "b2");
+
+ let focused = waitForEvent(EVENT_FOCUS, b1);
+ await invokeFocus(browser, "b1");
+ await focused;
+ testStates(docAcc, 0, 0, STATE_FOCUSED);
+ testStates(b1, STATE_FOCUSED);
+ testStates(b2, 0, 0, STATE_FOCUSED);
+
+ focused = waitForEvent(EVENT_FOCUS, b2);
+ await invokeFocus(browser, "b2");
+ await focused;
+ testStates(b2, STATE_FOCUSED);
+ testStates(b1, 0, 0, STATE_FOCUSED);
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test that the document initially gets the focused state.
+ * We can't do this in the test above because that test runs in iframes as well
+ * as a top level document.
+ */
+addAccessibleTask(
+ `
+ <button id="b1">b1</button>
+ <button id="b2">b2</button>
+ `,
+ async function (browser, docAcc) {
+ testStates(docAcc, STATE_FOCUSED);
+ }
+);
+
+/**
+ * Test caching of the focused state in iframes.
+ */
+addAccessibleTask(
+ `
+ <button id="button">button</button>
+ `,
+ async function (browser, iframeDocAcc, topDocAcc) {
+ testStates(topDocAcc, STATE_FOCUSED);
+ const button = findAccessibleChildByID(iframeDocAcc, "button");
+ testStates(button, 0, 0, STATE_FOCUSED);
+ let focused = waitForEvent(EVENT_FOCUS, button);
+ info("Focusing button in iframe");
+ button.takeFocus();
+ await focused;
+ testStates(topDocAcc, 0, 0, STATE_FOCUSED);
+ testStates(button, STATE_FOCUSED);
+ },
+ { topLevel: false, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of the focusable state in iframes which are initially visibility: hidden.
+ */
+addAccessibleTask(
+ `
+<button id="button"></button>
+<span id="span" tabindex="-1">span</span>`,
+ async function (browser, topDocAcc) {
+ info("Changing visibility on iframe");
+ let reordered = waitForEvent(EVENT_REORDER, topDocAcc);
+ await SpecialPowers.spawn(browser, [DEFAULT_IFRAME_ID], iframeId => {
+ content.document.getElementById(iframeId).style.visibility = "";
+ });
+ await reordered;
+ // The iframe doc a11y tree might not be built yet.
+ const iframeDoc = await TestUtils.waitForCondition(() =>
+ findAccessibleChildByID(topDocAcc, DEFAULT_IFRAME_DOC_BODY_ID)
+ );
+ // Log/verify whether this is an in-process or OOP iframe.
+ await comparePIDs(browser, gIsRemoteIframe);
+ const button = findAccessibleChildByID(iframeDoc, "button");
+ testStates(button, STATE_FOCUSABLE);
+ const span = findAccessibleChildByID(iframeDoc, "span");
+ ok(span, "span Accessible exists");
+ testStates(span, STATE_FOCUSABLE);
+ },
+ {
+ topLevel: false,
+ iframe: true,
+ remoteIframe: true,
+ iframeAttrs: { style: "visibility: hidden;" },
+ skipFissionDocLoad: true,
+ }
+);
+
+function checkOpacity(acc, present) {
+ let [, extraState] = getStates(acc);
+ let currOpacity = extraState & EXT_STATE_OPAQUE;
+ return present ? currOpacity : !currOpacity;
+}
+
+/**
+ * Test caching of the OPAQUE1 state.
+ */
+addAccessibleTask(
+ `
+ <div id="div">hello world</div>
+ `,
+ async function (browser, docAcc) {
+ const div = findAccessibleChildByID(docAcc, "div");
+ await untilCacheOk(() => checkOpacity(div, true), "Found opaque state");
+
+ await invokeContentTask(browser, [], () => {
+ let elm = content.document.getElementById("div");
+ elm.style = "opacity: 0.4;";
+ elm.offsetTop; // Flush layout.
+ });
+
+ await untilCacheOk(
+ () => checkOpacity(div, false),
+ "Did not find opaque state"
+ );
+
+ await invokeContentTask(browser, [], () => {
+ let elm = content.document.getElementById("div");
+ elm.style = "opacity: 1;";
+ elm.offsetTop; // Flush layout.
+ });
+
+ await untilCacheOk(() => checkOpacity(div, true), "Found opaque state");
+ },
+ { iframe: true, remoteIframe: true, chrome: true }
+);
+
+/**
+ * Test caching of the editable state.
+ */
+addAccessibleTask(
+ `<div id="div" contenteditable></div>`,
+ async function (browser, docAcc) {
+ const div = findAccessibleChildByID(docAcc, "div");
+ testStates(div, 0, EXT_STATE_EDITABLE, 0, 0);
+ // Ensure that a contentEditable descendant doesn't cause editable to be
+ // exposed on the document.
+ testStates(docAcc, STATE_READONLY, 0, 0, EXT_STATE_EDITABLE);
+
+ info("Setting contentEditable on the body");
+ let stateChanged = Promise.all([
+ waitForStateChange(docAcc, EXT_STATE_EDITABLE, true, true),
+ waitForStateChange(docAcc, STATE_READONLY, false, false),
+ ]);
+ await invokeContentTask(browser, [], () => {
+ content.document.body.contentEditable = true;
+ });
+ await stateChanged;
+ testStates(docAcc, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0);
+
+ info("Clearing contentEditable on the body");
+ stateChanged = Promise.all([
+ waitForStateChange(docAcc, EXT_STATE_EDITABLE, false, true),
+ waitForStateChange(docAcc, STATE_READONLY, true, false),
+ ]);
+ await invokeContentTask(browser, [], () => {
+ content.document.body.contentEditable = false;
+ });
+ await stateChanged;
+ testStates(docAcc, STATE_READONLY, 0, 0, EXT_STATE_EDITABLE);
+
+ info("Clearing contentEditable on div");
+ stateChanged = waitForStateChange(div, EXT_STATE_EDITABLE, false, true);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("div").contentEditable = false;
+ });
+ await stateChanged;
+ testStates(div, 0, 0, 0, EXT_STATE_EDITABLE);
+
+ info("Setting contentEditable on div");
+ stateChanged = waitForStateChange(div, EXT_STATE_EDITABLE, true, true);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("div").contentEditable = true;
+ });
+ await stateChanged;
+ testStates(div, 0, EXT_STATE_EDITABLE, 0, 0);
+
+ info("Setting designMode on document");
+ stateChanged = Promise.all([
+ waitForStateChange(docAcc, EXT_STATE_EDITABLE, true, true),
+ waitForStateChange(docAcc, STATE_READONLY, false, false),
+ ]);
+ await invokeContentTask(browser, [], () => {
+ content.document.designMode = "on";
+ });
+ await stateChanged;
+ testStates(docAcc, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0);
+
+ info("Clearing designMode on document");
+ stateChanged = Promise.all([
+ waitForStateChange(docAcc, EXT_STATE_EDITABLE, false, true),
+ waitForStateChange(docAcc, STATE_READONLY, true, false),
+ ]);
+ await invokeContentTask(browser, [], () => {
+ content.document.designMode = "off";
+ });
+ await stateChanged;
+ testStates(docAcc, STATE_READONLY, 0, 0, EXT_STATE_EDITABLE);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true, chrome: true }
+);
+
+/**
+ * Test caching of the stale and busy states.
+ */
+addAccessibleTask(
+ `<iframe id="iframe"></iframe>`,
+ async function (browser, docAcc) {
+ const iframe = findAccessibleChildByID(docAcc, "iframe");
+ info("Setting iframe src");
+ // This iframe won't finish loading. Thus, it will get the stale state and
+ // won't fire a document load complete event. We use the reorder event on
+ // the iframe to know when the document has been created.
+ let reordered = waitForEvent(EVENT_REORDER, iframe);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("iframe").src =
+ 'data:text/html,<img src="http://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs">';
+ });
+ const iframeDoc = (await reordered).accessible.firstChild;
+ testStates(iframeDoc, STATE_BUSY, EXT_STATE_STALE, 0, 0);
+
+ info("Finishing load of iframe doc");
+ let loadCompleted = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, iframeDoc);
+ await fetch(
+ "https://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs?complete"
+ );
+ await loadCompleted;
+ testStates(iframeDoc, 0, 0, STATE_BUSY, EXT_STATE_STALE);
+ },
+ { topLevel: true, chrome: true }
+);
+
+/**
+ * Test implicit selected state.
+ */
+addAccessibleTask(
+ `
+<div role="tablist">
+ <div id="noSel" role="tab" tabindex="0">noSel</div>
+ <div id="selFalse" role="tab" aria-selected="false" tabindex="0">selFalse</div>
+</div>
+<div role="listbox" aria-multiselectable="true">
+ <div id="multiNoSel" role="option" tabindex="0">multiNoSel</div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const noSel = findAccessibleChildByID(docAcc, "noSel");
+ testStates(noSel, 0, 0, STATE_FOCUSED | STATE_SELECTED, 0);
+ info("Focusing noSel");
+ let focused = waitForEvent(EVENT_FOCUS, noSel);
+ noSel.takeFocus();
+ await focused;
+ testStates(noSel, STATE_FOCUSED | STATE_SELECTED, 0, 0, 0);
+
+ const selFalse = findAccessibleChildByID(docAcc, "selFalse");
+ testStates(selFalse, 0, 0, STATE_FOCUSED | STATE_SELECTED, 0);
+ info("Focusing selFalse");
+ focused = waitForEvent(EVENT_FOCUS, selFalse);
+ selFalse.takeFocus();
+ await focused;
+ testStates(selFalse, STATE_FOCUSED, 0, STATE_SELECTED, 0);
+
+ const multiNoSel = findAccessibleChildByID(docAcc, "multiNoSel");
+ testStates(multiNoSel, 0, 0, STATE_FOCUSED | STATE_SELECTED, 0);
+ info("Focusing multiNoSel");
+ focused = waitForEvent(EVENT_FOCUS, multiNoSel);
+ multiNoSel.takeFocus();
+ await focused;
+ testStates(multiNoSel, STATE_FOCUSED, 0, STATE_SELECTED, 0);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true, chrome: true }
+);
+
+/**
+ * Test invalid state determined via DOM.
+ */
+addAccessibleTask(
+ `<input type="email" id="email">`,
+ async function (browser, docAcc) {
+ const email = findAccessibleChildByID(docAcc, "email");
+ info("Focusing email");
+ let focused = waitForEvent(EVENT_FOCUS, email);
+ email.takeFocus();
+ await focused;
+ info("Typing a");
+ let invalidChanged = waitForStateChange(email, STATE_INVALID, true);
+ EventUtils.sendString("a");
+ await invalidChanged;
+ testStates(email, STATE_INVALID);
+ info("Typing @b");
+ invalidChanged = waitForStateChange(email, STATE_INVALID, false);
+ EventUtils.sendString("@b");
+ await invalidChanged;
+ testStates(email, 0, 0, STATE_INVALID);
+ info("Typing backspace");
+ invalidChanged = waitForStateChange(email, STATE_INVALID, true);
+ EventUtils.synthesizeKey("KEY_Backspace");
+ await invalidChanged;
+ testStates(email, STATE_INVALID);
+ },
+ { chrome: true, topLevel: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_table.js b/accessible/tests/browser/e10s/browser_caching_table.js
new file mode 100644
index 0000000000..fa978c08eb
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_table.js
@@ -0,0 +1,532 @@
+/* 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 tables for both local and remote Accessibles. There is more extensive
+ * coverage in ../../mochitest/table. These tests are primarily to ensure that
+ * the cache works as expected and that there is consistency between local and
+ * remote.
+ */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/table.js */
+/* import-globals-from ../../mochitest/attributes.js */
+loadScripts(
+ { name: "table.js", dir: MOCHITESTS_DIR },
+ { name: "attributes.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test table counts, indexes, extents and implicit headers.
+ */
+addAccessibleTask(
+ `
+<table id="table">
+ <thead>
+ <tr><th id="a">a</th><th id="bc" colspan="2">bc</th><th id="d">d</th></tr>
+ </thead>
+ <tbody>
+ <tr><th id="ei" rowspan="2">ei</th><td id="fj" rowspan="0">fj</td><td id="g">g</td><td id="h">h</td></tr>
+ <tr><td id="k">k</td></tr>
+ </tbody>
+</table>
+ `,
+ async function (browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+ is(table.rowCount, 3, "table rowCount correct");
+ is(table.columnCount, 4, "table columnCount correct");
+ testTableIndexes(table, [
+ [0, 1, 1, 2],
+ [3, 4, 5, 6],
+ [3, 4, 7, -1],
+ ]);
+ const cells = {};
+ for (const id of ["a", "bc", "d", "ei", "fj", "g", "h", "k"]) {
+ cells[id] = findAccessibleChildByID(docAcc, id, [nsIAccessibleTableCell]);
+ }
+ is(cells.a.rowExtent, 1, "a rowExtent correct");
+ is(cells.a.columnExtent, 1, "a columnExtent correct");
+ is(cells.bc.rowExtent, 1, "bc rowExtent correct");
+ is(cells.bc.columnExtent, 2, "bc columnExtent correct");
+ is(cells.ei.rowExtent, 2, "ei rowExtent correct");
+ is(cells.fj.rowExtent, 2, "fj rowExtent correct");
+ testHeaderCells([
+ {
+ cell: cells.ei,
+ rowHeaderCells: [],
+ columnHeaderCells: [cells.a],
+ },
+ {
+ cell: cells.g,
+ rowHeaderCells: [cells.ei],
+ columnHeaderCells: [cells.bc],
+ },
+ {
+ cell: cells.k,
+ rowHeaderCells: [cells.ei],
+ columnHeaderCells: [cells.bc],
+ },
+ ]);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test table explicit headers.
+ */
+addAccessibleTask(
+ `
+<table id="table">
+ <tr><th id="a">a</th><th id="b">b</th></tr>
+ <tr><td id="c" headers="b d">c</td><th scope="row" id="d">d</th></tr>
+ <tr><td id="e" headers="c f">e</td><td id="f">f</td></tr>
+</table>
+ `,
+ async function (browser, docAcc) {
+ const cells = {};
+ for (const id of ["a", "b", "c", "d", "e", "f"]) {
+ cells[id] = findAccessibleChildByID(docAcc, id, [nsIAccessibleTableCell]);
+ }
+ testHeaderCells([
+ {
+ cell: cells.c,
+ rowHeaderCells: [cells.d],
+ columnHeaderCells: [cells.b],
+ },
+ {
+ cell: cells.e,
+ rowHeaderCells: [cells.f],
+ columnHeaderCells: [cells.c],
+ },
+ ]);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test that an inner table doesn't impact an outer table.
+ */
+addAccessibleTask(
+ `
+<table id="outerTable">
+ <tr><th id="outerCell">outerCell<table id="innerTable">
+ <tr><th id="innerCell">a</th></tr></table>
+ </table></th></tr>
+</table>
+ `,
+ async function (browser, docAcc) {
+ const outerTable = findAccessibleChildByID(docAcc, "outerTable", [
+ nsIAccessibleTable,
+ ]);
+ is(outerTable.rowCount, 1, "outerTable rowCount correct");
+ is(outerTable.columnCount, 1, "outerTable columnCount correct");
+ const outerCell = findAccessibleChildByID(docAcc, "outerCell");
+ is(
+ outerTable.getCellAt(0, 0),
+ outerCell,
+ "outerTable returns correct cell"
+ );
+ const innerTable = findAccessibleChildByID(docAcc, "innerTable", [
+ nsIAccessibleTable,
+ ]);
+ is(innerTable.rowCount, 1, "innerTable rowCount correct");
+ is(innerTable.columnCount, 1, "innerTable columnCount correct");
+ const innerCell = findAccessibleChildByID(docAcc, "innerCell");
+ is(
+ innerTable.getCellAt(0, 0),
+ innerCell,
+ "innerTable returns correct cell"
+ );
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test table caption and summary.
+ */
+addAccessibleTask(
+ `
+<table id="t1">
+ <caption id="c1">c1</caption>
+ <tr><th>a</th></tr>
+</table>
+<table id="t2" summary="s2">
+ <tr><th>a</th></tr>
+</table>
+<table id="t3" summary="s3">
+ <caption id="c3">c3</caption>
+ <tr><th>a</th></tr>
+</table>
+ `,
+ async function (browser, docAcc) {
+ const t1 = findAccessibleChildByID(docAcc, "t1", [nsIAccessibleTable]);
+ const c1 = findAccessibleChildByID(docAcc, "c1");
+ is(t1.caption, c1, "t1 caption correct");
+ ok(!t1.summary, "t1 no summary");
+ const t2 = findAccessibleChildByID(docAcc, "t2", [nsIAccessibleTable]);
+ ok(!t2.caption, "t2 caption is null");
+ is(t2.summary, "s2", "t2 summary correct");
+ const t3 = findAccessibleChildByID(docAcc, "t3", [nsIAccessibleTable]);
+ const c3 = findAccessibleChildByID(docAcc, "c3");
+ is(t3.caption, c3, "t3 caption correct");
+ is(t3.summary, "s3", "t3 summary correct");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test table layout guess.
+ */
+addAccessibleTask(
+ `
+<table id="layout"><tr><td>a</td></tr></table>
+<table id="data"><tr><th>a</th></tr></table>
+<table id="mutate"><tr><td>a</td><td>b</td></tr></table>
+<div id="newTableContainer"></div>
+ `,
+ async function (browser, docAcc) {
+ const layout = findAccessibleChildByID(docAcc, "layout");
+ testAttrs(layout, { "layout-guess": "true" }, true);
+ const data = findAccessibleChildByID(docAcc, "data");
+ testAbsentAttrs(data, { "layout-guess": "true" });
+ const mutate = findAccessibleChildByID(docAcc, "mutate");
+ testAttrs(mutate, { "layout-guess": "true" }, true);
+
+ info("mutate: Adding 5 rows");
+ let reordered = waitForEvent(EVENT_REORDER, mutate);
+ await invokeContentTask(browser, [], () => {
+ const frag = content.document.createDocumentFragment();
+ for (let r = 0; r < 6; ++r) {
+ const tr = content.document.createElement("tr");
+ tr.innerHTML = "<td>a</td><td>b</td>";
+ frag.append(tr);
+ }
+ content.document.getElementById("mutate").tBodies[0].append(frag);
+ });
+ await reordered;
+ testAbsentAttrs(mutate, { "layout-guess": "true" });
+
+ info("mutate: Removing 5 rows");
+ reordered = waitForEvent(EVENT_REORDER, mutate);
+ await invokeContentTask(browser, [], () => {
+ // Pause refresh driver so all the children removals below will
+ // be collated into the same tick and only one 'reorder' event will
+ // be dispatched.
+ content.windowUtils.advanceTimeAndRefresh(100);
+
+ let tBody = content.document.getElementById("mutate").tBodies[0];
+ for (let r = 0; r < 6; ++r) {
+ tBody.lastChild.remove();
+ }
+
+ // Resume refresh driver
+ content.windowUtils.restoreNormalRefresh();
+ });
+ await reordered;
+ testAttrs(mutate, { "layout-guess": "true" }, true);
+
+ info("mutate: Adding new table");
+ let shown = waitForEvent(EVENT_SHOW, "newTable");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById(
+ "newTableContainer"
+ ).innerHTML = `<table id="newTable"><tr><th>a</th></tr></table>`;
+ });
+ let newTable = (await shown).accessible;
+ testAbsentAttrs(newTable, { "layout-guess": "true" });
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test table layout guess with border styling changes.
+ */
+addAccessibleTask(
+ `
+ <table id="layout"><tr><td id="cell">a</td><td>b</td></tr>
+ <tr><td>c</td><td>d</td></tr><tr><td>c</td><td>d</td></tr></table>
+ `,
+ async function (browser, docAcc) {
+ const layout = findAccessibleChildByID(docAcc, "layout");
+ testAttrs(layout, { "layout-guess": "true" }, true);
+ info("changing border style on table cell");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("cell").style.border = "1px solid black";
+ content.document.body.offsetTop; // Flush layout.
+ });
+ await untilCacheOk(() => {
+ // manually verify the attribute doesn't exist, since `testAbsentAttrs`
+ // has internal calls to ok() which fail if the cache hasn't yet updated
+ for (let prop of layout.attributes.enumerate()) {
+ if (prop.key == "layout-guess") {
+ return false;
+ }
+ }
+ return true;
+ }, "Table is a data table");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test ARIA grid.
+ */
+addAccessibleTask(
+ `
+<div id="grid" role="grid">
+ <div role="rowgroup">
+ <div role="row"><div id="a" role="columnheader">a</div><div id="b" role="columnheader">b</div></div>
+ </div>
+ <div tabindex="-1">
+ <div role="row"><div id="c" role="rowheader">c</div><div id="d" role="gridcell">d</div></div>
+ </div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const grid = findAccessibleChildByID(docAcc, "grid", [nsIAccessibleTable]);
+ is(grid.rowCount, 2, "grid rowCount correct");
+ is(grid.columnCount, 2, "grid columnCount correct");
+ testTableIndexes(grid, [
+ [0, 1],
+ [2, 3],
+ ]);
+ const cells = {};
+ for (const id of ["a", "b", "c", "d"]) {
+ cells[id] = findAccessibleChildByID(docAcc, id, [nsIAccessibleTableCell]);
+ }
+ is(cells.a.rowExtent, 1, "a rowExtent correct");
+ is(cells.a.columnExtent, 1, "a columnExtent correct");
+ testHeaderCells([
+ {
+ cell: cells.c,
+ rowHeaderCells: [],
+ columnHeaderCells: [cells.a],
+ },
+ {
+ cell: cells.d,
+ rowHeaderCells: [cells.c],
+ columnHeaderCells: [cells.b],
+ },
+ ]);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+function setNodeHidden(browser, id, hidden) {
+ return invokeContentTask(browser, [id, hidden], (cId, cHidden) => {
+ content.document.getElementById(cId).hidden = cHidden;
+ });
+}
+
+/**
+ * Test that the table is updated correctly when it is mutated.
+ */
+addAccessibleTask(
+ `
+<table id="table">
+ <tr id="r1"><td>a</td><td id="b">b</td></tr>
+ <tr id="r2" hidden><td>c</td><td>d</td></tr>
+</table>
+<div id="owner"></div>
+ `,
+ async function (browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+ is(table.rowCount, 1, "table rowCount correct");
+ is(table.columnCount, 2, "table columnCount correct");
+ testTableIndexes(table, [[0, 1]]);
+ info("Showing r2");
+ let reordered = waitForEvent(EVENT_REORDER, table);
+ await setNodeHidden(browser, "r2", false);
+ await reordered;
+ is(table.rowCount, 2, "table rowCount correct");
+ testTableIndexes(table, [
+ [0, 1],
+ [2, 3],
+ ]);
+ info("Hiding r2");
+ reordered = waitForEvent(EVENT_REORDER, table);
+ await setNodeHidden(browser, "r2", true);
+ await reordered;
+ is(table.rowCount, 1, "table rowCount correct");
+ testTableIndexes(table, [[0, 1]]);
+ info("Hiding b");
+ reordered = waitForEvent(EVENT_REORDER, "r1");
+ await setNodeHidden(browser, "b", true);
+ await reordered;
+ is(table.columnCount, 1, "table columnCount correct");
+ testTableIndexes(table, [[0]]);
+ info("Showing b");
+ reordered = waitForEvent(EVENT_REORDER, "r1");
+ await setNodeHidden(browser, "b", false);
+ await reordered;
+ is(table.columnCount, 2, "table columnCount correct");
+ info("Moving b out of table using aria-owns");
+ reordered = waitForEvent(EVENT_REORDER, "r1");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("owner").setAttribute("aria-owns", "b");
+ });
+ await reordered;
+ is(table.columnCount, 1, "table columnCount correct");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test the handling of ARIA tables with display: contents.
+ */
+addAccessibleTask(
+ `
+<div id="table" role="table" style="display: contents;">
+ <div role="row"><div role="cell">a</div></div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+ is(table.rowCount, 1, "table rowCount correct");
+ is(table.columnCount, 1, "table columnCount correct");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test a broken ARIA table with an invalid cell.
+ */
+addAccessibleTask(
+ `
+<div id="table" role="table">
+ <div role="main">
+ <div role="row">
+ <div id="cell" role="cell">a</div>
+ </div>
+ </div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+ is(table.rowCount, 0, "table rowCount correct");
+ is(table.columnCount, 0, "table columnCount correct");
+ const cell = findAccessibleChildByID(docAcc, "cell");
+ let queryOk = false;
+ try {
+ cell.QueryInterface(nsIAccessibleTableCell);
+ queryOk = true;
+ } catch (e) {}
+ ok(!queryOk, "Got nsIAccessibleTableCell on an invalid cell");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test that building the cache for a malformed table with an iframe inside a
+ * row doesn't crash (bug 1800780).
+ */
+addAccessibleTask(
+ `<table><tr id="tr"></tr></table>`,
+ async function (browser, docAcc) {
+ let reordered = waitForEvent(EVENT_REORDER, "tr");
+ await invokeContentTask(browser, [], () => {
+ const iframe = content.document.createElement("iframe");
+ content.document.getElementById("tr").append(iframe);
+ });
+ await reordered;
+ },
+ { topLevel: true }
+);
+
+/**
+ * Verify that we don't crash for authoring error like <table role="gridcell">.
+ */
+addAccessibleTask(
+ `<table id="table" role="gridcell">`,
+ async function (browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table");
+ ok(table, "Retrieved table Accessible");
+ },
+ { chrome: true, topLevel: true }
+);
+
+/**
+ * Verify that we don't crash for authoring error like <tr role="grid">.
+ */
+addAccessibleTask(
+ `
+<table id="table">
+ <tr><th>a</th></tr>
+ <tr role="grid"><td id="b">b</td></tr>
+</table>
+ `,
+ async function (browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+ is(table.rowCount, 1, "table rowCount correct");
+ is(table.columnCount, 1, "table columnCount correct");
+ const b = findAccessibleChildByID(docAcc, "b");
+ let queryOk = false;
+ try {
+ b.QueryInterface(nsIAccessibleTableCell);
+ queryOk = true;
+ } catch (e) {}
+ ok(!queryOk, "No nsIAccessibleTableCell on invalid cell b");
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_text_bounds.js b/accessible/tests/browser/e10s/browser_caching_text_bounds.js
new file mode 100644
index 0000000000..2d2a857c8f
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_text_bounds.js
@@ -0,0 +1,696 @@
+/* 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";
+requestLongerTimeout(3);
+
+/* import-globals-from ../../mochitest/layout.js */
+loadScripts({ name: "layout.js", dir: MOCHITESTS_DIR });
+
+// Note that testTextNode, testChar and testTextRange currently don't handle
+// white space in the code that doesn't get rendered on screen. To work around
+// this, ensure that containers you want to test are all on a single line in the
+// test snippet.
+
+async function testTextNode(accDoc, browser, id) {
+ await testTextRange(accDoc, browser, id, 0, -1);
+}
+
+async function testChar(accDoc, browser, id, idx) {
+ await testTextRange(accDoc, browser, id, idx, idx + 1);
+}
+
+async function testTextRange(accDoc, browser, id, start, end) {
+ const r = await invokeContentTask(
+ browser,
+ [id, start, end],
+ (_id, _start, _end) => {
+ const htNode = content.document.getElementById(_id);
+ let [eX, eY, eW, eH] = [
+ Number.MAX_SAFE_INTEGER,
+ Number.MAX_SAFE_INTEGER,
+ 0,
+ 0,
+ ];
+ let traversed = 0;
+ let localStart = _start;
+ let endTraversal = false;
+ for (let element of htNode.childNodes) {
+ // ignore whitespace, but not embedded elements
+ let isEmbeddedElement = false;
+ if (element.length == undefined) {
+ let potentialTextContainer = element;
+ while (
+ potentialTextContainer &&
+ potentialTextContainer.length == undefined
+ ) {
+ potentialTextContainer = element.firstChild;
+ }
+ if (potentialTextContainer && potentialTextContainer.length) {
+ // If we can reach some text from this container, use that as part
+ // of our range. This is important when testing with intervening inline
+ // elements. ie. <pre><code>ab%0acd
+ element = potentialTextContainer;
+ } else if (element.firstChild) {
+ isEmbeddedElement = true;
+ } else {
+ continue;
+ }
+ }
+ if (element.length + traversed < _start) {
+ // If our start index is not within this
+ // node, keep looking.
+ traversed += element.length;
+ localStart -= element.length;
+ continue;
+ }
+
+ let rect;
+ if (isEmbeddedElement) {
+ rect = element.getBoundingClientRect();
+ } else {
+ const range = content.document.createRange();
+ range.setStart(element, localStart);
+
+ if (_end != -1 && _end - traversed <= element.length) {
+ // If the current node contains
+ // our end index, stop here.
+ endTraversal = true;
+ range.setEnd(element, _end - traversed);
+ } else {
+ range.setEnd(element, element.length);
+ }
+
+ rect = range.getBoundingClientRect();
+ }
+
+ const oldX = eX == Number.MAX_SAFE_INTEGER ? 0 : eX;
+ const oldY = eY == Number.MAX_SAFE_INTEGER ? 0 : eY;
+ eX = Math.min(eX, rect.x);
+ eY = Math.min(eY, rect.y);
+ eW = Math.abs(Math.max(oldX + eW, rect.x + rect.width) - eX);
+ eH = Math.abs(Math.max(oldY + eH, rect.y + rect.height) - eY);
+
+ if (endTraversal) {
+ break;
+ }
+ localStart = 0;
+ traversed += element.length;
+ }
+ return [Math.round(eX), Math.round(eY), Math.round(eW), Math.round(eH)];
+ }
+ );
+ let hyperTextNode = findAccessibleChildByID(accDoc, id);
+
+ // Add in the doc's screen coords because getBoundingClientRect
+ // is relative to the document, not the screen. This assumes the doc's
+ // screen coords are correct. We use getBoundsInCSSPixels to avoid factoring
+ // in the DPR ourselves.
+ let x = {};
+ let y = {};
+ let w = {};
+ let h = {};
+ accDoc.getBoundsInCSSPixels(x, y, w, h);
+ r[0] += x.value;
+ r[1] += y.value;
+ if (end != -1 && end - start == 1) {
+ // If we're only testing a character, use this function because it calls
+ // CharBounds() directly instead of TextBounds().
+ testTextPos(hyperTextNode, start, [r[0], r[1]], COORDTYPE_SCREEN_RELATIVE);
+ } else {
+ testTextBounds(hyperTextNode, start, end, r, COORDTYPE_SCREEN_RELATIVE);
+ }
+}
+
+/**
+ * Test the text range boundary for simple LtR text
+ */
+addAccessibleTask(
+ `
+ <p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p>
+ <p id='p2' style='font-family: monospace;'>ل</p>
+ <p id='p3' dir='ltr' style='font-family: monospace;'>Привіт Світ</p>
+ <pre id='p4' style='font-family: monospace;'>a%0abcdef</pre>
+ `,
+ async function (browser, accDoc) {
+ info("Testing simple LtR text");
+ await testTextNode(accDoc, browser, "p1");
+ await testTextNode(accDoc, browser, "p2");
+ await testTextNode(accDoc, browser, "p3");
+ await testTextNode(accDoc, browser, "p4");
+ },
+ {
+ iframe: true,
+ }
+);
+
+/**
+ * Test the partial text range boundary for LtR text
+ */
+addAccessibleTask(
+ `
+ <p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p>
+ <p id='p2' dir='ltr' style='font-family: monospace;'>Привіт Світ</p>
+ `,
+ async function (browser, accDoc) {
+ info("Testing partial ranges in LtR text");
+ await testTextRange(accDoc, browser, "p1", 0, 4);
+ await testTextRange(accDoc, browser, "p1", 2, 8);
+ await testTextRange(accDoc, browser, "p1", 12, 17);
+ await testTextRange(accDoc, browser, "p2", 0, 4);
+ await testTextRange(accDoc, browser, "p2", 2, 8);
+ await testTextRange(accDoc, browser, "p2", 6, 11);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test the text boundary for multiline LtR text
+ */
+addAccessibleTask(
+ `
+ <p id='p4' dir='ltr' style='font-family: monospace;'>Привіт Світ<br>Привіт Світ</p>
+ <p id='p5' dir='ltr' style='font-family: monospace;'>Привіт Світ<br> Я ще трохи тексту в другому рядку</p>
+ <p id='p6' style='font-family: monospace;'>hello world I'm on line one<br> and I'm a separate line two with slightly more text</p>
+ <p id='p7' style='font-family: monospace;'>hello world<br>hello world</p>
+ `,
+ async function (browser, accDoc) {
+ info("Testing multiline LtR text");
+ await testTextNode(accDoc, browser, "p4");
+ await testTextNode(accDoc, browser, "p5");
+ // await testTextNode(accDoc, browser, "p6"); // w/o cache, fails width (a 259, e 250), w/ cache wrong w, h in iframe (line wrapping)
+ await testTextNode(accDoc, browser, "p7");
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test the text boundary for simple RtL text
+ */
+addAccessibleTask(
+ `
+ <p id='p1' dir='rtl' style='font-family: monospace;'>Tilimilitryamdiya</p>
+ <p id='p2' dir='rtl' style='font-family: monospace;'>ل</p>
+ <p id='p3' dir='rtl' style='font-family: monospace;'>لل لللل لل</p>
+ <pre id='p4' dir='rtl' style='font-family: monospace;'>a%0abcdef</pre>
+ `,
+ async function (browser, accDoc) {
+ info("Testing simple RtL text");
+ await testTextNode(accDoc, browser, "p1");
+ await testTextNode(accDoc, browser, "p2");
+ await testTextNode(accDoc, browser, "p3");
+ await testTextNode(accDoc, browser, "p4");
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test the text boundary for multiline RtL text
+ */
+addAccessibleTask(
+ `
+ <p id='p4' dir='rtl' style='font-family: monospace;'>لل لللل لل<br>لل لللل لل</p>
+ <p id='p5' dir='rtl' style='font-family: monospace;'>لل لللل لل<br> لل لل لل لل ل لل لل لل</p>
+ <p id='p6' dir='rtl' style='font-family: monospace;'>hello world I'm on line one<br> and I'm a separate line two with slightly more text</p>
+ <p id='p7' dir='rtl' style='font-family: monospace;'>hello world<br>hello world</p>
+ `,
+ async function (browser, accDoc) {
+ info("Testing multiline RtL text");
+ await testTextNode(accDoc, browser, "p4");
+ //await testTextNode(accDoc, browser, "p5"); // w/ cache fails x, w - off by one char
+ // await testTextNode(accDoc, browser, "p6"); // w/o cache, fails width (a 259, e 250), w/ cache fails w, h in iframe (line wrapping)
+ await testTextNode(accDoc, browser, "p7");
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test the partial text range boundary for RtL text
+ */
+addAccessibleTask(
+ `
+ <p id='p1' dir='rtl' style='font-family: monospace;'>Tilimilitryamdiya</p>
+ <p id='p2' dir='rtl' style='font-family: monospace;'>لل لللل لل</p>
+ `,
+ async function (browser, accDoc) {
+ info("Testing partial ranges in RtL text");
+ await testTextRange(accDoc, browser, "p1", 0, 4);
+ await testTextRange(accDoc, browser, "p1", 2, 8);
+ await testTextRange(accDoc, browser, "p1", 12, 17);
+ await testTextRange(accDoc, browser, "p2", 0, 4);
+ await testTextRange(accDoc, browser, "p2", 2, 8);
+ await testTextRange(accDoc, browser, "p2", 6, 10);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test simple vertical text in rl and lr layouts
+ */
+addAccessibleTask(
+ `
+ <div style="writing-mode: vertical-rl;">
+ <p id='p1'>你好世界</p>
+ <p id='p2'>hello world</p>
+ <br>
+ <p id='p3'>こんにちは世界</p>
+ </div>
+ <div style="writing-mode: vertical-lr;">
+ <p id='p4'>你好世界</p>
+ <p id='p5'>hello world</p>
+ <br>
+ <p id='p6'>こんにちは世界</p>
+ </div>
+ `,
+ async function (browser, accDoc) {
+ info("Testing vertical-rl");
+ await testTextNode(accDoc, browser, "p1");
+ await testTextNode(accDoc, browser, "p2");
+ await testTextNode(accDoc, browser, "p3");
+ info("Testing vertical-lr");
+ await testTextNode(accDoc, browser, "p4");
+ await testTextNode(accDoc, browser, "p5");
+ await testTextNode(accDoc, browser, "p6");
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test multiline vertical-rl text
+ */
+addAccessibleTask(
+ `
+ <p id='p1' style='writing-mode: vertical-rl;'>你好世界<br>你好世界</p>
+ <p id='p2' style='writing-mode: vertical-rl;'>hello world<br>hello world</p>
+ <br>
+ <p id='p3' style='writing-mode: vertical-rl;'>你好世界<br> 你好世界 你好世界</p>
+ <p id='p4' style='writing-mode: vertical-rl;'>hello world<br> hello world hello world</p>
+ `,
+ async function (browser, accDoc) {
+ info("Testing vertical-rl multiline");
+ await testTextNode(accDoc, browser, "p1");
+ await testTextNode(accDoc, browser, "p2");
+ await testTextNode(accDoc, browser, "p3");
+ // await testTextNode(accDoc, browser, "p4"); // off by 4 with caching, iframe
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test text with embedded chars
+ */
+addAccessibleTask(
+ `<p id='p1' style='font-family: monospace;'>hello <a href="google.com">world</a></p>
+ <p id='p2' style='font-family: monospace;'>hello<br><a href="google.com">world</a></p>
+ <div id='d3'><p></p>hello world</div>
+ <div id='d4'>hello world<p></p></div>
+ <div id='d5'>oh<p></p>hello world</div>`,
+ async function (browser, accDoc) {
+ info("Testing embedded chars");
+ await testTextNode(accDoc, browser, "p1");
+ await testTextNode(accDoc, browser, "p2");
+ await testTextNode(accDoc, browser, "d3");
+ await testTextNode(accDoc, browser, "d4");
+ await testTextNode(accDoc, browser, "d5");
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test bounds after text mutations.
+ */
+addAccessibleTask(
+ `<p id="p">a</p>`,
+ async function (browser, docAcc) {
+ await testTextNode(docAcc, browser, "p");
+ const p = findAccessibleChildByID(docAcc, "p");
+ info("Appending a character to text leaf");
+ let textInserted = waitForEvent(EVENT_TEXT_INSERTED, p);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("p").firstChild.data = "ab";
+ });
+ await textInserted;
+ await testTextNode(docAcc, browser, "p");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test character bounds on the insertion point at the end of a text box.
+ */
+addAccessibleTask(
+ `<input id="input" value="a">`,
+ async function (browser, docAcc) {
+ const input = findAccessibleChildByID(docAcc, "input");
+ testTextPos(input, 1, [0, 0], COORDTYPE_SCREEN_RELATIVE);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test character bounds after non-br line break.
+ */
+addAccessibleTask(
+ `
+ <style>
+ @font-face {
+ font-family: Ahem;
+ src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
+ }
+ pre {
+ font: 20px/20px Ahem;
+ }
+ </style>
+ <pre id="t">XX
+XXX</pre>`,
+ async function (browser, docAcc) {
+ await testChar(docAcc, browser, "t", 3);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test character bounds in a pre with padding.
+ */
+addAccessibleTask(
+ `
+ <style>
+ @font-face {
+ font-family: Ahem;
+ src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
+ }
+ pre {
+ font: 20px/20px Ahem;
+ padding: 20px;
+ }
+ </style>
+ <pre id="t">XX
+XXX</pre>`,
+ async function (browser, docAcc) {
+ await testTextNode(docAcc, browser, "t");
+ await testChar(docAcc, browser, "t", 3);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test text bounds with an invalid end offset.
+ */
+addAccessibleTask(
+ `<p id="p">a</p>`,
+ async function (browser, docAcc) {
+ const p = findAccessibleChildByID(docAcc, "p");
+ testTextBounds(p, 0, 2, [0, 0, 0, 0], COORDTYPE_SCREEN_RELATIVE);
+ },
+ { chrome: true, topLevel: !true }
+);
+
+/**
+ * Test character bounds in an intervening inline element with non-br line breaks
+ */
+addAccessibleTask(
+ `
+ <style>
+ @font-face {
+ font-family: Ahem;
+ src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
+ }
+ pre {
+ font: 20px/20px Ahem;
+ }
+ </style>
+ <pre id="t"><code>XX
+XXX
+XX
+X</pre>`,
+ async function (browser, docAcc) {
+ await testChar(docAcc, browser, "t", 0);
+ await testChar(docAcc, browser, "t", 3);
+ await testChar(docAcc, browser, "t", 7);
+ await testChar(docAcc, browser, "t", 10);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test character bounds in an intervening inline element with margins
+ * and with non-br line breaks
+ */
+addAccessibleTask(
+ `
+ <style>
+ @font-face {
+ font-family: Ahem;
+ src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
+ }
+ </style>
+ <div>hello<pre id="t" style="margin-left:100px;margin-top:30px;background-color:blue;">XX
+XXX
+XX
+X</pre></div>`,
+ async function (browser, docAcc) {
+ await testChar(docAcc, browser, "t", 0);
+ await testChar(docAcc, browser, "t", 3);
+ await testChar(docAcc, browser, "t", 7);
+ await testChar(docAcc, browser, "t", 10);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test text bounds in a textarea after scrolling.
+ */
+addAccessibleTask(
+ `
+<textarea id="textarea" rows="1">a
+b
+c</textarea>
+ `,
+ async function (browser, docAcc) {
+ // We can't use testChar because Range.getBoundingClientRect isn't supported
+ // inside textareas.
+ const textarea = findAccessibleChildByID(docAcc, "textarea");
+ textarea.QueryInterface(nsIAccessibleText);
+ const oldY = {};
+ textarea.getCharacterExtents(
+ 4,
+ {},
+ oldY,
+ {},
+ {},
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ info("Moving textarea caret to c");
+ await invokeContentTask(browser, [], () => {
+ const textareaDom = content.document.getElementById("textarea");
+ textareaDom.focus();
+ textareaDom.selectionStart = 4;
+ });
+ await waitForContentPaint(browser);
+ const newY = {};
+ textarea.getCharacterExtents(
+ 4,
+ {},
+ newY,
+ {},
+ {},
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ ok(newY.value < oldY.value, "y coordinate smaller after scrolling down");
+ },
+ { chrome: true, topLevel: true, iframe: !true }
+);
+
+/**
+ * Test magic offsets with GetCharacter/RangeExtents.
+ */
+addAccessibleTask(
+ `<input id="input" value="abc">`,
+ async function (browser, docAcc) {
+ const input = findAccessibleChildByID(docAcc, "input", [nsIAccessibleText]);
+ info("Setting caret and focusing input");
+ let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
+ await invokeContentTask(browser, [], () => {
+ const inputDom = content.document.getElementById("input");
+ inputDom.selectionStart = inputDom.selectionEnd = 1;
+ inputDom.focus();
+ });
+ await caretMoved;
+ is(input.caretOffset, 1, "input caretOffset is 1");
+ let expectedX = {};
+ let expectedY = {};
+ let expectedW = {};
+ let expectedH = {};
+ let magicX = {};
+ let magicY = {};
+ let magicW = {};
+ let magicH = {};
+ input.getCharacterExtents(
+ 1,
+ expectedX,
+ expectedY,
+ expectedW,
+ expectedH,
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ input.getCharacterExtents(
+ nsIAccessibleText.TEXT_OFFSET_CARET,
+ magicX,
+ magicY,
+ magicW,
+ magicH,
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ Assert.deepEqual(
+ [magicX.value, magicY.value, magicW.value, magicH.value],
+ [expectedX.value, expectedY.value, expectedW.value, expectedH.value],
+ "GetCharacterExtents correct with TEXT_OFFSET_CARET"
+ );
+ input.getRangeExtents(
+ 1,
+ 3,
+ expectedX,
+ expectedY,
+ expectedW,
+ expectedH,
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ input.getRangeExtents(
+ nsIAccessibleText.TEXT_OFFSET_CARET,
+ nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT,
+ magicX,
+ magicY,
+ magicW,
+ magicH,
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ Assert.deepEqual(
+ [magicX.value, magicY.value, magicW.value, magicH.value],
+ [expectedX.value, expectedY.value, expectedW.value, expectedH.value],
+ "GetRangeExtents correct with TEXT_OFFSET_CARET/END_OF_TEXT"
+ );
+ },
+ { chrome: true, topLevel: true, remoteIframe: !true }
+);
+
+/**
+ * Test wrapped text and pre-formatted text beginning with an empty line.
+ */
+addAccessibleTask(
+ `
+<style>
+ #wrappedText {
+ width: 3ch;
+ font-family: monospace;
+ }
+</style>
+<p id="wrappedText"><a href="https://example.com/">a</a>b cd</p>
+<p id="emptyFirstLine" style="white-space: pre-line;">
+foo</p>
+ `,
+ async function (browser, docAcc) {
+ await testChar(docAcc, browser, "wrappedText", 0);
+ await testChar(docAcc, browser, "wrappedText", 1);
+ await testChar(docAcc, browser, "wrappedText", 2);
+ await testChar(docAcc, browser, "wrappedText", 3);
+ await testChar(docAcc, browser, "wrappedText", 4);
+
+ // We can't use testChar for emptyFirstLine because it doesn't handle white
+ // space properly. Instead, verify that the first character is at the top
+ // left of the text leaf.
+ const emptyFirstLine = findAccessibleChildByID(docAcc, "emptyFirstLine", [
+ nsIAccessibleText,
+ ]);
+ const emptyFirstLineLeaf = emptyFirstLine.firstChild;
+ const leafX = {};
+ const leafY = {};
+ emptyFirstLineLeaf.getBounds(leafX, leafY, {}, {});
+ testTextPos(
+ emptyFirstLine,
+ 0,
+ [leafX.value, leafY.value],
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ },
+ { chrome: true, topLevel: true, remoteIframe: !true }
+);
+
+/**
+ * Test character bounds in an intervening inline element with non-br line breaks
+ */
+addAccessibleTask(
+ `
+ <style>
+ @font-face {
+ font-family: Ahem;
+ src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
+ }
+ pre {
+ font: 20px/20px Ahem;
+ }
+ </style>
+ <pre><code id="t" role="group">XX
+XXX
+XX
+X</pre>`,
+ async function (browser, docAcc) {
+ await testChar(docAcc, browser, "t", 0);
+ await testChar(docAcc, browser, "t", 3);
+ await testChar(docAcc, browser, "t", 7);
+ await testChar(docAcc, browser, "t", 10);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_uniqueid.js b/accessible/tests/browser/e10s/browser_caching_uniqueid.js
new file mode 100644
index 0000000000..92eb2fe998
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_uniqueid.js
@@ -0,0 +1,30 @@
+/* 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";
+
+/**
+ * Test UniqueID property.
+ */
+addAccessibleTask(
+ '<div id="div"></div>',
+ async function (browser, accDoc) {
+ const div = findAccessibleChildByID(accDoc, "div");
+ const accUniqueID = await invokeContentTask(browser, [], () => {
+ const accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+
+ return accService.getAccessibleFor(content.document.getElementById("div"))
+ .uniqueID;
+ });
+
+ is(
+ accUniqueID,
+ div.uniqueID,
+ "Both proxy and the accessible return correct unique ID."
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_value.js b/accessible/tests/browser/e10s/browser_caching_value.js
new file mode 100644
index 0000000000..0e3cda93af
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_value.js
@@ -0,0 +1,415 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/states.js */
+/* import-globals-from ../../mochitest/value.js */
+loadScripts(
+ { name: "states.js", dir: MOCHITESTS_DIR },
+ { name: "value.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * id {String} given accessible DOMNode ID
+ * expected {String} expected value for a given accessible
+ * action {?AsyncFunction} an optional action that awaits a value change
+ * attrs {?Array} an optional list of attributes to update
+ * waitFor {?Number} an optional value change event to wait for
+ * }
+ */
+const valueTests = [
+ {
+ desc: "Initially value is set to 1st element of select",
+ id: "select",
+ expected: "1st",
+ },
+ {
+ desc: "Value should update to 3rd when 3 is pressed",
+ id: "select",
+ async action(browser) {
+ await invokeFocus(browser, "select");
+ await invokeContentTask(browser, [], () => {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.synthesizeKey("3", {}, content);
+ });
+ },
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: "3rd",
+ },
+ {
+ desc: "Initially value is set to @aria-valuenow for slider",
+ id: "slider",
+ expected: ["5", 5, 0, 7, 0],
+ },
+ {
+ desc: "Value should change when currentValue is called",
+ id: "slider",
+ async action(browser, acc) {
+ acc.QueryInterface(nsIAccessibleValue);
+ acc.currentValue = 4;
+ },
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: ["4", 4, 0, 7, 0],
+ },
+ {
+ desc: "Value should change when @aria-valuenow is updated",
+ id: "slider",
+ attrs: [
+ {
+ attr: "aria-valuenow",
+ value: "6",
+ },
+ ],
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: ["6", 6, 0, 7, 0],
+ },
+ {
+ desc: "Value should change when @aria-valuetext is set",
+ id: "slider",
+ attrs: [
+ {
+ attr: "aria-valuetext",
+ value: "plain",
+ },
+ ],
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: ["plain", 6, 0, 7, 0],
+ },
+ {
+ desc: "Value should change when @aria-valuetext is updated",
+ id: "slider",
+ attrs: [
+ {
+ attr: "aria-valuetext",
+ value: "hey!",
+ },
+ ],
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: ["hey!", 6, 0, 7, 0],
+ },
+ {
+ desc: "Value should change to @aria-valuetext when @aria-valuenow is removed",
+ id: "slider",
+ attrs: [
+ {
+ attr: "aria-valuenow",
+ },
+ ],
+ expected: ["hey!", 3.5, 0, 7, 0],
+ },
+ {
+ desc: "Initially value is not set for combobox",
+ id: "combobox",
+ expected: "",
+ },
+ {
+ desc: "Value should change when @value attribute is updated",
+ id: "combobox",
+ attrs: [
+ {
+ attr: "value",
+ value: "hello",
+ },
+ ],
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: "hello",
+ },
+ {
+ desc: "Initially value corresponds to @value attribute for progress",
+ id: "progress",
+ expected: "22%",
+ },
+ {
+ desc: "Value should change when @value attribute is updated",
+ id: "progress",
+ attrs: [
+ {
+ attr: "value",
+ value: "50",
+ },
+ ],
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: "50%",
+ },
+ {
+ desc: "Setting currentValue on a progress accessible should fail",
+ id: "progress",
+ async action(browser, acc) {
+ acc.QueryInterface(nsIAccessibleValue);
+ try {
+ acc.currentValue = 25;
+ ok(false, "Setting currValue on progress element should fail");
+ } catch (e) {}
+ },
+ expected: "50%",
+ },
+ {
+ desc: "Initially value corresponds to @value attribute for range",
+ id: "range",
+ expected: "6",
+ },
+ {
+ desc: "Value should change when slider is moved",
+ id: "range",
+ async action(browser) {
+ await invokeFocus(browser, "range");
+ await invokeContentTask(browser, [], () => {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.synthesizeKey("VK_LEFT", {}, content);
+ });
+ },
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: "5",
+ },
+ {
+ desc: "Value should change when currentValue is called",
+ id: "range",
+ async action(browser, acc) {
+ acc.QueryInterface(nsIAccessibleValue);
+ acc.currentValue = 4;
+ },
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: "4",
+ },
+ {
+ desc: "Initially textbox value is text subtree",
+ id: "textbox",
+ expected: "Some rich text",
+ },
+ {
+ desc: "Textbox value changes when subtree changes",
+ id: "textbox",
+ async action(browser) {
+ await invokeContentTask(browser, [], () => {
+ let boldText = content.document.createElement("strong");
+ boldText.textContent = " bold";
+ content.document.getElementById("textbox").appendChild(boldText);
+ });
+ },
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: "Some rich text bold",
+ },
+];
+
+/**
+ * Test caching of accessible object values
+ */
+addAccessibleTask(
+ `
+ <div id="slider" role="slider" aria-valuenow="5"
+ aria-valuemin="0" aria-valuemax="7">slider</div>
+ <select id="select">
+ <option>1st</option>
+ <option>2nd</option>
+ <option>3rd</option>
+ </select>
+ <input id="combobox" role="combobox" aria-autocomplete="inline">
+ <progress id="progress" value="22" max="100"></progress>
+ <input type="range" id="range" min="0" max="10" value="6">
+ <div contenteditable="yes" role="textbox" id="textbox">Some <a href="#">rich</a> text</div>`,
+ async function (browser, accDoc) {
+ for (let { desc, id, action, attrs, expected, waitFor } of valueTests) {
+ info(desc);
+ let acc = findAccessibleChildByID(accDoc, id);
+ let onUpdate;
+
+ if (waitFor) {
+ onUpdate = waitForEvent(waitFor, id);
+ }
+
+ if (action) {
+ await action(browser, acc);
+ } else if (attrs) {
+ for (let { attr, value } of attrs) {
+ await invokeSetAttribute(browser, id, attr, value);
+ }
+ }
+
+ await onUpdate;
+ if (Array.isArray(expected)) {
+ acc.QueryInterface(nsIAccessibleValue);
+ testValue(acc, ...expected);
+ } else {
+ is(acc.value, expected, `Correct value for ${prettyName(acc)}`);
+ }
+ }
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of link URL values.
+ */
+addAccessibleTask(
+ `<a id="link" href="https://example.com/">Test</a>`,
+ async function (browser, docAcc) {
+ const link = findAccessibleChildByID(docAcc, "link");
+ is(link.value, "https://example.com/", "link initial value correct");
+ const textLeaf = link.firstChild;
+ is(textLeaf.value, "https://example.com/", "link initial value correct");
+
+ info("Changing link href");
+ await invokeSetAttribute(browser, "link", "href", "https://example.net/");
+ await untilCacheIs(
+ () => link.value,
+ "https://example.net/",
+ "link value correct after change"
+ );
+
+ info("Removing link href");
+ await invokeSetAttribute(browser, "link", "href");
+ await untilCacheIs(() => link.value, "", "link value empty after removal");
+
+ info("Setting link href");
+ await invokeSetAttribute(browser, "link", "href", "https://example.com/");
+ await untilCacheIs(
+ () => link.value,
+ "https://example.com/",
+ "link value correct after change"
+ );
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of active state for select options - see bug 1788143.
+ */
+addAccessibleTask(
+ `
+ <select id="select">
+ <option id="first_option">First</option>
+ <option id="second_option">Second</option>
+ </select>`,
+ async function (browser, docAcc) {
+ const select = findAccessibleChildByID(docAcc, "select");
+ is(select.value, "First", "Select initial value correct");
+
+ // Focus the combo box.
+ await invokeFocus(browser, "select");
+
+ // Select the second option (drop-down collapsed).
+ let p = waitForEvents({
+ expected: [
+ [EVENT_SELECTION, "second_option"],
+ [EVENT_TEXT_VALUE_CHANGE, "select"],
+ ],
+ unexpected: [
+ stateChangeEventArgs("second_option", EXT_STATE_ACTIVE, true, true),
+ stateChangeEventArgs("first_option", EXT_STATE_ACTIVE, false, true),
+ ],
+ });
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("select").selectedIndex = 1;
+ });
+ await p;
+
+ is(select.value, "Second", "Select value correct after changing option");
+
+ // Expand the combobox dropdown.
+ p = waitForEvent(EVENT_STATE_CHANGE, "ContentSelectDropdown");
+ EventUtils.synthesizeKey("VK_SPACE");
+ await p;
+
+ p = waitForEvents({
+ expected: [
+ [EVENT_SELECTION, "first_option"],
+ [EVENT_TEXT_VALUE_CHANGE, "select"],
+ [EVENT_HIDE, "ContentSelectDropdown"],
+ ],
+ unexpected: [
+ stateChangeEventArgs("first_option", EXT_STATE_ACTIVE, true, true),
+ stateChangeEventArgs("second_option", EXT_STATE_ACTIVE, false, true),
+ ],
+ });
+
+ // Press the up arrow to select the first option (drop-down expanded).
+ // Then, press Enter to confirm the selection and close the dropdown.
+ // We do both of these together to unify testing across platforms, since
+ // events are not entirely consistent on Windows vs. Linux + macOS.
+ EventUtils.synthesizeKey("VK_UP");
+ EventUtils.synthesizeKey("VK_RETURN");
+ await p;
+
+ is(
+ select.value,
+ "First",
+ "Select value correct after changing option back"
+ );
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test combobox values for non-editable comboboxes.
+ */
+addAccessibleTask(
+ `
+ <div id="combo-div-1" role="combobox">value</div>
+ <div id="combo-div-2" role="combobox">
+ <div role="listbox">
+ <div role="option">value</div>
+ </div>
+ </div>
+ <div id="combo-div-3" role="combobox">
+ <div role="group">value</div>
+ </div>
+ <div id="combo-div-4" role="combobox">foo
+ <div role="listbox">
+ <div role="option">bar</div>
+ </div>
+ </div>
+
+ <input id="combo-input-1" role="combobox" value="value" disabled></input>
+ <input id="combo-input-2" role="combobox" value="value" disabled>testing</input>
+
+ <div id="combo-div-selected" role="combobox">
+ <div role="listbox">
+ <div aria-selected="true" role="option">value</div>
+ </div>
+ </div>
+`,
+ async function (browser, docAcc) {
+ const comboDiv1 = findAccessibleChildByID(docAcc, "combo-div-1");
+ const comboDiv2 = findAccessibleChildByID(docAcc, "combo-div-2");
+ const comboDiv3 = findAccessibleChildByID(docAcc, "combo-div-3");
+ const comboDiv4 = findAccessibleChildByID(docAcc, "combo-div-4");
+ const comboInput1 = findAccessibleChildByID(docAcc, "combo-input-1");
+ const comboInput2 = findAccessibleChildByID(docAcc, "combo-input-2");
+ const comboDivSelected = findAccessibleChildByID(
+ docAcc,
+ "combo-div-selected"
+ );
+
+ // Text as a descendant of the combobox: included in the value.
+ is(comboDiv1.value, "value", "Combobox value correct");
+
+ // Text as the descendant of a listbox: excluded from the value.
+ is(comboDiv2.value, "", "Combobox value correct");
+
+ // Text as the descendant of some other role that includes text in name computation.
+ // Here, the group role contains the text node with "value" in it.
+ is(comboDiv3.value, "value", "Combobox value correct");
+
+ // Some descendant text included, but text descendant of a listbox excluded.
+ is(comboDiv4.value, "foo", "Combobox value correct");
+
+ // Combobox inputs with explicit value report that value.
+ is(comboInput1.value, "value", "Combobox value correct");
+ is(comboInput2.value, "value", "Combobox value correct");
+
+ // Combobox role with aria-selected reports correct value.
+ is(comboDivSelected.value, "value", "Combobox value correct");
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_announcement.js b/accessible/tests/browser/e10s/browser_events_announcement.js
new file mode 100644
index 0000000000..046a7706e3
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_announcement.js
@@ -0,0 +1,30 @@
+/* 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";
+
+addAccessibleTask(
+ `<p id="p">abc</p>`,
+ async function (browser, accDoc) {
+ let acc = findAccessibleChildByID(accDoc, "p");
+ let onAnnounce = waitForEvent(EVENT_ANNOUNCEMENT, acc);
+ acc.announce("please", nsIAccessibleAnnouncementEvent.POLITE);
+ let evt = await onAnnounce;
+ evt.QueryInterface(nsIAccessibleAnnouncementEvent);
+ is(evt.announcement, "please", "announcement matches.");
+ is(evt.priority, nsIAccessibleAnnouncementEvent.POLITE, "priority matches");
+
+ onAnnounce = waitForEvent(EVENT_ANNOUNCEMENT, acc);
+ acc.announce("do it", nsIAccessibleAnnouncementEvent.ASSERTIVE);
+ evt = await onAnnounce;
+ evt.QueryInterface(nsIAccessibleAnnouncementEvent);
+ is(evt.announcement, "do it", "announcement matches.");
+ is(
+ evt.priority,
+ nsIAccessibleAnnouncementEvent.ASSERTIVE,
+ "priority matches"
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_caretmove.js b/accessible/tests/browser/e10s/browser_events_caretmove.js
new file mode 100644
index 0000000000..dff6586bf3
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_caretmove.js
@@ -0,0 +1,22 @@
+/* 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";
+
+/**
+ * Test caret move event and its interface:
+ * - caretOffset
+ */
+addAccessibleTask(
+ '<input id="textbox" value="hello"/>',
+ async function (browser) {
+ let onCaretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, "textbox");
+ await invokeFocus(browser, "textbox");
+ let event = await onCaretMoved;
+
+ let caretMovedEvent = event.QueryInterface(nsIAccessibleCaretMoveEvent);
+ is(caretMovedEvent.caretOffset, 5, "Correct caret offset.");
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_hide.js b/accessible/tests/browser/e10s/browser_events_hide.js
new file mode 100644
index 0000000000..77bd70c0f6
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_hide.js
@@ -0,0 +1,44 @@
+/* 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";
+
+/**
+ * Test hide event and its interface:
+ * - targetParent
+ * - targetNextSibling
+ * - targetPrevSibling
+ */
+addAccessibleTask(
+ `
+ <div id="parent">
+ <div id="previous"></div>
+ <div id="to-hide"></div>
+ <div id="next"></div>
+ </div>`,
+ async function (browser, accDoc) {
+ let acc = findAccessibleChildByID(accDoc, "to-hide");
+ let onHide = waitForEvent(EVENT_HIDE, acc);
+ await invokeSetStyle(browser, "to-hide", "visibility", "hidden");
+ let event = await onHide;
+ let hideEvent = event.QueryInterface(Ci.nsIAccessibleHideEvent);
+
+ is(
+ getAccessibleDOMNodeID(hideEvent.targetParent),
+ "parent",
+ "Correct target parent."
+ );
+ is(
+ getAccessibleDOMNodeID(hideEvent.targetNextSibling),
+ "next",
+ "Correct target next sibling."
+ );
+ is(
+ getAccessibleDOMNodeID(hideEvent.targetPrevSibling),
+ "previous",
+ "Correct target previous sibling."
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_show.js b/accessible/tests/browser/e10s/browser_events_show.js
new file mode 100644
index 0000000000..fb03ce2329
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_show.js
@@ -0,0 +1,22 @@
+/* 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";
+
+/**
+ * Test show event
+ */
+addAccessibleTask(
+ '<div id="div" style="visibility: hidden;"></div>',
+ async function (browser) {
+ let onShow = waitForEvent(EVENT_SHOW, "div");
+ await invokeSetStyle(browser, "div", "visibility", "visible");
+ let showEvent = await onShow;
+ ok(
+ showEvent.accessibleDocument instanceof nsIAccessibleDocument,
+ "Accessible document not present."
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_statechange.js b/accessible/tests/browser/e10s/browser_events_statechange.js
new file mode 100644
index 0000000000..a510c5b9b5
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_statechange.js
@@ -0,0 +1,71 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+function checkStateChangeEvent(event, state, isExtraState, isEnabled) {
+ let scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent);
+ is(scEvent.state, state, "Correct state of the statechange event.");
+ is(
+ scEvent.isExtraState,
+ isExtraState,
+ "Correct extra state bit of the statechange event."
+ );
+ is(scEvent.isEnabled, isEnabled, "Correct state of statechange event state");
+}
+
+// Insert mock source into the iframe to be able to verify the right document
+// body id.
+let iframeSrc = `data:text/html,
+ <html>
+ <head>
+ <meta charset='utf-8'/>
+ <title>Inner Iframe</title>
+ </head>
+ <body id='iframe'></body>
+ </html>`;
+
+/**
+ * Test state change event and its interface:
+ * - state
+ * - isExtraState
+ * - isEnabled
+ */
+addAccessibleTask(
+ `
+ <iframe id="iframe" src="${iframeSrc}"></iframe>
+ <input id="checkbox" type="checkbox" />`,
+ async function (browser) {
+ // Test state change
+ let onStateChange = waitForEvent(EVENT_STATE_CHANGE, "checkbox");
+ // Set checked for a checkbox.
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("checkbox").checked = true;
+ });
+ let event = await onStateChange;
+
+ checkStateChangeEvent(event, STATE_CHECKED, false, true);
+ testStates(event.accessible, STATE_CHECKED, 0);
+
+ // Test extra state
+ onStateChange = waitForEvent(EVENT_STATE_CHANGE, "iframe");
+ // Set design mode on.
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("iframe").contentDocument.designMode =
+ "on";
+ });
+ event = await onStateChange;
+
+ checkStateChangeEvent(event, EXT_STATE_EDITABLE, true, true);
+ testStates(event.accessible, 0, EXT_STATE_EDITABLE);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_textchange.js b/accessible/tests/browser/e10s/browser_events_textchange.js
new file mode 100644
index 0000000000..f39ecea8c4
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_textchange.js
@@ -0,0 +1,119 @@
+/* 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 checkTextChangeEvent(
+ event,
+ id,
+ text,
+ start,
+ end,
+ isInserted,
+ isFromUserInput
+) {
+ let tcEvent = event.QueryInterface(nsIAccessibleTextChangeEvent);
+ is(tcEvent.start, start, `Correct start offset for ${prettyName(id)}`);
+ is(tcEvent.length, end - start, `Correct length for ${prettyName(id)}`);
+ is(
+ tcEvent.isInserted,
+ isInserted,
+ `Correct isInserted flag for ${prettyName(id)}`
+ );
+ is(tcEvent.modifiedText, text, `Correct text for ${prettyName(id)}`);
+ is(
+ tcEvent.isFromUserInput,
+ isFromUserInput,
+ `Correct value of isFromUserInput for ${prettyName(id)}`
+ );
+ ok(
+ tcEvent.accessibleDocument instanceof nsIAccessibleDocument,
+ "Accessible document not present."
+ );
+}
+
+async function changeText(browser, id, value, events) {
+ let onEvents = waitForOrderedEvents(
+ events.map(({ isInserted }) => {
+ let eventType = isInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
+ return [eventType, id];
+ })
+ );
+ // Change text in the subtree.
+ await invokeContentTask(browser, [id, value], (contentId, contentValue) => {
+ content.document.getElementById(contentId).firstChild.textContent =
+ contentValue;
+ });
+ let resolvedEvents = await onEvents;
+
+ events.forEach(({ isInserted, str, offset }, idx) =>
+ checkTextChangeEvent(
+ resolvedEvents[idx],
+ id,
+ str,
+ offset,
+ offset + str.length,
+ isInserted,
+ false
+ )
+ );
+}
+
+async function removeTextFromInput(browser, id, value, start, end) {
+ let onTextRemoved = waitForEvent(EVENT_TEXT_REMOVED, id);
+ // Select text and delete it.
+ await invokeContentTask(
+ browser,
+ [id, start, end],
+ (contentId, contentStart, contentEnd) => {
+ let el = content.document.getElementById(contentId);
+ el.focus();
+ el.setSelectionRange(contentStart, contentEnd);
+ }
+ );
+ await invokeContentTask(browser, [], () => {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.sendChar("VK_DELETE", content);
+ });
+
+ let event = await onTextRemoved;
+ checkTextChangeEvent(event, id, value, start, end, false, true);
+}
+
+/**
+ * Test text change event and its interface:
+ * - start
+ * - length
+ * - isInserted
+ * - modifiedText
+ * - isFromUserInput
+ */
+addAccessibleTask(
+ `
+ <p id="p">abc</p>
+ <input id="input" value="input" />`,
+ async function (browser) {
+ let events = [
+ { isInserted: false, str: "abc", offset: 0 },
+ { isInserted: true, str: "def", offset: 0 },
+ ];
+ await changeText(browser, "p", "def", events);
+
+ // Adding text should not send events with diffs for non-editable text.
+ // We do this to avoid screen readers reading out confusing diffs for
+ // live regions.
+ events = [
+ { isInserted: false, str: "def", offset: 0 },
+ { isInserted: true, str: "deDEFf", offset: 0 },
+ ];
+ await changeText(browser, "p", "deDEFf", events);
+
+ // Test isFromUserInput property.
+ await removeTextFromInput(browser, "input", "n", 1, 2);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_vcchange.js b/accessible/tests/browser/e10s/browser_events_vcchange.js
new file mode 100644
index 0000000000..3571d66212
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_vcchange.js
@@ -0,0 +1,87 @@
+/* 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";
+
+addAccessibleTask(
+ `
+ <p id="p1">abc</p>
+ <input id="input1" value="input" />`,
+ async function (browser) {
+ let onVCChanged = waitForEvent(
+ EVENT_VIRTUALCURSOR_CHANGED,
+ matchContentDoc
+ );
+ await invokeContentTask(browser, [], () => {
+ const { CommonUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs"
+ );
+ let vc = CommonUtils.getAccessible(
+ content.document,
+ Ci.nsIAccessibleDocument
+ ).virtualCursor;
+ vc.position = CommonUtils.getAccessible(
+ "p1",
+ null,
+ null,
+ null,
+ content.document
+ );
+ });
+ let vccEvent = (await onVCChanged).QueryInterface(
+ nsIAccessibleVirtualCursorChangeEvent
+ );
+ is(vccEvent.newAccessible.id, "p1", "New position is correct");
+ is(vccEvent.newStartOffset, -1, "New start offset is correct");
+ is(vccEvent.newEndOffset, -1, "New end offset is correct");
+ ok(!vccEvent.isFromUserInput, "not user initiated");
+
+ onVCChanged = waitForEvent(EVENT_VIRTUALCURSOR_CHANGED, matchContentDoc);
+ await invokeContentTask(browser, [], () => {
+ const { CommonUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs"
+ );
+ let vc = CommonUtils.getAccessible(
+ content.document,
+ Ci.nsIAccessibleDocument
+ ).virtualCursor;
+ vc.moveNextByText(Ci.nsIAccessiblePivot.CHAR_BOUNDARY);
+ });
+ vccEvent = (await onVCChanged).QueryInterface(
+ nsIAccessibleVirtualCursorChangeEvent
+ );
+ is(vccEvent.newAccessible.id, vccEvent.oldAccessible.id, "Same position");
+ is(vccEvent.newStartOffset, 0, "New start offset is correct");
+ is(vccEvent.newEndOffset, 1, "New end offset is correct");
+ ok(vccEvent.isFromUserInput, "user initiated");
+
+ onVCChanged = waitForEvent(EVENT_VIRTUALCURSOR_CHANGED, matchContentDoc);
+ await invokeContentTask(browser, [], () => {
+ const { CommonUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs"
+ );
+ let vc = CommonUtils.getAccessible(
+ content.document,
+ Ci.nsIAccessibleDocument
+ ).virtualCursor;
+ vc.position = CommonUtils.getAccessible(
+ "input1",
+ null,
+ null,
+ null,
+ content.document
+ );
+ });
+ vccEvent = (await onVCChanged).QueryInterface(
+ nsIAccessibleVirtualCursorChangeEvent
+ );
+ isnot(vccEvent.oldAccessible, vccEvent.newAccessible, "positions differ");
+ is(vccEvent.oldAccessible.id, "p1", "Old position is correct");
+ is(vccEvent.newAccessible.id, "input1", "New position is correct");
+ is(vccEvent.newStartOffset, -1, "New start offset is correct");
+ is(vccEvent.newEndOffset, -1, "New end offset is correct");
+ ok(!vccEvent.isFromUserInput, "not user initiated");
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_language.js b/accessible/tests/browser/e10s/browser_language.js
new file mode 100644
index 0000000000..684d915693
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_language.js
@@ -0,0 +1,29 @@
+/* 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";
+
+addAccessibleTask(
+ `
+<script>
+ // We can't include the html element in snippets, so set lang on it here.
+ document.documentElement.lang = "en";
+</script>
+<div id="inheritEn"></div>
+<div id="de" lang="de">
+ <div id="inheritDe"></div>
+ <div id="fr" lang="fr"></div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ is(docAcc.language, "en", "Document language correct");
+ const inheritEn = findAccessibleChildByID(docAcc, "inheritEn");
+ is(inheritEn.language, "en", "inheritEn language correct");
+ const de = findAccessibleChildByID(docAcc, "de");
+ is(de.language, "de", "de language correct");
+ const fr = findAccessibleChildByID(docAcc, "fr");
+ is(fr.language, "fr", "fr language correct");
+ },
+ { chrome: true, topLevel: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_obj_group.js b/accessible/tests/browser/e10s/browser_obj_group.js
new file mode 100644
index 0000000000..7e22b8b491
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_obj_group.js
@@ -0,0 +1,430 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/attributes.js */
+loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
+
+/**
+ * select elements
+ */
+addAccessibleTask(
+ `<select>
+ <option id="opt1-nosize">option1</option>
+ <option id="opt2-nosize">option2</option>
+ <option id="opt3-nosize">option3</option>
+ <option id="opt4-nosize">option4</option>
+ </select>
+
+ <select size="4">
+ <option id="opt1">option1</option>
+ <option id="opt2">option2</option>
+ </select>
+
+ <select size="4">
+ <optgroup id="select2_optgroup" label="group">
+ <option id="select2_opt1">option1</option>
+ <option id="select2_opt2">option2</option>
+ </optgroup>
+ <option id="select2_opt3">option3</option>
+ <option id="select2_opt4">option4</option>
+ </select>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML select with no size attribute.
+ testGroupAttrs(getAcc("opt1-nosize"), 1, 4);
+ testGroupAttrs(getAcc("opt2-nosize"), 2, 4);
+ testGroupAttrs(getAcc("opt3-nosize"), 3, 4);
+ testGroupAttrs(getAcc("opt4-nosize"), 4, 4);
+
+ // Container should have item count and not hierarchical
+ testGroupParentAttrs(getAcc("opt1-nosize").parent, 4, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML select
+ testGroupAttrs(getAcc("opt1"), 1, 2);
+ testGroupAttrs(getAcc("opt2"), 2, 2);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML select with optgroup
+ testGroupAttrs(getAcc("select2_opt3"), 1, 2, 1);
+ testGroupAttrs(getAcc("select2_opt4"), 2, 2, 1);
+ testGroupAttrs(getAcc("select2_opt1"), 1, 2, 2);
+ testGroupAttrs(getAcc("select2_opt2"), 2, 2, 2);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+/**
+ * HTML radios
+ */
+addAccessibleTask(
+ `<form>
+ <input type="radio" id="radio1" name="group1"/>
+ <input type="radio" id="radio2" name="group1"/>
+ </form>
+
+ <input type="radio" id="radio3" name="group2"/>
+ <label><input type="radio" id="radio4" name="group2"/></label>
+
+ <form>
+ <input type="radio" style="display: none;" name="group3">
+ <input type="radio" id="radio5" name="group3">
+ <input type="radio" id="radio6" name="group4">
+ </form>
+
+ <input type="radio" id="radio7">`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML input@type="radio" within form
+ testGroupAttrs(getAcc("radio1"), 1, 2);
+ testGroupAttrs(getAcc("radio2"), 2, 2);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML input@type="radio" within document
+ testGroupAttrs(getAcc("radio3"), 1, 2);
+ // radio4 is wrapped in a label
+ testGroupAttrs(getAcc("radio4"), 2, 2);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Hidden HTML input@type="radio"
+ testGroupAttrs(getAcc("radio5"), 1, 1);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML input@type="radio" with different name but same parent
+ testGroupAttrs(getAcc("radio6"), 1, 1);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML input@type="radio" with no name
+ testGroupAttrs(getAcc("radio7"), 0, 0);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+/**
+ * lists
+ */
+addAccessibleTask(
+ `<ul id="ul">
+ <li id="li1">Oranges</li>
+ <li id="li2">Apples</li>
+ <li id="li3">Bananas</li>
+ </ul>
+
+ <ol id="ol">
+ <li id="li4">Oranges</li>
+ <li id="li5">Apples</li>
+ <li id="li6">Bananas
+ <ul id="ol_nested">
+ <li id="n_li4">Oranges</li>
+ <li id="n_li5">Apples</li>
+ <li id="n_li6">Bananas</li>
+ </ul>
+ </li>
+ </ol>
+
+ <span role="list" id="aria-list_1">
+ <span role="listitem" id="li7">Oranges</span>
+ <span role="listitem" id="li8">Apples</span>
+ <span role="listitem" id="li9">Bananas</span>
+ </span>
+
+ <span role="list" id="aria-list_2">
+ <span role="listitem" id="li10">Oranges</span>
+ <span role="listitem" id="li11">Apples</span>
+ <span role="listitem" id="li12">Bananas
+ <span role="list" id="aria-list_2_1">
+ <span role="listitem" id="n_li10">Oranges</span>
+ <span role="listitem" id="n_li11">Apples</span>
+ <span role="listitem" id="n_li12">Bananas</span>
+ </span>
+ </span>
+ </span>
+
+ <div role="list" id="aria-list_3">
+ <div role="listitem" id="lgt_li1">Item 1
+ <div role="group">
+ <div role="listitem" id="lgt_li1_nli1">Item 1A</div>
+ <div role="listitem" id="lgt_li1_nli2">Item 1B</div>
+ </div>
+ </div>
+ <div role="listitem" id="lgt_li2">Item 2
+ <div role="group">
+ <div role="listitem" id="lgt_li2_nli1">Item 2A</div>
+ <div role="listitem" id="lgt_li2_nli2">Item 2B</div>
+ </div>
+ </div>
+ </div>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML ul/ol
+ testGroupAttrs(getAcc("li1"), 1, 3);
+ testGroupAttrs(getAcc("li2"), 2, 3);
+ testGroupAttrs(getAcc("li3"), 3, 3);
+
+ // ul should have item count and not hierarchical
+ testGroupParentAttrs(getAcc("ul"), 3, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML ul/ol (nested lists)
+
+ testGroupAttrs(getAcc("li4"), 1, 3, 1);
+ testGroupAttrs(getAcc("li5"), 2, 3, 1);
+ testGroupAttrs(getAcc("li6"), 3, 3, 1);
+ // ol with nested list should have 1st level item count and be hierarchical
+ testGroupParentAttrs(getAcc("ol"), 3, true);
+
+ testGroupAttrs(getAcc("n_li4"), 1, 3, 2);
+ testGroupAttrs(getAcc("n_li5"), 2, 3, 2);
+ testGroupAttrs(getAcc("n_li6"), 3, 3, 2);
+ // nested ol should have item count and be hierarchical
+ testGroupParentAttrs(getAcc("ol_nested"), 3, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA list
+ testGroupAttrs(getAcc("li7"), 1, 3);
+ testGroupAttrs(getAcc("li8"), 2, 3);
+ testGroupAttrs(getAcc("li9"), 3, 3);
+ // simple flat aria list
+ testGroupParentAttrs(getAcc("aria-list_1"), 3, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA list (nested lists: list -> listitem -> list -> listitem)
+ testGroupAttrs(getAcc("li10"), 1, 3, 1);
+ testGroupAttrs(getAcc("li11"), 2, 3, 1);
+ testGroupAttrs(getAcc("li12"), 3, 3, 1);
+ // aria list with nested list
+ testGroupParentAttrs(getAcc("aria-list_2"), 3, true);
+
+ testGroupAttrs(getAcc("n_li10"), 1, 3, 2);
+ testGroupAttrs(getAcc("n_li11"), 2, 3, 2);
+ testGroupAttrs(getAcc("n_li12"), 3, 3, 2);
+ // nested aria list.
+ testGroupParentAttrs(getAcc("aria-list_2_1"), 3, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA list (nested lists: list -> listitem -> group -> listitem)
+ testGroupAttrs(getAcc("lgt_li1"), 1, 2, 1);
+ testGroupAttrs(getAcc("lgt_li1_nli1"), 1, 2, 2);
+ testGroupAttrs(getAcc("lgt_li1_nli2"), 2, 2, 2);
+ testGroupAttrs(getAcc("lgt_li2"), 2, 2, 1);
+ testGroupAttrs(getAcc("lgt_li2_nli1"), 1, 2, 2);
+ testGroupAttrs(getAcc("lgt_li2_nli2"), 2, 2, 2);
+ // aria list with nested list
+ testGroupParentAttrs(getAcc("aria-list_3"), 2, true);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<ul role="menubar" id="menubar">
+ <li role="menuitem" aria-haspopup="true" id="menu_item1">File
+ <ul role="menu" id="menu">
+ <li role="menuitem" id="menu_item1.1">New</li>
+ <li role="menuitem" id="menu_item1.2">Open…</li>
+ <li role="separator">-----</li>
+ <li role="menuitem" id="menu_item1.3">Item</li>
+ <li role="menuitemradio" id="menu_item1.4">Radio</li>
+ <li role="menuitemcheckbox" id="menu_item1.5">Checkbox</li>
+ </ul>
+ </li>
+ <li role="menuitem" aria-haspopup="false" id="menu_item2">Help</li>
+ </ul>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA menu (menuitem, separator, menuitemradio and menuitemcheckbox)
+ testGroupAttrs(getAcc("menu_item1"), 1, 2);
+ testGroupAttrs(getAcc("menu_item2"), 2, 2);
+ testGroupAttrs(getAcc("menu_item1.1"), 1, 2);
+ testGroupAttrs(getAcc("menu_item1.2"), 2, 2);
+ testGroupAttrs(getAcc("menu_item1.3"), 1, 3);
+ testGroupAttrs(getAcc("menu_item1.4"), 2, 3);
+ testGroupAttrs(getAcc("menu_item1.5"), 3, 3);
+ // menu bar item count
+ testGroupParentAttrs(getAcc("menubar"), 2, false);
+ // Bug 1492529. Menu should have total number of items 5 from both sets,
+ // but only has the first 2 item set.
+ todoAttr(getAcc("menu"), "child-item-count", "5");
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<ul id="tablist_1" role="tablist">
+ <li id="tab_1" role="tab">Crust</li>
+ <li id="tab_2" role="tab">Veges</li>
+ <li id="tab_3" role="tab">Carnivore</li>
+ </ul>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA tab
+ testGroupAttrs(getAcc("tab_1"), 1, 3);
+ testGroupAttrs(getAcc("tab_2"), 2, 3);
+ testGroupAttrs(getAcc("tab_3"), 3, 3);
+ // tab list tab count
+ testGroupParentAttrs(getAcc("tablist_1"), 3, false);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<ul id="rg1" role="radiogroup">
+ <li id="r1" role="radio" aria-checked="false">Thai</li>
+ <li id="r2" role="radio" aria-checked="false">Subway</li>
+ <li id="r3" role="radio" aria-checked="false">Jimmy Johns</li>
+ </ul>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA radio
+ testGroupAttrs(getAcc("r1"), 1, 3);
+ testGroupAttrs(getAcc("r2"), 2, 3);
+ testGroupAttrs(getAcc("r3"), 3, 3);
+ // explicit aria radio group
+ testGroupParentAttrs(getAcc("rg1"), 3, false);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<table role="tree" id="tree_1">
+ <tr role="presentation">
+ <td role="treeitem" aria-expanded="true" aria-level="1"
+ id="ti1">vegetables</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti2">cucumber</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti3">carrot</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-expanded="false" aria-level="1"
+ id="ti4">cars</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti5">mercedes</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti6">BMW</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti7">Audi</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="1" id="ti8">people</td>
+ </tr>
+ </table>
+
+ <ul role="tree" id="tree_2">
+ <li role="treeitem" id="tree2_ti1">Item 1
+ <ul role="group">
+ <li role="treeitem" id="tree2_ti1a">Item 1A</li>
+ <li role="treeitem" id="tree2_ti1b">Item 1B</li>
+ </ul>
+ </li>
+ <li role="treeitem" id="tree2_ti2">Item 2
+ <ul role="group">
+ <li role="treeitem" id="tree2_ti2a">Item 2A</li>
+ <li role="treeitem" id="tree2_ti2b">Item 2B</li>
+ </ul>
+ </li>
+ </div>
+
+ <div role="tree" id="tree_3">
+ <div role="treeitem" id="tree3_ti1">Item 1</div>
+ <div role="group">
+ <li role="treeitem" id="tree3_ti1a">Item 1A</li>
+ <li role="treeitem" id="tree3_ti1b">Item 1B</li>
+ </div>
+ <div role="treeitem" id="tree3_ti2">Item 2</div>
+ <div role="group">
+ <div role="treeitem" id="tree3_ti2a">Item 2A</div>
+ <div role="treeitem" id="tree3_ti2b">Item 2B</div>
+ </div>
+ </div>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA tree
+ testGroupAttrs(getAcc("ti1"), 1, 3, 1);
+ testGroupAttrs(getAcc("ti2"), 1, 2, 2);
+ testGroupAttrs(getAcc("ti3"), 2, 2, 2);
+ testGroupAttrs(getAcc("ti4"), 2, 3, 1);
+ testGroupAttrs(getAcc("ti5"), 1, 3, 2);
+ testGroupAttrs(getAcc("ti6"), 2, 3, 2);
+ testGroupAttrs(getAcc("ti7"), 3, 3, 2);
+ testGroupAttrs(getAcc("ti8"), 3, 3, 1);
+ testGroupParentAttrs(getAcc("tree_1"), 3, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA tree (tree -> treeitem -> group -> treeitem)
+ testGroupAttrs(getAcc("tree2_ti1"), 1, 2, 1);
+ testGroupAttrs(getAcc("tree2_ti1a"), 1, 2, 2);
+ testGroupAttrs(getAcc("tree2_ti1b"), 2, 2, 2);
+ testGroupAttrs(getAcc("tree2_ti2"), 2, 2, 1);
+ testGroupAttrs(getAcc("tree2_ti2a"), 1, 2, 2);
+ testGroupAttrs(getAcc("tree2_ti2b"), 2, 2, 2);
+ testGroupParentAttrs(getAcc("tree_2"), 2, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA tree (tree -> treeitem, group -> treeitem)
+ testGroupAttrs(getAcc("tree3_ti1"), 1, 2, 1);
+ testGroupAttrs(getAcc("tree3_ti1a"), 1, 2, 2);
+ testGroupAttrs(getAcc("tree3_ti1b"), 2, 2, 2);
+ testGroupAttrs(getAcc("tree3_ti2"), 2, 2, 1);
+ testGroupAttrs(getAcc("tree3_ti2a"), 1, 2, 2);
+ testGroupAttrs(getAcc("tree3_ti2b"), 2, 2, 2);
+ testGroupParentAttrs(getAcc("tree_3"), 2, true);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_obj_group_002.js b/accessible/tests/browser/e10s/browser_obj_group_002.js
new file mode 100644
index 0000000000..54cad4a019
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_obj_group_002.js
@@ -0,0 +1,390 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/attributes.js */
+loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ `<table role="grid" id="grid">
+ <tr role="row" id="grid_row1">
+ <td role="gridcell" id="grid_cell1">cell1</td>
+ <td role="gridcell" id="grid_cell2">cell2</td>
+ </tr>
+ <tr role="row" id="grid_row2">
+ <td role="gridcell" id="grid_cell3">cell3</td>
+ <td role="gridcell" id="grid_cell4">cell4</td>
+ </tr>
+ </table>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA grid
+ testGroupAttrs(getAcc("grid_row1"), 1, 2);
+ testAbsentAttrs(getAcc("grid_cell1"), { posinset: "", setsize: "" });
+ testAbsentAttrs(getAcc("grid_cell2"), { posinset: "", setsize: "" });
+
+ testGroupAttrs(getAcc("grid_row2"), 2, 2);
+ testAbsentAttrs(getAcc("grid_cell3"), { posinset: "", setsize: "" });
+ testAbsentAttrs(getAcc("grid_cell4"), { posinset: "", setsize: "" });
+ testGroupParentAttrs(getAcc("grid"), 2, false, false);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<div role="treegrid" id="treegrid" aria-colcount="4">
+ <div role="row" aria-level="1" id="treegrid_row1">
+ <div role="gridcell" id="treegrid_cell1">cell1</div>
+ <div role="gridcell" id="treegrid_cell2">cell2</div>
+ </div>
+ <div role="row" aria-level="2" id="treegrid_row2">
+ <div role="gridcell" id="treegrid_cell3">cell1</div>
+ <div role="gridcell" id="treegrid_cell4">cell2</div>
+ </div>
+ <div role="row" id="treegrid_row3">
+ <div role="gridcell" id="treegrid_cell5">cell1</div>
+ <div role="gridcell" id="treegrid_cell6">cell2</div>
+ </div>
+ </div>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA treegrid
+ testGroupAttrs(getAcc("treegrid_row1"), 1, 2, 1);
+ testAbsentAttrs(getAcc("treegrid_cell1"), { posinset: "", setsize: "" });
+ testAbsentAttrs(getAcc("treegrid_cell2"), { posinset: "", setsize: "" });
+
+ testGroupAttrs(getAcc("treegrid_row2"), 1, 1, 2);
+ testAbsentAttrs(getAcc("treegrid_cell3"), { posinset: "", setsize: "" });
+ testAbsentAttrs(getAcc("treegrid_cell4"), { posinset: "", setsize: "" });
+
+ testGroupAttrs(getAcc("treegrid_row3"), 2, 2, 1);
+ testAbsentAttrs(getAcc("treegrid_cell5"), { posinset: "", setsize: "" });
+ testAbsentAttrs(getAcc("treegrid_cell6"), { posinset: "", setsize: "" });
+
+ testGroupParentAttrs(getAcc("treegrid"), 2, true);
+ // row child item count provided by parent grid's aria-colcount
+ testGroupParentAttrs(getAcc("treegrid_row1"), 4, false);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<div id="headings">
+ <h1 id="h1">heading1</h1>
+ <h2 id="h2">heading2</h2>
+ <h3 id="h3">heading3</h3>
+ <h4 id="h4">heading4</h4>
+ <h5 id="h5">heading5</h5>
+ <h6 id="h6">heading6</h6>
+ <div id="ariaHeadingNoLevel" role="heading">ariaHeadingNoLevel</div>
+ </div>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML headings
+ testGroupAttrs(getAcc("h1"), 0, 0, 1);
+ testGroupAttrs(getAcc("h2"), 0, 0, 2);
+ testGroupAttrs(getAcc("h3"), 0, 0, 3);
+ testGroupAttrs(getAcc("h4"), 0, 0, 4);
+ testGroupAttrs(getAcc("h5"), 0, 0, 5);
+ testGroupAttrs(getAcc("h6"), 0, 0, 6);
+ testGroupAttrs(getAcc("ariaHeadingNoLevel"), 0, 0, 2);
+ // No child item counts or "tree" flag for parent of headings
+ testAbsentAttrs(getAcc("headings"), { "child-item-count": "", tree: "" });
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<ul id="combo1" role="combobox">Password
+ <li id="combo1_opt1" role="option">Xyzzy</li>
+ <li id="combo1_opt2" role="option">Plughs</li>
+ <li id="combo1_opt3" role="option">Shazaam</li>
+ <li id="combo1_opt4" role="option">JoeSentMe</li>
+ </ul>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA combobox
+ testGroupAttrs(getAcc("combo1_opt1"), 1, 4);
+ testGroupAttrs(getAcc("combo1_opt2"), 2, 4);
+ testGroupAttrs(getAcc("combo1_opt3"), 3, 4);
+ testGroupAttrs(getAcc("combo1_opt4"), 4, 4);
+ testGroupParentAttrs(getAcc("combo1"), 4, false);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<div role="table" aria-colcount="4" aria-rowcount="2" id="table">
+ <div role="row" id="table_row" aria-rowindex="2">
+ <div role="cell" id="table_cell" aria-colindex="3">cell</div>
+ </div>
+ </div>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA table
+ testGroupAttrs(getAcc("table_cell"), 3, 4);
+ testGroupAttrs(getAcc("table_row"), 2, 2);
+
+ // grid child item count provided by aria-rowcount
+ testGroupParentAttrs(getAcc("table"), 2, false);
+ // row child item count provided by parent grid's aria-colcount
+ testGroupParentAttrs(getAcc("table_row"), 4, false);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<div role="grid" aria-readonly="true">
+ <div tabindex="-1">
+ <div role="row" id="wrapped_row_1">
+ <div role="gridcell">cell content</div>
+ </div>
+ </div>
+ <div tabindex="-1">
+ <div role="row" id="wrapped_row_2">
+ <div role="gridcell">cell content</div>
+ </div>
+ </div>
+ </div>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // Attributes calculated even when row is wrapped in a div.
+ testGroupAttrs(getAcc("wrapped_row_1"), 1, 2, null);
+ testGroupAttrs(getAcc("wrapped_row_2"), 2, 2, null);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<div role="list" aria-owns="t1_li1 t1_li2 t1_li3" id="aria-list_4">
+ <div role="listitem" id="t1_li2">Apples</div>
+ <div role="listitem" id="t1_li1">Oranges</div>
+ </div>
+ <div role="listitem" id="t1_li3">Bananas</div>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA list constructed by ARIA owns
+ testGroupAttrs(getAcc("t1_li1"), 1, 3);
+ testGroupAttrs(getAcc("t1_li2"), 2, 3);
+ testGroupAttrs(getAcc("t1_li3"), 3, 3);
+ testGroupParentAttrs(getAcc("aria-list_4"), 3, false);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<!-- ARIA comments, 1 level, group pos and size calculation -->
+ <article>
+ <p id="comm_single_1" role="comment">Comment 1</p>
+ <p id="comm_single_2" role="comment">Comment 2</p>
+ </article>
+
+ <!-- Nested comments -->
+ <article>
+ <div id="comm_nested_1" role="comment"><p>Comment 1 level 1</p>
+ <div id="comm_nested_1_1" role="comment"><p>Comment 1 level 2</p></div>
+ <div id="comm_nested_1_2" role="comment"><p>Comment 2 level 2</p></div>
+ </div>
+ <div id="comm_nested_2" role="comment"><p>Comment 2 level 1</p>
+ <div id="comm_nested_2_1" role="comment"><p>Comment 3 level 2</p>
+ <div id="comm_nested_2_1_1" role="comment"><p>Comment 1 level 3</p></div>
+ </div>
+ </div>
+ <div id="comm_nested_3" role="comment"><p>Comment 3 level 1</p></div>
+ </article>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // Test group attributes of ARIA comments
+ testGroupAttrs(getAcc("comm_single_1"), 1, 2, 1);
+ testGroupAttrs(getAcc("comm_single_2"), 2, 2, 1);
+ testGroupAttrs(getAcc("comm_nested_1"), 1, 3, 1);
+ testGroupAttrs(getAcc("comm_nested_1_1"), 1, 2, 2);
+ testGroupAttrs(getAcc("comm_nested_1_2"), 2, 2, 2);
+ testGroupAttrs(getAcc("comm_nested_2"), 2, 3, 1);
+ testGroupAttrs(getAcc("comm_nested_2_1"), 1, 1, 2);
+ testGroupAttrs(getAcc("comm_nested_2_1_1"), 1, 1, 3);
+ testGroupAttrs(getAcc("comm_nested_3"), 3, 3, 1);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<div role="tree" id="tree4"><div role="treeitem"
+ id="tree4_ti1">Item 1</div><div role="treeitem"
+ id="tree4_ti2">Item 2</div></div>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // Test that group position information updates after deleting node.
+ testGroupAttrs(getAcc("tree4_ti1"), 1, 2, 1);
+ testGroupAttrs(getAcc("tree4_ti2"), 2, 2, 1);
+ testGroupParentAttrs(getAcc("tree4"), 2, true);
+
+ let p = waitForEvent(EVENT_REORDER, "tree4");
+ invokeContentTask(browser, [], () => {
+ content.document.getElementById("tree4_ti1").remove();
+ });
+
+ await p;
+ testGroupAttrs(getAcc("tree4_ti2"), 1, 1, 1);
+ testGroupParentAttrs(getAcc("tree4"), 1, true);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+// Verify that intervening SECTION accs in ARIA compound widgets do not split
+// up the group info for descendant owned elements. Test various types of
+// widgets that should all be treated the same.
+addAccessibleTask(
+ `<div role="tree" id="tree">
+ <div tabindex="0">
+ <div role="treeitem" id="ti1">treeitem 1</div>
+ </div>
+ <div tabindex="0">
+ <div role="treeitem" id="ti2">treeitem 2</div>
+ </div>
+ </div>
+ <div role="listbox" id="listbox">
+ <div tabindex="0">
+ <div role="option" id="opt1">option 1</div>
+ </div>
+ <div tabindex="0">
+ <div role="option" id="opt2">option 2</div>
+ </div>
+ </div>
+ <div role="list" id="list">
+ <div tabindex="0">
+ <div role="listitem" id="li1">listitem 1</div>
+ </div>
+ <div tabindex="0">
+ <div role="listitem" id="li2">listitem 2</div>
+ </div>
+ </div>
+ <div role="menu" id="menu">
+ <div tabindex="0">
+ <div role="menuitem" id="mi1">menuitem 1</div>
+ </div>
+ <div tabindex="0">
+ <div role="menuitem" id="mi2">menuitem 2</div>
+ </div>
+ </div>
+ <div role="radiogroup" id="radiogroup">
+ <div tabindex="0">
+ <div role="radio" id="r1">radio 1</div>
+ </div>
+ <div tabindex="0">
+ <div role="radio" id="r2">radio 2</div>
+ </div>
+ </div>
+`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ testGroupAttrs(getAcc("ti1"), 1, 2, 1);
+ testGroupAttrs(getAcc("ti2"), 2, 2, 1);
+
+ testGroupAttrs(getAcc("opt1"), 1, 2, 0);
+ testGroupAttrs(getAcc("opt2"), 2, 2, 0);
+
+ testGroupAttrs(getAcc("li1"), 1, 2, 0);
+ testGroupAttrs(getAcc("li2"), 2, 2, 0);
+
+ testGroupAttrs(getAcc("mi1"), 1, 2, 0);
+ testGroupAttrs(getAcc("mi2"), 2, 2, 0);
+
+ testGroupAttrs(getAcc("r1"), 1, 2, 0);
+ testGroupAttrs(getAcc("r2"), 2, 2, 0);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
+
+// Verify that non-generic accessibles (like buttons) correctly split the group
+// info of descendant owned elements.
+addAccessibleTask(
+ `<div role="tree" id="tree">
+ <div role="button">
+ <div role="treeitem" id="ti1">first</div>
+ </div>
+ <div tabindex="0">
+ <div role="treeitem" id="ti2">second</div>
+ </div>
+ </div>`,
+ async function (browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ testGroupAttrs(getAcc("ti1"), 1, 1, 1);
+ testGroupAttrs(getAcc("ti2"), 1, 1, 1);
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js b/accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js
new file mode 100644
index 0000000000..6d5995531e
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js
@@ -0,0 +1,45 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+// Test ARIA Dialog
+addAccessibleTask(
+ "e10s/doc_treeupdate_ariadialog.html",
+ async function (browser, accDoc) {
+ testAccessibleTree(accDoc, {
+ role: ROLE_DOCUMENT,
+ children: [],
+ });
+
+ // Make dialog visible and update its inner content.
+ let onShow = waitForEvent(EVENT_SHOW, "dialog");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("dialog").style.display = "block";
+ });
+ await onShow;
+
+ testAccessibleTree(accDoc, {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_DIALOG,
+ children: [
+ {
+ role: ROLE_PUSHBUTTON,
+ children: [{ role: ROLE_TEXT_LEAF }],
+ },
+ {
+ role: ROLE_ENTRY,
+ },
+ ],
+ },
+ ],
+ });
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js
new file mode 100644
index 0000000000..c8fb7e5488
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js
@@ -0,0 +1,325 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+async function testContainer1(browser, accDoc) {
+ const id = "t1_container";
+ const docID = getAccessibleDOMNodeID(accDoc);
+ const acc = findAccessibleChildByID(accDoc, id);
+
+ /* ================= Initial tree test ==================================== */
+ // children are swapped by ARIA owns
+ let tree = {
+ SECTION: [{ CHECKBUTTON: [{ SECTION: [] }] }, { PUSHBUTTON: [] }],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Change ARIA owns ====================================== */
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeSetAttribute(browser, id, "aria-owns", "t1_button t1_subdiv");
+ await onReorder;
+
+ // children are swapped again, button and subdiv are appended to
+ // the children.
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [] }, // checkbox, native order
+ { PUSHBUTTON: [] }, // button, rearranged by ARIA own
+ { SECTION: [] }, // subdiv from the subtree, ARIA owned
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Remove ARIA owns ====================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeSetAttribute(browser, id, "aria-owns");
+ await onReorder;
+
+ // children follow the DOM order
+ tree = {
+ SECTION: [{ PUSHBUTTON: [] }, { CHECKBUTTON: [{ SECTION: [] }] }],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Set ARIA owns ========================================= */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeSetAttribute(browser, id, "aria-owns", "t1_button t1_subdiv");
+ await onReorder;
+
+ // children are swapped again, button and subdiv are appended to
+ // the children.
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [] }, // checkbox
+ { PUSHBUTTON: [] }, // button, rearranged by ARIA own
+ { SECTION: [] }, // subdiv from the subtree, ARIA owned
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Add ID to ARIA owns =================================== */
+ onReorder = waitForEvent(EVENT_REORDER, docID);
+ await invokeSetAttribute(
+ browser,
+ id,
+ "aria-owns",
+ "t1_button t1_subdiv t1_group"
+ );
+ await onReorder;
+
+ // children are swapped again, button and subdiv are appended to
+ // the children.
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [] }, // t1_checkbox
+ { PUSHBUTTON: [] }, // button, t1_button
+ { SECTION: [] }, // subdiv from the subtree, t1_subdiv
+ { GROUPING: [] }, // group from outside, t1_group
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Append element ======================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ let div = content.document.createElement("div");
+ div.setAttribute("id", "t1_child3");
+ div.setAttribute("role", "radio");
+ content.document.getElementById(contentId).appendChild(div);
+ });
+ await onReorder;
+
+ // children are invalidated, they includes aria-owns swapped kids and
+ // newly inserted child.
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [] }, // existing explicit, t1_checkbox
+ { RADIOBUTTON: [] }, // new explicit, t1_child3
+ { PUSHBUTTON: [] }, // ARIA owned, t1_button
+ { SECTION: [] }, // ARIA owned, t1_subdiv
+ { GROUPING: [] }, // ARIA owned, t1_group
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Remove element ======================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("t1_span").remove();
+ });
+ await onReorder;
+
+ // subdiv should go away
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [] }, // explicit, t1_checkbox
+ { RADIOBUTTON: [] }, // explicit, t1_child3
+ { PUSHBUTTON: [] }, // ARIA owned, t1_button
+ { GROUPING: [] }, // ARIA owned, t1_group
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Remove ID ============================================= */
+ onReorder = waitForEvent(EVENT_REORDER, docID);
+ await invokeSetAttribute(browser, "t1_group", "id");
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [] },
+ { RADIOBUTTON: [] },
+ { PUSHBUTTON: [] }, // ARIA owned, t1_button
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Set ID ================================================ */
+ onReorder = waitForEvent(EVENT_REORDER, docID);
+ await invokeSetAttribute(browser, "t1_grouptmp", "id", "t1_group");
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [] },
+ { RADIOBUTTON: [] },
+ { PUSHBUTTON: [] }, // ARIA owned, t1_button
+ { GROUPING: [] }, // ARIA owned, t1_group, previously t1_grouptmp
+ ],
+ };
+ testAccessibleTree(acc, tree);
+}
+
+async function removeContainer(browser, accDoc) {
+ const id = "t2_container1";
+ const acc = findAccessibleChildByID(accDoc, id);
+
+ let tree = {
+ SECTION: [
+ { CHECKBUTTON: [] }, // ARIA owned, 't2_owned'
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("t2_container2")
+ .removeChild(content.document.getElementById("t2_container3"));
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [],
+ };
+ testAccessibleTree(acc, tree);
+}
+
+async function stealAndRecacheChildren(browser, accDoc) {
+ const id1 = "t3_container1";
+ const id2 = "t3_container2";
+ const acc1 = findAccessibleChildByID(accDoc, id1);
+ const acc2 = findAccessibleChildByID(accDoc, id2);
+
+ /* ================ Attempt to steal from other ARIA owns ================= */
+ let onReorder = waitForEvent(EVENT_REORDER, id2);
+ await invokeSetAttribute(browser, id2, "aria-owns", "t3_child");
+ await invokeContentTask(browser, [id2], id => {
+ let div = content.document.createElement("div");
+ div.setAttribute("role", "radio");
+ content.document.getElementById(id).appendChild(div);
+ });
+ await onReorder;
+
+ let tree = {
+ SECTION: [
+ { CHECKBUTTON: [] }, // ARIA owned
+ ],
+ };
+ testAccessibleTree(acc1, tree);
+
+ tree = {
+ SECTION: [{ RADIOBUTTON: [] }],
+ };
+ testAccessibleTree(acc2, tree);
+}
+
+async function showHiddenElement(browser, accDoc) {
+ const id = "t4_container1";
+ const acc = findAccessibleChildByID(accDoc, id);
+
+ let tree = {
+ SECTION: [{ RADIOBUTTON: [] }],
+ };
+ testAccessibleTree(acc, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeSetStyle(browser, "t4_child1", "display", "block");
+ await onReorder;
+
+ tree = {
+ SECTION: [{ CHECKBUTTON: [] }, { RADIOBUTTON: [] }],
+ };
+ testAccessibleTree(acc, tree);
+}
+
+async function rearrangeARIAOwns(browser, accDoc) {
+ const id = "t5_container";
+ const acc = findAccessibleChildByID(accDoc, id);
+ const tests = [
+ {
+ val: "t5_checkbox t5_radio t5_button",
+ roleList: ["CHECKBUTTON", "RADIOBUTTON", "PUSHBUTTON"],
+ },
+ {
+ val: "t5_radio t5_button t5_checkbox",
+ roleList: ["RADIOBUTTON", "PUSHBUTTON", "CHECKBUTTON"],
+ },
+ ];
+
+ for (let { val, roleList } of tests) {
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeSetAttribute(browser, id, "aria-owns", val);
+ await onReorder;
+
+ let tree = { SECTION: [] };
+ for (let role of roleList) {
+ let ch = {};
+ ch[role] = [];
+ tree.SECTION.push(ch);
+ }
+ testAccessibleTree(acc, tree);
+ }
+}
+
+async function removeNotARIAOwnedEl(browser, accDoc) {
+ const id = "t6_container";
+ const acc = findAccessibleChildByID(accDoc, id);
+
+ let tree = {
+ SECTION: [{ TEXT_LEAF: [] }, { GROUPING: [] }],
+ };
+ testAccessibleTree(acc, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ content.document
+ .getElementById(contentId)
+ .removeChild(content.document.getElementById("t6_span"));
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [{ GROUPING: [] }],
+ };
+ testAccessibleTree(acc, tree);
+}
+
+addAccessibleTask(
+ "e10s/doc_treeupdate_ariaowns.html",
+ async function (browser, accDoc) {
+ await testContainer1(browser, accDoc);
+ await removeContainer(browser, accDoc);
+ await stealAndRecacheChildren(browser, accDoc);
+ await showHiddenElement(browser, accDoc);
+ await rearrangeARIAOwns(browser, accDoc);
+ await removeNotARIAOwnedEl(browser, accDoc);
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+// Test owning an ancestor which isn't created yet with an iframe in the
+// subtree.
+addAccessibleTask(
+ `
+ <span id="a">
+ <div id="b" aria-owns="c"></div>
+ </span>
+ <div id="c">
+ <iframe></iframe>
+ </div>
+ <script>
+ document.getElementById("c").setAttribute("aria-owns", "a");
+ </script>
+ `,
+ async function (browser, accDoc) {
+ testAccessibleTree(accDoc, {
+ DOCUMENT: [
+ {
+ // b
+ SECTION: [
+ {
+ // c
+ SECTION: [{ INTERNAL_FRAME: [{ DOCUMENT: [] }] }],
+ },
+ ],
+ },
+ ],
+ });
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_canvas.js b/accessible/tests/browser/e10s/browser_treeupdate_canvas.js
new file mode 100644
index 0000000000..ad7338f725
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_canvas.js
@@ -0,0 +1,28 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ `
+ <canvas id="canvas">
+ <div id="dialog" role="dialog" style="display: none;"></div>
+ </canvas>`,
+ async function (browser, accDoc) {
+ let canvas = findAccessibleChildByID(accDoc, "canvas");
+ let dialog = findAccessibleChildByID(accDoc, "dialog");
+
+ testAccessibleTree(canvas, { CANVAS: [] });
+
+ let onShow = waitForEvent(EVENT_SHOW, "dialog");
+ await invokeSetStyle(browser, "dialog", "display", "block");
+ await onShow;
+
+ testAccessibleTree(dialog, { DIALOG: [] });
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_csscontentvisibility.js b/accessible/tests/browser/e10s/browser_treeupdate_csscontentvisibility.js
new file mode 100644
index 0000000000..0af583a96c
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_csscontentvisibility.js
@@ -0,0 +1,72 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+const snippet = `
+ <style>
+ #target {
+ width: 150px;
+ height: 150px;
+ background-color: lightblue;
+ }
+ #child {
+ width: 100px;
+ height: 100px;
+ background-color: lightgreen;
+ }
+ #content-child {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ display: contents;
+ }
+ .hidden {
+ content-visibility: hidden;
+ }
+ .auto {
+ content-visibility: auto;
+ }
+ </style>
+ <div class="hidden" id="target">
+ <div id="child"></div>
+ <div id="content-child"></div>
+ </div>
+ `;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["layout.css.content-visibility.enabled", true]],
+ });
+});
+
+async function setContentVisibility(browser, id, value) {
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+
+ // Change the value of `content-visibility` property for the target
+ info(`Setting content-visibility: ${value} on ${id}`);
+ await invokeSetAttribute(browser, id, "class", value);
+ await onReorder;
+}
+
+addAccessibleTask(
+ snippet,
+ async function (browser, accDoc) {
+ const targetId = "target";
+ const target = findAccessibleChildByID(accDoc, targetId);
+
+ info("Initial Accessibility Structure Test");
+ testAccessibleTree(target, { SECTION: [] });
+
+ await setContentVisibility(browser, targetId, "auto");
+ testAccessibleTree(target, { SECTION: [{ SECTION: [] }, { SECTION: [] }] });
+
+ await setContentVisibility(browser, targetId, "hidden");
+ testAccessibleTree(target, { SECTION: [] });
+ },
+ { iframe: true, remoteIframe: true, chrome: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js b/accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js
new file mode 100644
index 0000000000..4d18f1c08d
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js
@@ -0,0 +1,60 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ `
+ <div id="container"><div id="scrollarea" style="overflow:auto;"><input>`,
+ async function (browser, accDoc) {
+ const id1 = "container";
+ const container = findAccessibleChildByID(accDoc, id1);
+
+ /* ================= Change scroll range ================================== */
+ let tree = {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // scroll area
+ ENTRY: [], // child content
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(container, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id1);
+ await invokeContentTask(browser, [id1], id => {
+ let doc = content.document;
+ doc.getElementById("scrollarea").style.width = "20px";
+ doc.getElementById(id).appendChild(doc.createElement("input"));
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // scroll area
+ ENTRY: [], // child content
+ },
+ ],
+ },
+ {
+ ENTRY: [], // inserted input
+ },
+ ],
+ };
+ testAccessibleTree(container, tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_doc.js b/accessible/tests/browser/e10s/browser_treeupdate_doc.js
new file mode 100644
index 0000000000..982b039762
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_doc.js
@@ -0,0 +1,320 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+const iframeSrc = `data:text/html,
+ <html>
+ <head>
+ <meta charset='utf-8'/>
+ <title>Inner Iframe</title>
+ </head>
+ <body id='inner-iframe'></body>
+ </html>`;
+
+addAccessibleTask(
+ `
+ <iframe id="iframe" src="${iframeSrc}"></iframe>`,
+ async function (browser, accDoc) {
+ // ID of the iframe that is being tested
+ const id = "inner-iframe";
+
+ let iframe = findAccessibleChildByID(accDoc, id);
+
+ /* ================= Initial tree check =================================== */
+ let tree = {
+ role: ROLE_DOCUMENT,
+ children: [],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Write iframe document ================================ */
+ let reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ let newHTMLNode = docNode.createElement("html");
+ let newBodyNode = docNode.createElement("body");
+ let newTextNode = docNode.createTextNode("New Wave");
+ newBodyNode.id = contentId;
+ newBodyNode.appendChild(newTextNode);
+ newHTMLNode.appendChild(newBodyNode);
+ docNode.replaceChild(newHTMLNode, docNode.documentElement);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "New Wave",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Replace iframe HTML element ========================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ // We can't use open/write/close outside of iframe document because of
+ // security error.
+ let script = docNode.createElement("script");
+ script.textContent = `
+ document.open();
+ document.write('<body id="${contentId}">hello</body>');
+ document.close();`;
+ docNode.body.appendChild(script);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "hello",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Replace iframe body ================================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ let newBodyNode = docNode.createElement("body");
+ let newTextNode = docNode.createTextNode("New Hello");
+ newBodyNode.id = contentId;
+ newBodyNode.appendChild(newTextNode);
+ newBodyNode.setAttribute("role", "application");
+ docNode.documentElement.replaceChild(newBodyNode, docNode.body);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_APPLICATION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "New Hello",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Open iframe document ================================= */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ // Open document.
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ let script = docNode.createElement("script");
+ script.textContent = `
+ function closeMe() {
+ document.write('Works?');
+ document.close();
+ }
+ window.closeMe = closeMe;
+ document.open();
+ document.write('<body id="${contentId}"></body>');`;
+ docNode.body.appendChild(script);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Close iframe document ================================ */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [], () => {
+ // Write and close document.
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ docNode.write("Works?");
+ docNode.close();
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Works?",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Remove HTML from iframe document ===================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
+ await invokeContentTask(browser, [], () => {
+ // Remove HTML element.
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ docNode.firstChild.remove();
+ });
+ let event = await reorderEventPromise;
+
+ ok(
+ event.accessible instanceof nsIAccessibleDocument,
+ "Reorder should happen on the document"
+ );
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Insert HTML to iframe document ======================= */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ // Insert HTML element.
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ let html = docNode.createElement("html");
+ let body = docNode.createElement("body");
+ let text = docNode.createTextNode("Haha");
+ body.appendChild(text);
+ body.id = contentId;
+ html.appendChild(body);
+ docNode.appendChild(html);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Haha",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Remove body from iframe document ===================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
+ await invokeContentTask(browser, [], () => {
+ // Remove body element.
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ docNode.documentElement.removeChild(docNode.body);
+ });
+ event = await reorderEventPromise;
+
+ ok(
+ event.accessible instanceof nsIAccessibleDocument,
+ "Reorder should happen on the document"
+ );
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================ Insert element under document element while body missed */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
+ await invokeContentTask(browser, [], () => {
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ let inputNode = (content.window.inputNode =
+ docNode.createElement("input"));
+ docNode.documentElement.appendChild(inputNode);
+ });
+ event = await reorderEventPromise;
+
+ ok(
+ event.accessible instanceof nsIAccessibleDocument,
+ "Reorder should happen on the document"
+ );
+ tree = {
+ DOCUMENT: [{ ENTRY: [] }],
+ };
+ testAccessibleTree(iframe, tree);
+
+ reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
+ await invokeContentTask(browser, [], () => {
+ let docEl =
+ content.document.getElementById("iframe").contentDocument
+ .documentElement;
+ // Remove aftermath of this test before next test starts.
+ docEl.firstChild.remove();
+ });
+ // Make sure reorder event was fired and that the input was removed.
+ await reorderEventPromise;
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Insert body to iframe document ======================= */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ // Write and close document.
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ // Insert body element.
+ let body = docNode.createElement("body");
+ let text = docNode.createTextNode("Yo ho ho i butylka roma!");
+ body.appendChild(text);
+ body.id = contentId;
+ docNode.documentElement.appendChild(body);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Yo ho ho i butylka roma!",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Change source ======================================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, "iframe");
+ await invokeSetAttribute(
+ browser,
+ "iframe",
+ "src",
+ `data:text/html,<html><body id="${id}"><input></body></html>`
+ );
+ event = await reorderEventPromise;
+
+ tree = {
+ INTERNAL_FRAME: [{ DOCUMENT: [{ ENTRY: [] }] }],
+ };
+ testAccessibleTree(event.accessible, tree);
+ iframe = findAccessibleChildByID(event.accessible, id);
+
+ /* ================= Replace iframe body on ARIA role body ================ */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ let newBodyNode = docNode.createElement("body");
+ let newTextNode = docNode.createTextNode("New Hello");
+ newBodyNode.appendChild(newTextNode);
+ newBodyNode.setAttribute("role", "application");
+ newBodyNode.id = contentId;
+ docNode.documentElement.replaceChild(newBodyNode, docNode.body);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_APPLICATION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "New Hello",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_gencontent.js b/accessible/tests/browser/e10s/browser_treeupdate_gencontent.js
new file mode 100644
index 0000000000..95406d96cf
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_gencontent.js
@@ -0,0 +1,94 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ `
+ <style>
+ .gentext:before {
+ content: "START"
+ }
+ .gentext:after {
+ content: "END"
+ }
+ </style>
+ <div id="container1"></div>
+ <div id="container2"><div id="container2_child">text</div></div>`,
+ async function (browser, accDoc) {
+ const id1 = "container1";
+ const id2 = "container2";
+ let container1 = findAccessibleChildByID(accDoc, id1);
+ let container2 = findAccessibleChildByID(accDoc, id2);
+
+ let tree = {
+ SECTION: [], // container
+ };
+ testAccessibleTree(container1, tree);
+
+ tree = {
+ SECTION: [
+ {
+ // container2
+ SECTION: [
+ {
+ // container2 child
+ TEXT_LEAF: [], // primary text
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(container2, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id1);
+ // Create and add an element with CSS generated content to container1
+ await invokeContentTask(browser, [id1], id => {
+ let node = content.document.createElement("div");
+ node.textContent = "text";
+ node.setAttribute("class", "gentext");
+ content.document.getElementById(id).appendChild(node);
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ // container
+ {
+ SECTION: [
+ // inserted node
+ { STATICTEXT: [] }, // :before
+ { TEXT_LEAF: [] }, // primary text
+ { STATICTEXT: [] }, // :after
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(container1, tree);
+
+ onReorder = waitForEvent(EVENT_REORDER, "container2_child");
+ // Add CSS generated content to an element in container2's subtree
+ await invokeSetAttribute(browser, "container2_child", "class", "gentext");
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ // container2
+ {
+ SECTION: [
+ // container2 child
+ { STATICTEXT: [] }, // :before
+ { TEXT_LEAF: [] }, // primary text
+ { STATICTEXT: [] }, // :after
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(container2, tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_hidden.js b/accessible/tests/browser/e10s/browser_treeupdate_hidden.js
new file mode 100644
index 0000000000..d3817a003b
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_hidden.js
@@ -0,0 +1,32 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+async function setHidden(browser, value) {
+ let onReorder = waitForEvent(EVENT_REORDER, "container");
+ await invokeSetAttribute(browser, "child", "hidden", value);
+ await onReorder;
+}
+
+addAccessibleTask(
+ '<div id="container"><input id="child"></div>',
+ async function (browser, accDoc) {
+ let container = findAccessibleChildByID(accDoc, "container");
+
+ testAccessibleTree(container, { SECTION: [{ ENTRY: [] }] });
+
+ // Set @hidden attribute
+ await setHidden(browser, "true");
+ testAccessibleTree(container, { SECTION: [] });
+
+ // Remove @hidden attribute
+ await setHidden(browser);
+ testAccessibleTree(container, { SECTION: [{ ENTRY: [] }] });
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_image.js b/accessible/tests/browser/e10s/browser_treeupdate_image.js
new file mode 100644
index 0000000000..cf45de65e0
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_image.js
@@ -0,0 +1,192 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+const IMG_ID = "img";
+const ALT_TEXT = "some-text";
+const ARIA_LABEL = "some-label";
+
+// Verify that granting alt text adds the graphic accessible.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png" alt=""/>`,
+ async function (browser, accDoc) {
+ // Test initial state; the img has empty alt text so it should not be in the tree.
+ const acc = findAccessibleChildByID(accDoc, IMG_ID);
+ ok(!acc, "Image has no Accessible");
+
+ // Add the alt text. The graphic should have been inserted into the tree.
+ info(`Adding alt text "${ALT_TEXT}" to img id '${IMG_ID}'`);
+ const shown = waitForEvent(EVENT_SHOW, IMG_ID);
+ await invokeSetAttribute(browser, IMG_ID, "alt", ALT_TEXT);
+ await shown;
+ let tree = {
+ role: ROLE_GRAPHIC,
+ name: ALT_TEXT,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that the graphic accessible exists even with a missing alt attribute.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png"/>`,
+ async function (browser, accDoc) {
+ // Test initial state; the img has no alt attribute so the name is empty.
+ const acc = findAccessibleChildByID(accDoc, IMG_ID);
+ let tree = {
+ role: ROLE_GRAPHIC,
+ name: null,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+
+ // Add the alt text. The graphic should still be present in the tree.
+ info(`Adding alt attribute with text "${ALT_TEXT}" to id ${IMG_ID}`);
+ const shown = waitForEvent(EVENT_NAME_CHANGE, IMG_ID);
+ await invokeSetAttribute(browser, IMG_ID, "alt", ALT_TEXT);
+ await shown;
+ tree = {
+ role: ROLE_GRAPHIC,
+ name: ALT_TEXT,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that removing alt text removes the graphic accessible.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png" alt="${ALT_TEXT}"/>`,
+ async function (browser, accDoc) {
+ // Test initial state; the img has alt text so it should be in the tree.
+ let acc = findAccessibleChildByID(accDoc, IMG_ID);
+ let tree = {
+ role: ROLE_GRAPHIC,
+ name: ALT_TEXT,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+
+ // Set the alt text empty. The graphic should have been removed from the tree.
+ info(`Setting empty alt text for img id ${IMG_ID}`);
+ const hidden = waitForEvent(EVENT_HIDE, acc);
+ await invokeContentTask(browser, [IMG_ID, "alt", ""], (id, attr, value) => {
+ let elm = content.document.getElementById(id);
+ elm.setAttribute(attr, value);
+ });
+ await hidden;
+ acc = findAccessibleChildByID(accDoc, IMG_ID);
+ ok(!acc, "Image has no Accessible");
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that the presence of an aria-label creates an accessible, even if
+// there is no alt text.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png" aria-label="${ARIA_LABEL}" alt=""/>`,
+ async function (browser, accDoc) {
+ // Test initial state; the img has empty alt text, but it does have an
+ // aria-label, so it should be in the tree.
+ const acc = findAccessibleChildByID(accDoc, IMG_ID);
+ let tree = {
+ role: ROLE_GRAPHIC,
+ name: ARIA_LABEL,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+
+ // Add the alt text. The graphic should still be in the tree.
+ info(`Adding alt text "${ALT_TEXT}" to img id '${IMG_ID}'`);
+ await invokeSetAttribute(browser, IMG_ID, "alt", ALT_TEXT);
+ tree = {
+ role: ROLE_GRAPHIC,
+ name: ARIA_LABEL,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that the presence of a click listener results in the graphic
+// accessible's presence in the tree.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png" alt=""/>`,
+ async function (browser, accDoc) {
+ // Add a click listener to the img element.
+ info(`Adding click listener to img id '${IMG_ID}'`);
+ const shown = waitForEvent(EVENT_SHOW, IMG_ID);
+ await invokeContentTask(browser, [IMG_ID], id => {
+ content.document.getElementById(id).addEventListener("click", () => {});
+ });
+ await shown;
+
+ // Test initial state; the img has empty alt text, but it does have a click
+ // listener, so it should be in the tree.
+ let acc = findAccessibleChildByID(accDoc, IMG_ID);
+ let tree = {
+ role: ROLE_GRAPHIC,
+ name: null,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that the presentation role prevents creation of the graphic accessible.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png" role="presentation"/>`,
+ async function (browser, accDoc) {
+ // Test initial state; the img is presentational and should not be in the tree.
+ const acc = findAccessibleChildByID(accDoc, IMG_ID);
+ ok(!acc, "Image has no Accessible");
+
+ // Add some alt text. There should still be no accessible for the img in the tree.
+ info(`Adding alt attribute with text "${ALT_TEXT}" to id ${IMG_ID}`);
+ await invokeSetAttribute(browser, IMG_ID, "alt", ALT_TEXT);
+ ok(!acc, "Image has no Accessible");
+
+ // Remove the presentation role. The accessible should be created.
+ info(`Removing presentation role from img id ${IMG_ID}`);
+ const shown = waitForEvent(EVENT_SHOW, IMG_ID);
+ await invokeSetAttribute(browser, IMG_ID, "role", "");
+ await shown;
+ let tree = {
+ role: ROLE_GRAPHIC,
+ name: ALT_TEXT,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that setting empty alt text on a hidden image does not crash.
+// See Bug 1799208 for more info.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png" hidden/>`,
+ async function (browser, accDoc) {
+ // Test initial state; should be no accessible since img is hidden.
+ const acc = findAccessibleChildByID(accDoc, IMG_ID);
+ ok(!acc, "Image has no Accessible");
+
+ // Add empty alt text. We shouldn't crash.
+ info(`Adding empty alt text "" to img id '${IMG_ID}'`);
+ await invokeContentTask(browser, [IMG_ID, "alt", ""], (id, attr, value) => {
+ let elm = content.document.getElementById(id);
+ elm.setAttribute(attr, value);
+ });
+ ok(true, "Setting empty alt text on a hidden image did not crash");
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_imagemap.js b/accessible/tests/browser/e10s/browser_treeupdate_imagemap.js
new file mode 100644
index 0000000000..82fbd3427e
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_imagemap.js
@@ -0,0 +1,190 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+async function testImageMap(browser, accDoc) {
+ const id = "imgmap";
+ const acc = findAccessibleChildByID(accDoc, id);
+
+ /* ================= Initial tree test ==================================== */
+ let tree = {
+ IMAGE_MAP: [{ role: ROLE_LINK, name: "b", children: [] }],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Insert area ========================================== */
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [], () => {
+ let areaElm = content.document.createElement("area");
+ let mapNode = content.document.getElementById("map");
+ areaElm.setAttribute(
+ "href",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ );
+ areaElm.setAttribute("coords", "0,0,13,14");
+ areaElm.setAttribute("alt", "a");
+ areaElm.setAttribute("shape", "rect");
+ mapNode.insertBefore(areaElm, mapNode.firstChild);
+ });
+ await onReorder;
+
+ tree = {
+ IMAGE_MAP: [
+ { role: ROLE_LINK, name: "a", children: [] },
+ { role: ROLE_LINK, name: "b", children: [] },
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Append area ========================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [], () => {
+ let areaElm = content.document.createElement("area");
+ let mapNode = content.document.getElementById("map");
+ areaElm.setAttribute(
+ "href",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#c"
+ );
+ areaElm.setAttribute("coords", "34,0,47,14");
+ areaElm.setAttribute("alt", "c");
+ areaElm.setAttribute("shape", "rect");
+ mapNode.appendChild(areaElm);
+ });
+ await onReorder;
+
+ tree = {
+ IMAGE_MAP: [
+ { role: ROLE_LINK, name: "a", children: [] },
+ { role: ROLE_LINK, name: "b", children: [] },
+ { role: ROLE_LINK, name: "c", children: [] },
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Remove area ========================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [], () => {
+ let mapNode = content.document.getElementById("map");
+ mapNode.removeChild(mapNode.firstElementChild);
+ });
+ await onReorder;
+
+ tree = {
+ IMAGE_MAP: [
+ { role: ROLE_LINK, name: "b", children: [] },
+ { role: ROLE_LINK, name: "c", children: [] },
+ ],
+ };
+ testAccessibleTree(acc, tree);
+}
+
+async function testContainer(browser) {
+ const id = "container";
+ /* ================= Remove name on map =================================== */
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeSetAttribute(browser, "map", "name");
+ let event = await onReorder;
+ const acc = event.accessible;
+
+ let tree = {
+ SECTION: [{ GRAPHIC: [] }],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Restore name on map ================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeSetAttribute(browser, "map", "name", "atoz_map");
+ // XXX: force repainting of the image (see bug 745788 for details).
+ await invokeContentTask(browser, [], () => {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.synthesizeMouse(
+ content.document.getElementById("imgmap"),
+ 10,
+ 10,
+ { type: "mousemove" },
+ content
+ );
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ {
+ IMAGE_MAP: [{ LINK: [] }, { LINK: [] }],
+ },
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Remove map =========================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [], () => {
+ let mapNode = content.document.getElementById("map");
+ mapNode.remove();
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [{ GRAPHIC: [] }],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Insert map =========================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ let map = content.document.createElement("map");
+ let area = content.document.createElement("area");
+
+ map.setAttribute("name", "atoz_map");
+ map.setAttribute("id", "map");
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ area.setAttribute("href", "http://www.bbc.co.uk/radio4/atoz/index.shtml#b");
+ area.setAttribute("coords", "17,0,30,14");
+ area.setAttribute("alt", "b");
+ area.setAttribute("shape", "rect");
+
+ map.appendChild(area);
+ content.document.getElementById(contentId).appendChild(map);
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ {
+ IMAGE_MAP: [{ LINK: [] }],
+ },
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Hide image map ======================================= */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeSetStyle(browser, "imgmap", "display", "none");
+ await onReorder;
+
+ tree = {
+ SECTION: [],
+ };
+ testAccessibleTree(acc, tree);
+}
+
+addAccessibleTask(
+ "e10s/doc_treeupdate_imagemap.html",
+ async function (browser, accDoc) {
+ await waitForImageMap(browser, accDoc);
+ await testImageMap(browser, accDoc);
+ await testContainer(browser);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_list.js b/accessible/tests/browser/e10s/browser_treeupdate_list.js
new file mode 100644
index 0000000000..2ca14d5572
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_list.js
@@ -0,0 +1,52 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+async function setDisplayAndWaitForReorder(browser, value) {
+ let onReorder = waitForEvent(EVENT_REORDER, "ul");
+ await invokeSetStyle(browser, "li", "display", value);
+ return onReorder;
+}
+
+addAccessibleTask(
+ `
+ <ul id="ul">
+ <li id="li">item1</li>
+ </ul>`,
+ async function (browser, accDoc) {
+ let li = findAccessibleChildByID(accDoc, "li");
+ let bullet = li.firstChild;
+ let accTree = {
+ role: ROLE_LISTITEM,
+ children: [
+ {
+ role: ROLE_LISTITEM_MARKER,
+ children: [],
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ children: [],
+ },
+ ],
+ };
+ testAccessibleTree(li, accTree);
+
+ await setDisplayAndWaitForReorder(browser, "none");
+
+ ok(isDefunct(li), "Check that li is defunct.");
+ ok(isDefunct(bullet), "Check that bullet is defunct.");
+
+ let event = await setDisplayAndWaitForReorder(browser, "list-item");
+
+ testAccessibleTree(
+ findAccessibleChildByID(event.accessible, "li"),
+ accTree
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js b/accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js
new file mode 100644
index 0000000000..dd678d93fa
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js
@@ -0,0 +1,48 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ '<ol id="list"></ol>',
+ async function (browser, accDoc) {
+ let list = findAccessibleChildByID(accDoc, "list");
+
+ testAccessibleTree(list, {
+ role: ROLE_LIST,
+ children: [],
+ });
+
+ await invokeSetAttribute(
+ browser,
+ currentContentDoc(),
+ "contentEditable",
+ "true"
+ );
+ let onReorder = waitForEvent(EVENT_REORDER, "list");
+ await invokeContentTask(browser, [], () => {
+ let li = content.document.createElement("li");
+ li.textContent = "item";
+ content.document.getElementById("list").appendChild(li);
+ });
+ await onReorder;
+
+ testAccessibleTree(list, {
+ role: ROLE_LIST,
+ children: [
+ {
+ role: ROLE_LISTITEM,
+ children: [
+ { role: ROLE_LISTITEM_MARKER, name: "1. ", children: [] },
+ { role: ROLE_TEXT_LEAF, children: [] },
+ ],
+ },
+ ],
+ });
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_listener.js b/accessible/tests/browser/e10s/browser_treeupdate_listener.js
new file mode 100644
index 0000000000..735f7871af
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_listener.js
@@ -0,0 +1,38 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ '<span id="parent"><span id="child"></span></span>',
+ async function (browser, accDoc) {
+ is(
+ findAccessibleChildByID(accDoc, "parent"),
+ null,
+ "Check that parent is not accessible."
+ );
+ is(
+ findAccessibleChildByID(accDoc, "child"),
+ null,
+ "Check that child is not accessible."
+ );
+
+ let onReorder = waitForEvent(EVENT_REORDER, matchContentDoc);
+ // Add an event listener to parent.
+ await invokeContentTask(browser, [], () => {
+ content.window.dummyListener = () => {};
+ content.document
+ .getElementById("parent")
+ .addEventListener("click", content.window.dummyListener);
+ });
+ await onReorder;
+
+ let tree = { TEXT: [] };
+ testAccessibleTree(findAccessibleChildByID(accDoc, "parent"), tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_move.js b/accessible/tests/browser/e10s/browser_treeupdate_move.js
new file mode 100644
index 0000000000..8ed6188ef3
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_move.js
@@ -0,0 +1,84 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test moving Accessibles:
+ * 1. A moved Accessible keeps the same Accessible.
+ * 2. If the moved Accessible is focused, it remains focused.
+ * 3. A child of the moved Accessible also keeps the same Accessible.
+ * 4. A child removed at the same time as the move gets shut down.
+ */
+addAccessibleTask(
+ `
+<div id="scrollable" role="presentation" style="height: 1px;">
+ <div contenteditable id="textbox" role="textbox">
+ <h1 id="heading">Heading</h1>
+ <p id="para">Para</p>
+ </div>
+ <iframe id="iframe" src="https://example.com/"></iframe>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const textbox = findAccessibleChildByID(docAcc, "textbox");
+ const heading = findAccessibleChildByID(docAcc, "heading");
+ const para = findAccessibleChildByID(docAcc, "para");
+ const iframe = findAccessibleChildByID(docAcc, "iframe");
+ const iframeDoc = iframe.firstChild;
+ ok(iframeDoc, "iframe contains a document");
+
+ let focused = waitForEvent(EVENT_FOCUS, textbox);
+ textbox.takeFocus();
+ await focused;
+ testStates(textbox, STATE_FOCUSED, 0, 0, EXT_STATE_DEFUNCT);
+
+ let reordered = waitForEvent(EVENT_REORDER, docAcc);
+ await invokeContentTask(browser, [], () => {
+ // scrollable wasn't in the a11y tree, but this will force it to be created.
+ // textbox will be moved inside it.
+ content.document.getElementById("scrollable").style.overflow = "scroll";
+ content.document.getElementById("heading").remove();
+ });
+ await reordered;
+ // Despite the move, ensure textbox is still alive and is focused.
+ testStates(textbox, STATE_FOCUSED, 0, 0, EXT_STATE_DEFUNCT);
+ // Ensure para (a child of textbox) is also still alive.
+ ok(!isDefunct(para), "para is alive");
+ // heading was a child of textbox, but was removed when textbox
+ // was moved. Ensure it is dead.
+ ok(isDefunct(heading), "heading is dead");
+ // Ensure the iframe and its embedded document are alive.
+ ok(!isDefunct(iframe), "iframe is alive");
+ ok(!isDefunct(iframeDoc), "iframeDoc is alive");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test that moving a subtree containing an iframe doesn't cause assertions or
+ * crashes. Note that aria-owns moves Accessibles even if it is set before load.
+ */
+addAccessibleTask(
+ `
+<div id="container">
+ <iframe id="iframe"></iframe>
+ <div aria-owns="iframe"></div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const container = findAccessibleChildByID(docAcc, "container");
+ testAccessibleTree(container, {
+ SECTION: [{ SECTION: [{ INTERNAL_FRAME: [{ DOCUMENT: [] }] }] }],
+ });
+ },
+ { topLevel: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js b/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js
new file mode 100644
index 0000000000..ec7eed0919
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js
@@ -0,0 +1,100 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ '<select id="select"></select>',
+ async function (browser, accDoc) {
+ let select = findAccessibleChildByID(accDoc, "select");
+
+ let onEvent = waitForEvent(EVENT_REORDER, "select");
+ // Create a combobox with grouping and 2 standalone options
+ await invokeContentTask(browser, [], () => {
+ let doc = content.document;
+ let contentSelect = doc.getElementById("select");
+ let optGroup = doc.createElement("optgroup");
+
+ for (let i = 0; i < 2; i++) {
+ let opt = doc.createElement("option");
+ opt.value = i;
+ opt.text = "Option: Value " + i;
+ optGroup.appendChild(opt);
+ }
+ contentSelect.add(optGroup, null);
+
+ for (let i = 0; i < 2; i++) {
+ let opt = doc.createElement("option");
+ contentSelect.add(opt, null);
+ }
+ contentSelect.firstChild.firstChild.id = "option1Node";
+ });
+ let event = await onEvent;
+ let option1Node = findAccessibleChildByID(event.accessible, "option1Node");
+
+ let tree = {
+ COMBOBOX: [
+ {
+ COMBOBOX_LIST: [
+ {
+ GROUPING: [{ COMBOBOX_OPTION: [] }, { COMBOBOX_OPTION: [] }],
+ },
+ {
+ COMBOBOX_OPTION: [],
+ },
+ {
+ COMBOBOX_OPTION: [],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(select, tree);
+ ok(!isDefunct(option1Node), "option shouldn't be defunct");
+
+ onEvent = waitForEvent(EVENT_REORDER, "select");
+ // Remove grouping from combobox
+ await invokeContentTask(browser, [], () => {
+ let contentSelect = content.document.getElementById("select");
+ contentSelect.firstChild.remove();
+ });
+ await onEvent;
+
+ tree = {
+ COMBOBOX: [
+ {
+ COMBOBOX_LIST: [{ COMBOBOX_OPTION: [] }, { COMBOBOX_OPTION: [] }],
+ },
+ ],
+ };
+ testAccessibleTree(select, tree);
+ ok(
+ isDefunct(option1Node),
+ "removed option shouldn't be accessible anymore!"
+ );
+
+ onEvent = waitForEvent(EVENT_REORDER, "select");
+ // Remove all options from combobox
+ await invokeContentTask(browser, [], () => {
+ let contentSelect = content.document.getElementById("select");
+ while (contentSelect.length) {
+ contentSelect.remove(0);
+ }
+ });
+ await onEvent;
+
+ tree = {
+ COMBOBOX: [
+ {
+ COMBOBOX_LIST: [],
+ },
+ ],
+ };
+ testAccessibleTree(select, tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_removal.js b/accessible/tests/browser/e10s/browser_treeupdate_removal.js
new file mode 100644
index 0000000000..6b5246f0bf
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_removal.js
@@ -0,0 +1,58 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ "e10s/doc_treeupdate_removal.xhtml",
+ async function (browser, accDoc) {
+ ok(
+ isAccessible(findAccessibleChildByID(accDoc, "the_table")),
+ "table should be accessible"
+ );
+
+ // Move the_table element into hidden subtree.
+ let onReorder = waitForEvent(EVENT_REORDER, matchContentDoc);
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("the_displaynone")
+ .appendChild(content.document.getElementById("the_table"));
+ });
+ await onReorder;
+
+ ok(
+ !isAccessible(findAccessibleChildByID(accDoc, "the_table")),
+ "table in display none tree shouldn't be accessible"
+ );
+ ok(
+ !isAccessible(findAccessibleChildByID(accDoc, "the_row")),
+ "row shouldn't be accessible"
+ );
+
+ // Remove the_row element (since it did not have accessible, no event needed).
+ await invokeContentTask(browser, [], () => {
+ content.document.body.removeChild(
+ content.document.getElementById("the_row")
+ );
+ });
+
+ // make sure no accessibles have stuck around.
+ ok(
+ !isAccessible(findAccessibleChildByID(accDoc, "the_row")),
+ "row shouldn't be accessible"
+ );
+ ok(
+ !isAccessible(findAccessibleChildByID(accDoc, "the_table")),
+ "table shouldn't be accessible"
+ );
+ ok(
+ !isAccessible(findAccessibleChildByID(accDoc, "the_displayNone")),
+ "display none things shouldn't be accessible"
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js b/accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js
new file mode 100644
index 0000000000..a82fc4c04d
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js
@@ -0,0 +1,73 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+const snippet = `
+<select id="select">
+ <option>o1</option>
+ <optgroup label="g1">
+ <option>g1o1</option>
+ <option>g1o2</option>
+ </optgroup>
+ <optgroup label="g2">
+ <option>g2o1</option>
+ <option>g2o2</option>
+ </optgroup>
+ <option>o2</option>
+</select>
+`;
+
+addAccessibleTask(
+ snippet,
+ async function (browser, accDoc) {
+ await invokeFocus(browser, "select");
+ // Expand the select. A dropdown item should get focus.
+ // Note that the dropdown is rendered in the parent process.
+ let focused = waitForEvent(
+ EVENT_FOCUS,
+ event => event.accessible.role == ROLE_COMBOBOX_OPTION,
+ "Dropdown item focused after select expanded"
+ );
+ await invokeContentTask(browser, [], () => {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.synthesizeKey("VK_DOWN", { altKey: true }, content);
+ });
+ info("Waiting for parent focus");
+ let event = await focused;
+ let dropdown = event.accessible.parent;
+
+ let selectedOptionChildren = [];
+ if (MAC) {
+ // Checkmark is part of the Mac menu styling.
+ selectedOptionChildren = [{ STATICTEXT: [] }];
+ }
+ let tree = {
+ COMBOBOX_LIST: [
+ { COMBOBOX_OPTION: selectedOptionChildren },
+ { GROUPING: [{ COMBOBOX_OPTION: [] }, { COMBOBOX_OPTION: [] }] },
+ { GROUPING: [{ COMBOBOX_OPTION: [] }, { COMBOBOX_OPTION: [] }] },
+ { COMBOBOX_OPTION: [] },
+ ],
+ };
+ testAccessibleTree(dropdown, tree);
+
+ // Collapse the select. Focus should return to the select.
+ focused = waitForEvent(
+ EVENT_FOCUS,
+ "select",
+ "select focused after collapsed"
+ );
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, window);
+ info("Waiting for child focus");
+ await focused;
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_table.js b/accessible/tests/browser/e10s/browser_treeupdate_table.js
new file mode 100644
index 0000000000..c188a06044
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_table.js
@@ -0,0 +1,48 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ `
+ <table id="table">
+ <tr>
+ <td>cell1</td>
+ <td>cell2</td>
+ </tr>
+ </table>`,
+ async function (browser, accDoc) {
+ let table = findAccessibleChildByID(accDoc, "table");
+
+ let tree = {
+ TABLE: [
+ { ROW: [{ CELL: [{ TEXT_LEAF: [] }] }, { CELL: [{ TEXT_LEAF: [] }] }] },
+ ],
+ };
+ testAccessibleTree(table, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, "table");
+ await invokeContentTask(browser, [], () => {
+ // append a caption, it should appear as a first element in the
+ // accessible tree.
+ let doc = content.document;
+ let caption = doc.createElement("caption");
+ caption.textContent = "table caption";
+ doc.getElementById("table").appendChild(caption);
+ });
+ await onReorder;
+
+ tree = {
+ TABLE: [
+ { CAPTION: [{ TEXT_LEAF: [] }] },
+ { ROW: [{ CELL: [{ TEXT_LEAF: [] }] }, { CELL: [{ TEXT_LEAF: [] }] }] },
+ ],
+ };
+ testAccessibleTree(table, tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_textleaf.js b/accessible/tests/browser/e10s/browser_treeupdate_textleaf.js
new file mode 100644
index 0000000000..0c617e7026
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_textleaf.js
@@ -0,0 +1,38 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+async function removeTextData(browser, accessible, id, role) {
+ let tree = {
+ role,
+ children: [{ role: ROLE_TEXT_LEAF, name: "text" }],
+ };
+ testAccessibleTree(accessible, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ content.document.getElementById(contentId).firstChild.textContent = "";
+ });
+ await onReorder;
+
+ tree = { role, children: [] };
+ testAccessibleTree(accessible, tree);
+}
+
+addAccessibleTask(
+ `
+ <p id="p">text</p>
+ <pre id="pre">text</pre>`,
+ async function (browser, accDoc) {
+ let p = findAccessibleChildByID(accDoc, "p");
+ let pre = findAccessibleChildByID(accDoc, "pre");
+ await removeTextData(browser, p, "p", ROLE_PARAGRAPH);
+ await removeTextData(browser, pre, "pre", ROLE_TEXT_CONTAINER);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_visibility.js b/accessible/tests/browser/e10s/browser_treeupdate_visibility.js
new file mode 100644
index 0000000000..636a00e210
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_visibility.js
@@ -0,0 +1,342 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+async function testTreeOnHide(browser, accDoc, containerID, id, before, after) {
+ let acc = findAccessibleChildByID(accDoc, containerID);
+ testAccessibleTree(acc, before);
+
+ let onReorder = waitForEvent(EVENT_REORDER, containerID);
+ await invokeSetStyle(browser, id, "visibility", "hidden");
+ await onReorder;
+
+ testAccessibleTree(acc, after);
+}
+
+async function test3(browser, accessible) {
+ let tree = {
+ SECTION: [
+ // container
+ {
+ SECTION: [
+ // parent
+ {
+ SECTION: [
+ // child
+ { TEXT_LEAF: [] },
+ ],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ // parent2
+ {
+ SECTION: [
+ // child2
+ { TEXT_LEAF: [] },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(accessible, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, "t3_container");
+ await invokeContentTask(browser, [], () => {
+ let doc = content.document;
+ doc.getElementById("t3_container").style.color = "red";
+ doc.getElementById("t3_parent").style.visibility = "hidden";
+ doc.getElementById("t3_parent2").style.visibility = "hidden";
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ // container
+ {
+ SECTION: [
+ // child
+ { TEXT_LEAF: [] },
+ ],
+ },
+ {
+ SECTION: [
+ // child2
+ { TEXT_LEAF: [] },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(accessible, tree);
+}
+
+async function test4(browser, accessible) {
+ let tree = {
+ SECTION: [{ TABLE: [{ ROW: [{ CELL: [] }] }] }],
+ };
+ testAccessibleTree(accessible, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, "t4_parent");
+ await invokeContentTask(browser, [], () => {
+ let doc = content.document;
+ doc.getElementById("t4_container").style.color = "red";
+ doc.getElementById("t4_child").style.visibility = "visible";
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ {
+ TABLE: [
+ {
+ ROW: [
+ {
+ CELL: [
+ {
+ SECTION: [
+ {
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(accessible, tree);
+}
+
+addAccessibleTask(
+ "e10s/doc_treeupdate_visibility.html",
+ async function (browser, accDoc) {
+ let t3Container = findAccessibleChildByID(accDoc, "t3_container");
+ let t4Container = findAccessibleChildByID(accDoc, "t4_container");
+
+ await testTreeOnHide(
+ browser,
+ accDoc,
+ "t1_container",
+ "t1_parent",
+ {
+ SECTION: [
+ {
+ SECTION: [
+ {
+ SECTION: [{ TEXT_LEAF: [] }],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ SECTION: [{ TEXT_LEAF: [] }],
+ },
+ ],
+ }
+ );
+
+ await testTreeOnHide(
+ browser,
+ accDoc,
+ "t2_container",
+ "t2_grandparent",
+ {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // grand parent
+ SECTION: [
+ {
+ SECTION: [
+ {
+ // child
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // child2
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // child
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // child2
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ }
+ );
+
+ await test3(browser, t3Container);
+ await test4(browser, t4Container);
+
+ await testTreeOnHide(
+ browser,
+ accDoc,
+ "t5_container",
+ "t5_subcontainer",
+ {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // subcontainer
+ TABLE: [
+ {
+ ROW: [
+ {
+ CELL: [
+ {
+ SECTION: [
+ {
+ // child
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // child
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ }
+ );
+
+ await testTreeOnHide(
+ browser,
+ accDoc,
+ "t6_container",
+ "t6_subcontainer",
+ {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // subcontainer
+ TABLE: [
+ {
+ ROW: [
+ {
+ CELL: [
+ {
+ TABLE: [
+ {
+ // nested table
+ ROW: [
+ {
+ CELL: [
+ {
+ SECTION: [
+ {
+ // child
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // child2
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // child
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // child2
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ }
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_whitespace.js b/accessible/tests/browser/e10s/browser_treeupdate_whitespace.js
new file mode 100644
index 0000000000..78ab47cd51
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_whitespace.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/. */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ "e10s/doc_treeupdate_whitespace.html",
+ async function (browser, accDoc) {
+ let container1 = findAccessibleChildByID(accDoc, "container1");
+ let container2Parent = findAccessibleChildByID(accDoc, "container2-parent");
+
+ let tree = {
+ SECTION: [
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] },
+ ],
+ };
+ testAccessibleTree(container1, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, "container1");
+ // Remove img1 from container1
+ await invokeContentTask(browser, [], () => {
+ let doc = content.document;
+ doc.getElementById("container1").removeChild(doc.getElementById("img1"));
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [{ GRAPHIC: [] }, { TEXT_LEAF: [] }, { GRAPHIC: [] }],
+ };
+ testAccessibleTree(container1, tree);
+
+ tree = {
+ SECTION: [{ LINK: [] }, { LINK: [{ GRAPHIC: [] }] }],
+ };
+ testAccessibleTree(container2Parent, tree);
+
+ onReorder = waitForEvent(EVENT_REORDER, "container2-parent");
+ // Append an img with valid src to container2
+ await invokeContentTask(browser, [], () => {
+ let doc = content.document;
+ let img = doc.createElement("img");
+ img.setAttribute(
+ "src",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/a11y/accessible/tests/mochitest/moz.png"
+ );
+ doc.getElementById("container2").appendChild(img);
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ { LINK: [{ GRAPHIC: [] }] },
+ { TEXT_LEAF: [] },
+ { LINK: [{ GRAPHIC: [] }] },
+ ],
+ };
+ testAccessibleTree(container2Parent, tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html b/accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html
new file mode 100644
index 0000000000..089391523d
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html
@@ -0,0 +1,23 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update ARIA Dialog Test</title>
+ </head>
+ <body id="body">
+ <div id="dialog" role="dialog" style="display: none;">
+ <table id="table" role="presentation"
+ style="display: block; position: absolute; top: 88px; left: 312.5px; z-index: 10010;">
+ <tbody>
+ <tr>
+ <td role="presentation">
+ <div role="presentation">
+ <a id="a" role="button">text</a>
+ </div>
+ <input id="input">
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html b/accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html
new file mode 100644
index 0000000000..38b5c333a1
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html
@@ -0,0 +1,44 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update ARIA Owns Test</title>
+ </head>
+ <body id="body">
+ <div id="t1_container" aria-owns="t1_checkbox t1_button">
+ <div role="button" id="t1_button"></div>
+ <div role="checkbox" id="t1_checkbox">
+ <span id="t1_span">
+ <div id="t1_subdiv"></div>
+ </span>
+ </div>
+ </div>
+ <div id="t1_group" role="group"></div>
+ <div id="t1_grouptmp" role="group"></div>
+
+ <div id="t2_container1" aria-owns="t2_owned"></div>
+ <div id="t2_container2">
+ <div id="t2_container3"><div id="t2_owned" role="checkbox"></div></div>
+ </div>
+
+ <div id="t3_container1" aria-owns="t3_child"></div>
+ <div id="t3_child" role="checkbox"></div>
+ <div id="t3_container2"></div>
+
+ <div id="t4_container1" aria-owns="t4_child1 t4_child2"></div>
+ <div id="t4_container2">
+ <div id="t4_child1" style="display:none" role="checkbox"></div>
+ <div id="t4_child2" role="radio"></div>
+ </div>
+
+ <div id="t5_container">
+ <div role="button" id="t5_button"></div>
+ <div role="checkbox" id="t5_checkbox"></div>
+ <div role="radio" id="t5_radio"></div>
+ </div>
+
+ <div id="t6_container" aria-owns="t6_fake">
+ <span id="t6_span">hey</span>
+ </div>
+ <div id="t6_fake" role="group"></div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_imagemap.html b/accessible/tests/browser/e10s/doc_treeupdate_imagemap.html
new file mode 100644
index 0000000000..4dd230fc28
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_imagemap.html
@@ -0,0 +1,21 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update Imagemap Test</title>
+ </head>
+ <body id="body">
+ <map name="atoz_map" id="map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ </map>
+
+ <div id="container">
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="http://example.com/a11y/accessible/tests/mochitest/letters.gif"><!--
+ Important: no whitespace between the <img> and the </div>, so we
+ don't end up with textframes there, because those would be reflected
+ in our accessible tree in some cases.
+ --></div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml b/accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml
new file mode 100644
index 0000000000..9c59fb9d11
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update Removal Test</title>
+ </head>
+ <body id="body">
+ <div id="the_displaynone" style="display: none;"></div>
+ <table id="the_table"></table>
+ <tr id="the_row"></tr>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_visibility.html b/accessible/tests/browser/e10s/doc_treeupdate_visibility.html
new file mode 100644
index 0000000000..00213b2b70
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_visibility.html
@@ -0,0 +1,78 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update Visibility Test</title>
+ </head>
+ <body id="body">
+ <!-- hide parent while child stays visible -->
+ <div id="t1_container">
+ <div id="t1_parent">
+ <div id="t1_child" style="visibility: visible">text</div>
+ </div>
+ </div>
+
+ <!-- hide grandparent while its children stay visible -->
+ <div id="t2_container">
+ <div id="t2_grandparent">
+ <div id="t2_parent">
+ <div id="t2_child" style="visibility: visible">text</div>
+ <div id="t2_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- change container style, hide parents while their children stay visible -->
+ <div id="t3_container">
+ <div id="t3_parent">
+ <div id="t3_child" style="visibility: visible">text</div>
+ </div>
+ <div id="t3_parent2">
+ <div id="t3_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+
+ <!-- change container style, show child inside the table -->
+ <div id="t4_container">
+ <table>
+ <tr>
+ <td id="t4_parent">
+ <div id="t4_child" style="visibility: hidden;">text</div>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ <!-- hide subcontainer while child inside the table stays visible -->
+ <div id="t5_container">
+ <div id="t5_subcontainer">
+ <table>
+ <tr>
+ <td>
+ <div id="t5_child" style="visibility: visible;">text</div>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <!-- hide subcontainer while its child and child inside the nested table stays visible -->
+ <div id="t6_container">
+ <div id="t6_subcontainer">
+ <table>
+ <tr>
+ <td>
+ <table>
+ <tr>
+ <td>
+ <div id="t6_child" style="visibility: visible;">text</div>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <div id="t6_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_whitespace.html b/accessible/tests/browser/e10s/doc_treeupdate_whitespace.html
new file mode 100644
index 0000000000..f17dbbd60e
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_whitespace.html
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf-8"/>
+ <title>Whitespace text accessible creation/destruction</title>
+ </head>
+ <body id="body">
+ <div id="container1"> <img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> <img id="img1" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> <img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> </div>
+ <div id="container2-parent"> <a id="container2"></a> <a><img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"></a> </div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/fonts/Ahem.sjs b/accessible/tests/browser/e10s/fonts/Ahem.sjs
new file mode 100644
index 0000000000..e801a801ab
--- /dev/null
+++ b/accessible/tests/browser/e10s/fonts/Ahem.sjs
@@ -0,0 +1,241 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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";
+
+/*
+ * A CORS-enabled font resource.
+ */
+
+const FONT_BYTES = atob(
+ "AAEAAAALAIAAAwAwT1MvMnhQSo0AAAE4AAAAYGNtYXAP1hZGAAAFbAAABnJnYXNwABcACQAAMLAA" +
+ "AAAQZ2x5ZkmzdNoAAAvgAAAaZGhlYWTWok4cAAAAvAAAADZoaGVhBwoEFgAAAPQAAAAkaG10eLkg" +
+ "AH0AAAGYAAAD1GxvY2EgdSciAAAmRAAAAextYXhwAPgACQAAARgAAAAgbmFtZX4UjLgAACgwAAAG" +
+ "aHBvc3SN0B2KAAAumAAAAhgAAQAAAAEAQhIXUWdfDzz1AAkD6AAAAACzb19ZAAAAAMAtq0kAAP84" +
+ "A+gDIAAAAAMAAgAAAAAAAAABAAADIP84AAAD6AAAAAAD6AABAAAAAAAAAAAAAAAAAAAA9QABAAAA" +
+ "9QAIAAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAID6AGQAAUAAAK8AooAAACPArwCigAAAcUAMgED" +
+ "AAACAAQJAAAAAAAAgAAArxAAIEgAAAAAAAAAAFczQwAAQAAg8AIDIP84AAADIADIIAABEUAAAAAD" +
+ "IAMgAAAAIAAAA+gAfQAAAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAA" +
+ "A+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD" +
+ "6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPo" +
+ "AAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gA" +
+ "AAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAA" +
+ "A+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD" +
+ "6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPo" +
+ "AAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gA" +
+ "AAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAA" +
+ "A+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD" +
+ "6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPo" +
+ "AAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gA" +
+ "AAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAA" +
+ "A+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD" +
+ "6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPo" +
+ "AAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gA" +
+ "AAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAA" +
+ "A+gAAAPoAAAD6AAAA+gAAAPoAAAAAAADAAAAAwAABEwAAQAAAAAAHAADAAEAAAImAAYCCgAAAAAB" +
+ "AAABAAAAAAAAAAAAAAAAAAAAAQACAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAQAAAAAAAwAEAAUABgAHAAgACQAAAAoACwAMAA0ADgAPABAAEQASABMAFAAVABYAFwAYABkA" +
+ "GgAbABwAHQAeAB8AIAAhACIAIwAkACUAJgAnACgAKQAqACsALAAtAC4ALwAwADEAMgAzADQANQA2" +
+ "ADcAOAA5ADoAOwA8AD0APgA/AEAAQQBCAEMARABFAEYARwBIAEkASgBLAEwATQBOAE8AUABRAFIA" +
+ "UwBUAFUAVgBXAFgAWQBaAFsAXABdAF4AXwBgAAAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBu" +
+ "AG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAANsAgQCCAIMAhADdAIUAhgCHAIgA" +
+ "4wCJAIoA6gCLAIwA6ACNAOsA7ACOAI8A5ADmAOUA1ADpAJAAkQDTAJIAkwCUAJUAlgDnANEA7QDS" +
+ "AJcAmADeAAMAmgCbAJwAzgDPANUA1gDYANkAnQCeAJ8A7gCgANAA4gChAOAA4QAAAAAA3ACiANcA" +
+ "2gDfAKMApAClAKYApwCoAKkAqgCrAKwArQAAAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8" +
+ "AAQCJgAAAE4AQAAFAA4AJgB+AP8BMQFTAXgBkgLHAskC3QOUA6kDvAPAIBAgFCAaIB4gIiAmIDAg" +
+ "OiBEISIhJiICIgYiDyISIhoiHiIrIkgiYCJlIvIlyvAC//8AAAAgACgAoAExAVIBeAGSAsYCyQLY" +
+ "A5QDqQO8A8AgECATIBggHCAgICYgMCA5IEQhIiEmIgIiBiIPIhEiGSIeIisiSCJgImQi8iXK8AD/" +
+ "///j/+IAAP+B/3z/WP8/AAD97AAA/T79KvzT/RTf/+DCAADgvOC74Ljgr+Cn4J7fwd+t3uLezN7W" +
+ "AAAAAN7K3r7epd6K3ofd+9skEO8AAQAAAAAASgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAP4A" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwA7gAAAAAAAAAAAAAAAAAAAAAAAACZAJUAggCDAKEAjgC9" +
+ "AIQAigCIAJAAlwCWAMQAhwC1AIEAjQDHAMgAiQCPAIUAogC5AMYAkQCYAMoAyQDLAJQAmgClAKMA" +
+ "mwBhAGIAiwBjAKcAZACkAKYAqwCoAKkAqgC+AGUArgCsAK0AnABmAMUAjACxAK8AsABnAMAAwgCG" +
+ "AGkAaABqAGwAawBtAJIAbgBwAG8AcQByAHQAcwB1AHYAvwB3AHkAeAB6AHwAewCfAJMAfgB9AH8A" +
+ "gADBAMMAoACzALwAtgC3ALgAuwC0ALoAnQCeANcA5gDEAKIA5wAEAiYAAABOAEAABQAOACYAfgD/" +
+ "ATEBUwF4AZICxwLJAt0DlAOpA7wDwCAQIBQgGiAeICIgJiAwIDogRCEiISYiAiIGIg8iEiIaIh4i" +
+ "KyJIImAiZSLyJcrwAv//AAAAIAAoAKABMQFSAXgBkgLGAskC2AOUA6kDvAPAIBAgEyAYIBwgICAm" +
+ "IDAgOSBEISIhJiICIgYiDyIRIhkiHiIrIkgiYCJkIvIlyvAA////4//iAAD/gf98/1j/PwAA/ewA" +
+ "AP0+/Sr80/0U3//gwgAA4Lzgu+C44K/gp+Ce38Hfrd7i3sze1gAAAADeyt6+3qXeit6H3fvbJBDv" +
+ "AAEAAAAAAEoAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAD+AAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AADsAO4AAAAAAAAAAAAAAAAAAAAAAAAAmQCVAIIAgwChAI4AvQCEAIoAiACQAJcAlgDEAIcAtQCB" +
+ "AI0AxwDIAIkAjwCFAKIAuQDGAJEAmADKAMkAywCUAJoApQCjAJsAYQBiAIsAYwCnAGQApACmAKsA" +
+ "qACpAKoAvgBlAK4ArACtAJwAZgDFAIwAsQCvALAAZwDAAMIAhgBpAGgAagBsAGsAbQCSAG4AcABv" +
+ "AHEAcgB0AHMAdQB2AL8AdwB5AHgAegB8AHsAnwCTAH4AfQB/AIAAwQDDAKAAswC8ALYAtwC4ALsA" +
+ "tAC6AJ0AngDXAOYAxACiAOcAAAACAH0AAANrAyAAAwAHAAAzESERJSERIX0C7v2PAfT+DAMg/OB9" +
+ "AiYAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgA" +
+ "AAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAA" +
+ "AAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAA" +
+ "AQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAAB" +
+ "AAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEA" +
+ "AP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA" +
+ "/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/" +
+ "OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84" +
+ "A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD" +
+ "6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPo" +
+ "AyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gD" +
+ "IAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMg" +
+ "AAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAA" +
+ "AwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAAD" +
+ "AAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMA" +
+ "ABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAA" +
+ "ESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAAR" +
+ "IREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEh" +
+ "ESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESER" +
+ "IQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREh" +
+ "A+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED" +
+ "6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo" +
+ "/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8" +
+ "GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwY" +
+ "AyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgD" +
+ "IPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg" +
+ "/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8" +
+ "GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwY" +
+ "AAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgA" +
+ "AAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAA" +
+ "AAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAA" +
+ "AQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAAB" +
+ "AAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEA" +
+ "AP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA" +
+ "/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/" +
+ "OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84" +
+ "A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD" +
+ "6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPo" +
+ "AyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AAAAAMAADEhFSED6PwYyAAAAQAA/zgD6AMgAAMA" +
+ "ABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAA" +
+ "ESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAAR" +
+ "IREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEh" +
+ "ESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESER" +
+ "IQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREh" +
+ "A+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED" +
+ "6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo" +
+ "/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8" +
+ "GAMg/BgAAAABAAAAAAPoAyAAAwAAESERIQPo/BgDIPzgAAAAAQAA/zgD6AMgAAMAABEhESED6PwY" +
+ "AyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgD" +
+ "IPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg" +
+ "/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8" +
+ "GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwY" +
+ "AAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgA" +
+ "AAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAA" +
+ "AAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAA" +
+ "AQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAAB" +
+ "AAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEA" +
+ "AP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA" +
+ "/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/" +
+ "OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84" +
+ "A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD" +
+ "6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPo" +
+ "AyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gD" +
+ "IAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMg" +
+ "AAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAA" +
+ "AwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAAD" +
+ "AAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMA" +
+ "ABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAA" +
+ "ESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAAR" +
+ "IREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEh" +
+ "ESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESER" +
+ "IQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREh" +
+ "A+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED" +
+ "6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo" +
+ "/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8" +
+ "GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwY" +
+ "AyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgD" +
+ "IPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg" +
+ "/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8" +
+ "GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwY" +
+ "AAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgA" +
+ "AAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAA" +
+ "AAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAA" +
+ "AQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAAB" +
+ "AAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEA" +
+ "AP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA" +
+ "/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/" +
+ "OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84" +
+ "A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD" +
+ "6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPo" +
+ "AyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gD" +
+ "IAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMg" +
+ "AAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAA" +
+ "AwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAAD" +
+ "AAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMA" +
+ "ABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAA" +
+ "ESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAAR" +
+ "IREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEh" +
+ "ESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESER" +
+ "IQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREh" +
+ "A+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED" +
+ "6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo" +
+ "/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8" +
+ "GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwY" +
+ "AyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgD" +
+ "IPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg" +
+ "/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8" +
+ "GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwY" +
+ "AAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgA" +
+ "AAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAA" +
+ "AAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAA" +
+ "AQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAAB" +
+ "AAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEA" +
+ "AP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA" +
+ "/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/" +
+ "OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84" +
+ "A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD" +
+ "6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAAAABQAFAAU" +
+ "ABQAIgAwAD4ATABaAGgAdgCEAJIAoACuALwAygDYAOYA9AECARABHgEsAToBSAFWAWQBcgGAAY4B" +
+ "nAGqAbgBxgHUAeIB8AH+AgwCGgIoAjYCRAJSAmACbgJ8AooCmAKmArQCwgLQAt4C7AL6AwgDFgMk" +
+ "AzIDQANOA1wDagN4A4YDlAOiA7ADvgPMA9oD6AP2BAQEEgQgBC4EPARKBFgEZARyBIAEjgScBKoE" +
+ "uATGBNQE4gTwBP4FDAUaBSgFNgVEBVIFYAVuBXwFigWYBaYFtAXCBdAF3gXsBfoGCAYWBiQGMgZA" +
+ "Bk4GXAZqBngGhgaUBqIGsAa+BswG2gboBvYHBAcSByAHLgc8B0oHWAdmB3QHggeQB54HrAe6B8gH" +
+ "1gfkB/IIAAgOCBwIKgg4CDgIRghUCGIIcAh+CIwImgioCLYIxAjSCOAI7gj8CQoJGAkmCTQJQglQ" +
+ "CV4JbAl6CYgJlgmkCbIJwAnOCdwJ6gn4CgYKFAoiCjAKPgpMCloKaAp2CoQKkgqgCq4KvArKCtgK" +
+ "5gr0CwILEAseCywLOgtIC1YLZAtyC4ALjgucC6oLuAvGC9QL4gvwC/4MDAwaDCgMNgxEDFIMYAxu" +
+ "DHwMigyYDKYMtAzCDNAM3gzsDPoNCA0WDSQNMgAAABsBSgAAAAAAAAAAAZ4AAAAAAAAAAAABAAgB" +
+ "ngAAAAAAAAACAA4BpgAAAAAAAAADACABtAAAAAAAAAAEAAgB1AAAAAAAAAAFABYB3AAAAAAAAAAG" +
+ "AAgB8gABAAAAAAAAAM8B+gABAAAAAAABAAQCyQABAAAAAAACAAcCzQABAAAAAAADABAC1AABAAAA" +
+ "AAAEAAQC5AABAAAAAAAFAAsC6AABAAAAAAAGAAQC8wABAAAAAAAQAAQC9wABAAAAAAARAAcC+wAB" +
+ "AAAAAAASAAQDAgADAAEECQAAAZ4DBgADAAEECQABAAgEpAADAAEECQACAA4ErAADAAEECQADACAE" +
+ "ugADAAEECQAEAAgE2gADAAEECQAFABYE4gADAAEECQAGAAgE+AADAAEECQAQAAgFAAADAAEECQAR" +
+ "AA4FCAADAAEECQASAAgFFgBNAG8AcwB0ACAAYwBoAGEAcgBhAGMAdABlAHIAcwAgAGEAcgBlACAA" +
+ "dABoAGUAIABlAG0AIABzAHEAdQBhAHIAZQAsACAAZQB4AGMAZQBwAHQAIAAmAEUAQQBjAHUAdABl" +
+ "ACAAYQBuAGQAIAAiAHAAIgAsACAAdwBoAGkAYwBoACAAcwBoAG8AdwAgAGEAcwBjAGUAbgB0AC8A" +
+ "ZABlAHMAYwBlAG4AdAAgAGYAcgBvAG0AIAB0AGgAZQAgAGIAYQBzAGUAbABpAG4AZQAuACAAVQBz" +
+ "AGUAZgB1AGwAIABmAG8AcgAgAHQAZQBzAHQAaQBuAGcAIABjAG8AbQBwAG8AcwBpAHQAaQBvAG4A" +
+ "IABzAHkAcwB0AGUAbQBzAC4AIABQAHIAbwBkAHUAYwBlAGQAIABiAHkAIABUAG8AZABkACAARgBh" +
+ "AGgAcgBuAGUAcgAgAGYAbwByACAAdABoAGUAIABDAFMAUwAgAFMAYQBtAHUAcgBhAGkAJwBzACAA" +
+ "YgByAG8AdwBzAGUAcgAgAHQAZQBzAHQAaQBuAGcALgBBAGgAZQBtAFIAZQBnAHUAbABhAHIAVgBl" +
+ "AHIAcwBpAG8AbgAgADEALgAxACAAQQBoAGUAbQBBAGgAZQBtAFYAZQByAHMAaQBvAG4AIAAxAC4A" +
+ "MQBBAGgAZQBtTW9zdCBjaGFyYWN0ZXJzIGFyZSB0aGUgZW0gc3F1YXJlLCBleGNlcHQgJkVBY3V0" +
+ "ZSBhbmQgInAiLCB3aGljaCBzaG93IGFzY2VudC9kZXNjZW50IGZyb20gdGhlIGJhc2VsaW5lLiBV" +
+ "c2VmdWwgZm9yIHRlc3RpbmcgY29tcG9zaXRpb24gc3lzdGVtcy4gUHJvZHVjZWQgYnkgVG9kZCBG" +
+ "YWhybmVyIGZvciB0aGUgQ1NTIFNhbXVyYWkncyBicm93c2VyIHRlc3RpbmcuQWhlbVJlZ3VsYXJW" +
+ "ZXJzaW9uIDEuMSBBaGVtQWhlbVZlcnNpb24gMS4xQWhlbUFoZW1SZWd1bGFyQWhlbQBNAG8AcwB0" +
+ "ACAAYwBoAGEAcgBhAGMAdABlAHIAcwAgAGEAcgBlACAAdABoAGUAIABlAG0AIABzAHEAdQBhAHIA" +
+ "ZQAsACAAZQB4AGMAZQBwAHQAIAAmAEUAQQBjAHUAdABlACAAYQBuAGQAIAAiAHAAIgAsACAAdwBo" +
+ "AGkAYwBoACAAcwBoAG8AdwAgAGEAcwBjAGUAbgB0AC8AZABlAHMAYwBlAG4AdAAgAGYAcgBvAG0A" +
+ "IAB0AGgAZQAgAGIAYQBzAGUAbABpAG4AZQAuACAAVQBzAGUAZgB1AGwAIABmAG8AcgAgAHQAZQBz" +
+ "AHQAaQBuAGcAIABjAG8AbQBwAG8AcwBpAHQAaQBvAG4AIABzAHkAcwB0AGUAbQBzAC4AIABQAHIA" +
+ "bwBkAHUAYwBlAGQAIABiAHkAIABUAG8AZABkACAARgBhAGgAcgBuAGUAcgAgAGYAbwByACAAdABo" +
+ "AGUAIABDAFMAUwAgAFMAYQBtAHUAcgBhAGkAJwBzACAAYgByAG8AdwBzAGUAcgAgAHQAZQBzAHQA" +
+ "aQBuAGcALgBBAGgAZQBtAFIAZQBnAHUAbABhAHIAVgBlAHIAcwBpAG8AbgAgADEALgAxACAAQQBo" +
+ "AGUAbQBBAGgAZQBtAFYAZQByAHMAaQBvAG4AIAAxAC4AMQBBAGgAZQBtAEEAaABlAG0AUgBlAGcA" +
+ "dQBsAGEAcgBBAGgAZQBtAAIAAAAAAAD/ewAUAAAAAQAAAAAAAAAAAAAAAAAAAAAA9QAAAQIAAgAD" +
+ "AAQABQAGAAcACAAJAAsADAANAA4ADwAQABEAEgATABQAFQAWABcAGAAZABoAGwAcAB0AHgAfACAA" +
+ "IQAiACMAJAAlACYAJwAoACkAKgArACwALQAuAC8AMAAxADIAMwA0ADUANgA3ADgAOQA6ADsAPAA9" +
+ "AD4APwBAAEEAQgBDAEQARQBGAEcASABJAEoASwBMAE0ATgBPAFAAUQBSAFMAVABVAFYAVwBYAFkA" +
+ "WgBbAFwAXQBeAF8AYABhAGIAYwBkAGUAZgBnAGgAaQBqAGsAbABtAG4AbwBwAHEAcgBzAHQAdQB2" +
+ "AHcAeAB5AHoAewB8AH0AfgB/AIAAgQCDAIQAhQCGAIgAiQCKAIsAjQCOAJAAkQCTAJYAlwCdAJ4A" +
+ "oAChAKIAowCkAKkAqgCsAK0ArgCvALYAtwC4ALoAvQDDAMcAyADJAMoAywDMAM0AzgDPANAA0QDT" +
+ "ANQA1QDWANcA2ADZANoA2wDcAN0A3gDfAOAA4QDoAOkA6gDrAOwA7QDuAO8A8ADxAPIA8wD0APUA" +
+ "9gAAAAAAsACxALsApgCoAJ8AmwCyALMAxAC0ALUAxQCCAMIAhwCrAMYAvgC/ALwAjACYAJoAmQCl" +
+ "AJIAnACPAJQAlQCnALkA0gDAAMEBAwACAQQETlVMTAJIVANERUwAAAADAAgAAgAQAAH//wAD"
+);
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "application/octet-stream", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.write(FONT_BYTES);
+}
diff --git a/accessible/tests/browser/e10s/head.js b/accessible/tests/browser/e10s/head.js
new file mode 100644
index 0000000000..bdbcb7445f
--- /dev/null
+++ b/accessible/tests/browser/e10s/head.js
@@ -0,0 +1,192 @@
+/* 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";
+
+/* exported testCachedRelation, testRelated */
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js and relations.js.
+/* import-globals-from ../../mochitest/relations.js */
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR },
+ { name: "relations.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test the accessible relation.
+ *
+ * @param identifier [in] identifier to get an accessible, may be ID
+ * attribute or DOM element or accessible object
+ * @param relType [in] relation type (see constants above)
+ * @param relatedIdentifiers [in] identifier or array of identifiers of
+ * expected related accessibles
+ */
+async function testCachedRelation(identifier, relType, relatedIdentifiers) {
+ const relDescr = getRelationErrorMsg(identifier, relType);
+ const relDescrStart = getRelationErrorMsg(identifier, relType, true);
+ info(`Testing ${relDescr}`);
+
+ if (!relatedIdentifiers) {
+ await untilCacheOk(function () {
+ let r = getRelationByType(identifier, relType);
+ if (r) {
+ info(`Fetched ${r.targetsCount} relations from cache`);
+ } else {
+ info("Could not fetch relations");
+ }
+ return r && !r.targetsCount;
+ }, relDescrStart + " has no targets, as expected");
+ return;
+ }
+
+ const relatedIds =
+ relatedIdentifiers instanceof Array
+ ? relatedIdentifiers
+ : [relatedIdentifiers];
+ await untilCacheOk(function () {
+ let r = getRelationByType(identifier, relType);
+ if (r) {
+ info(
+ `Fetched ${r.targetsCount} relations from cache, looking for ${relatedIds.length}`
+ );
+ } else {
+ info("Could not fetch relations");
+ }
+
+ return r && r.targetsCount == relatedIds.length;
+ }, "Found correct number of expected relations");
+
+ let targets = [];
+ for (let idx = 0; idx < relatedIds.length; idx++) {
+ targets.push(getAccessible(relatedIds[idx]));
+ }
+
+ if (targets.length != relatedIds.length) {
+ return;
+ }
+
+ await untilCacheOk(function () {
+ const relation = getRelationByType(identifier, relType);
+ const actualTargets = relation ? relation.getTargets() : null;
+ if (!actualTargets) {
+ info("Could not fetch relations");
+ return false;
+ }
+
+ // Check if all given related accessibles are targets of obtained relation.
+ for (let idx = 0; idx < targets.length; idx++) {
+ let isFound = false;
+ for (let relatedAcc of actualTargets.enumerate(Ci.nsIAccessible)) {
+ if (targets[idx] == relatedAcc) {
+ isFound = true;
+ break;
+ }
+ }
+
+ if (!isFound) {
+ info(
+ prettyName(relatedIds[idx]) +
+ " could not be found in relation: " +
+ relDescr
+ );
+ return false;
+ }
+ }
+
+ return true;
+ }, "All given related accessibles are targets of fetched relation.");
+
+ await untilCacheOk(function () {
+ const relation = getRelationByType(identifier, relType);
+ const actualTargets = relation ? relation.getTargets() : null;
+ if (!actualTargets) {
+ info("Could not fetch relations");
+ return false;
+ }
+
+ // Check if all obtained targets are given related accessibles.
+ for (let relatedAcc of actualTargets.enumerate(Ci.nsIAccessible)) {
+ let wasFound = false;
+ for (let idx = 0; idx < targets.length; idx++) {
+ if (relatedAcc == targets[idx]) {
+ wasFound = true;
+ }
+ }
+ if (!wasFound) {
+ info(
+ prettyName(relatedAcc) +
+ " was found, but shouldn't be in relation: " +
+ relDescr
+ );
+ return false;
+ }
+ }
+ return true;
+ }, "No unexpected targets found.");
+}
+
+async function testRelated(
+ browser,
+ accDoc,
+ attr,
+ hostRelation,
+ dependantRelation
+) {
+ let host = findAccessibleChildByID(accDoc, "host");
+ let dependant1 = findAccessibleChildByID(accDoc, "dependant1");
+ let dependant2 = findAccessibleChildByID(accDoc, "dependant2");
+
+ /**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * attrs {?Array} an optional list of attributes to update
+ * expected {Array} expected relation values for dependant1, dependant2
+ * and host respectively.
+ * }
+ */
+ const tests = [
+ {
+ desc: "No attribute",
+ expected: [null, null, null],
+ },
+ {
+ desc: "Set attribute",
+ attrs: [{ key: attr, value: "dependant1" }],
+ expected: [host, null, dependant1],
+ },
+ {
+ desc: "Change attribute",
+ attrs: [{ key: attr, value: "dependant2" }],
+ expected: [null, host, dependant2],
+ },
+ {
+ desc: "Remove attribute",
+ attrs: [{ key: attr }],
+ expected: [null, null, null],
+ },
+ ];
+
+ for (let { desc, attrs, expected } of tests) {
+ info(desc);
+
+ if (attrs) {
+ for (let { key, value } of attrs) {
+ await invokeSetAttribute(browser, "host", key, value);
+ }
+ }
+
+ await testCachedRelation(dependant1, dependantRelation, expected[0]);
+ await testCachedRelation(dependant2, dependantRelation, expected[1]);
+ await testCachedRelation(host, hostRelation, expected[2]);
+ }
+}
diff --git a/accessible/tests/browser/events/browser.ini b/accessible/tests/browser/events/browser.ini
new file mode 100644
index 0000000000..576d3e53ab
--- /dev/null
+++ b/accessible/tests/browser/events/browser.ini
@@ -0,0 +1,24 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/browser/*.jsm
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_test_A11yUtils_announce.js]
+[browser_test_caret_move_granularity.js]
+[browser_test_docload.js]
+skip-if = true
+[browser_test_focus_browserui.js]
+[browser_test_focus_dialog.js]
+[browser_test_focus_urlbar.js]
+skip-if =
+ os == "linux" # Bug 1782783
+ os == "win" # Bug 1818994
+[browser_test_panel.js]
+[browser_test_scrolling.js]
+[browser_test_selection_urlbar.js]
+[browser_test_textcaret.js]
diff --git a/accessible/tests/browser/events/browser_test_A11yUtils_announce.js b/accessible/tests/browser/events/browser_test_A11yUtils_announce.js
new file mode 100644
index 0000000000..b2848f35c2
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_A11yUtils_announce.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+// Check that the browser A11yUtils.announce() function works correctly.
+// Note that this does not use mozilla::a11y::Accessible::Announce and a11y
+// announcement events, as these aren't yet supported on desktop.
+async function runTests() {
+ const alert = document.getElementById("a11y-announcement");
+ let alerted = waitForEvent(EVENT_ALERT, alert);
+ A11yUtils.announce({ raw: "first" });
+ let event = await alerted;
+ const alertAcc = event.accessible;
+ is(alertAcc.role, ROLE_ALERT);
+ ok(!alertAcc.name);
+ is(alertAcc.childCount, 1);
+ is(alertAcc.firstChild.name, "first");
+
+ alerted = waitForEvent(EVENT_ALERT, alertAcc);
+ A11yUtils.announce({ raw: "second" });
+ event = await alerted;
+ ok(!alertAcc.name);
+ is(alertAcc.childCount, 1);
+ is(alertAcc.firstChild.name, "second");
+
+ info("Testing Fluent message");
+ // We need a simple Fluent message here without arguments or attributes.
+ const fluentId = "search-one-offs-with-title";
+ const fluentMessage = await document.l10n.formatValue(fluentId);
+ alerted = waitForEvent(EVENT_ALERT, alertAcc);
+ A11yUtils.announce({ id: fluentId });
+ event = await alerted;
+ ok(!alertAcc.name);
+ is(alertAcc.childCount, 1);
+ is(alertAcc.firstChild.name, fluentMessage);
+
+ info("Ensuring Fluent message is cancelled if announce is re-entered");
+ alerted = waitForEvent(EVENT_ALERT, alertAcc);
+ // This call runs async.
+ let asyncAnnounce = A11yUtils.announce({ id: fluentId });
+ // Before the async call finishes, call announce again.
+ A11yUtils.announce({ raw: "third" });
+ // Wait for the async call to complete.
+ await asyncAnnounce;
+ event = await alerted;
+ ok(!alertAcc.name);
+ is(alertAcc.childCount, 1);
+ // The async call should have been cancelled. If it wasn't, we would get
+ // fluentMessage here instead of "third".
+ is(alertAcc.firstChild.name, "third");
+}
+
+addAccessibleTask(``, runTests);
diff --git a/accessible/tests/browser/events/browser_test_caret_move_granularity.js b/accessible/tests/browser/events/browser_test_caret_move_granularity.js
new file mode 100644
index 0000000000..c72ae42d85
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_caret_move_granularity.js
@@ -0,0 +1,102 @@
+/* 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 CLUSTER_AMOUNT = Ci.nsISelectionListener.CLUSTER_AMOUNT;
+const WORD_AMOUNT = Ci.nsISelectionListener.WORD_AMOUNT;
+const LINE_AMOUNT = Ci.nsISelectionListener.LINE_AMOUNT;
+const BEGINLINE_AMOUNT = Ci.nsISelectionListener.BEGINLINE_AMOUNT;
+const ENDLINE_AMOUNT = Ci.nsISelectionListener.ENDLINE_AMOUNT;
+
+const isMac = AppConstants.platform == "macosx";
+
+function matchCaretMoveEvent(id, caretOffset) {
+ return evt => {
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ return (
+ getAccessibleDOMNodeID(evt.accessible) == id &&
+ evt.caretOffset == caretOffset
+ );
+ };
+}
+
+addAccessibleTask(
+ `<textarea id="textarea" style="scrollbar-width: none;" cols="15">` +
+ `one two three four five six seven eight` +
+ `</textarea>`,
+ async function (browser, accDoc) {
+ const textarea = findAccessibleChildByID(accDoc, "textarea");
+ let caretMoved = waitForEvent(
+ EVENT_TEXT_CARET_MOVED,
+ matchCaretMoveEvent("textarea", 0)
+ );
+ textarea.takeFocus();
+ let evt = await caretMoved;
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+
+ caretMoved = waitForEvent(
+ EVENT_TEXT_CARET_MOVED,
+ matchCaretMoveEvent("textarea", 1)
+ );
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ is(evt.granularity, CLUSTER_AMOUNT, "Caret moved by cluster");
+
+ caretMoved = waitForEvent(
+ EVENT_TEXT_CARET_MOVED,
+ matchCaretMoveEvent("textarea", 15)
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ evt = await caretMoved;
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ todo(!evt.isAtEndOfLine, "Caret is not at end of line");
+ is(evt.granularity, LINE_AMOUNT, "Caret moved by line");
+
+ caretMoved = waitForEvent(
+ EVENT_TEXT_CARET_MOVED,
+ matchCaretMoveEvent("textarea", 14)
+ );
+ if (isMac) {
+ EventUtils.synthesizeKey("KEY_ArrowLeft", { metaKey: true });
+ } else {
+ EventUtils.synthesizeKey("KEY_Home");
+ }
+ evt = await caretMoved;
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ is(evt.granularity, BEGINLINE_AMOUNT, "Caret moved to line start");
+
+ caretMoved = waitForEvent(
+ EVENT_TEXT_CARET_MOVED,
+ matchCaretMoveEvent("textarea", 28)
+ );
+ if (isMac) {
+ EventUtils.synthesizeKey("KEY_ArrowRight", { metaKey: true });
+ } else {
+ EventUtils.synthesizeKey("KEY_End");
+ }
+ evt = await caretMoved;
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(evt.isAtEndOfLine, "Caret is at end of line");
+ is(evt.granularity, ENDLINE_AMOUNT, "Caret moved to line end");
+
+ caretMoved = waitForEvent(
+ EVENT_TEXT_CARET_MOVED,
+ matchCaretMoveEvent("textarea", 24)
+ );
+ if (isMac) {
+ EventUtils.synthesizeKey("KEY_ArrowLeft", { altKey: true });
+ } else {
+ EventUtils.synthesizeKey("KEY_ArrowLeft", { ctrlKey: true });
+ }
+ evt = await caretMoved;
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ is(evt.granularity, WORD_AMOUNT, "Caret moved by word");
+ }
+);
diff --git a/accessible/tests/browser/events/browser_test_docload.js b/accessible/tests/browser/events/browser_test_docload.js
new file mode 100644
index 0000000000..11ba90db19
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_docload.js
@@ -0,0 +1,122 @@
+/* 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 busyChecker(isBusy) {
+ return function (event) {
+ let scEvent;
+ try {
+ scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent);
+ } catch (e) {
+ return false;
+ }
+
+ return scEvent.state == STATE_BUSY && scEvent.isEnabled == isBusy;
+ };
+}
+
+function inIframeChecker(iframeId) {
+ return function (event) {
+ return getAccessibleDOMNodeID(event.accessibleDocument.parent) == iframeId;
+ };
+}
+
+function urlChecker(url) {
+ return function (event) {
+ info(`${event.accessibleDocument.URL} == ${url}`);
+ return event.accessibleDocument.URL == url;
+ };
+}
+
+async function runTests(browser, accDoc) {
+ let onLoadEvents = waitForEvents({
+ expected: [
+ [EVENT_REORDER, getAccessible(browser)],
+ [EVENT_DOCUMENT_LOAD_COMPLETE, "body2"],
+ [EVENT_STATE_CHANGE, busyChecker(false)],
+ ],
+ unexpected: [
+ [EVENT_DOCUMENT_LOAD_COMPLETE, inIframeChecker("iframe1")],
+ [EVENT_STATE_CHANGE, inIframeChecker("iframe1")],
+ ],
+ });
+
+ BrowserTestUtils.loadURIString(
+ browser,
+ `data:text/html;charset=utf-8,
+ <html><body id="body2">
+ <iframe id="iframe1" src="http://example.com"></iframe>
+ </body></html>`
+ );
+
+ await onLoadEvents;
+
+ onLoadEvents = waitForEvents([
+ [EVENT_DOCUMENT_LOAD_COMPLETE, urlChecker("about:about")],
+ [EVENT_STATE_CHANGE, busyChecker(false)],
+ [EVENT_REORDER, getAccessible(browser)],
+ ]);
+
+ BrowserTestUtils.loadURIString(browser, "about:about");
+
+ await onLoadEvents;
+
+ onLoadEvents = waitForEvents([
+ [EVENT_DOCUMENT_RELOAD, evt => evt.isFromUserInput],
+ [EVENT_REORDER, getAccessible(browser)],
+ [EVENT_STATE_CHANGE, busyChecker(false)],
+ ]);
+
+ EventUtils.synthesizeKey("VK_F5", {}, browser.ownerGlobal);
+
+ await onLoadEvents;
+
+ onLoadEvents = waitForEvents([
+ [EVENT_DOCUMENT_LOAD_COMPLETE, urlChecker("about:mozilla")],
+ [EVENT_STATE_CHANGE, busyChecker(false)],
+ [EVENT_REORDER, getAccessible(browser)],
+ ]);
+
+ BrowserTestUtils.loadURIString(browser, "about:mozilla");
+
+ await onLoadEvents;
+
+ onLoadEvents = waitForEvents([
+ [EVENT_DOCUMENT_RELOAD, evt => !evt.isFromUserInput],
+ [EVENT_REORDER, getAccessible(browser)],
+ [EVENT_STATE_CHANGE, busyChecker(false)],
+ ]);
+
+ browser.reload();
+
+ await onLoadEvents;
+
+ onLoadEvents = waitForEvents([
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ [EVENT_DOCUMENT_LOAD_COMPLETE, urlChecker("http://www.wronguri.wronguri/")],
+ [EVENT_STATE_CHANGE, busyChecker(false)],
+ [EVENT_REORDER, getAccessible(browser)],
+ ]);
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ BrowserTestUtils.loadURIString(browser, "http://www.wronguri.wronguri/");
+
+ await onLoadEvents;
+
+ onLoadEvents = waitForEvents([
+ [EVENT_DOCUMENT_LOAD_COMPLETE, urlChecker("https://nocert.example.com/")],
+ [EVENT_STATE_CHANGE, busyChecker(false)],
+ [EVENT_REORDER, getAccessible(browser)],
+ ]);
+
+ BrowserTestUtils.loadURIString(browser, "https://nocert.example.com:443/");
+
+ await onLoadEvents;
+}
+
+/**
+ * Test caching of accessible object states
+ */
+addAccessibleTask("", runTests);
diff --git a/accessible/tests/browser/events/browser_test_focus_browserui.js b/accessible/tests/browser/events/browser_test_focus_browserui.js
new file mode 100644
index 0000000000..969d336c74
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_focus_browserui.js
@@ -0,0 +1,57 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/states.js */
+/* import-globals-from ../../mochitest/role.js */
+loadScripts(
+ { name: "states.js", dir: MOCHITESTS_DIR },
+ { name: "role.js", dir: MOCHITESTS_DIR }
+);
+
+async function runTests(browser, accDoc) {
+ await SpecialPowers.pushPrefEnv({
+ // If Fission is disabled, the pref is no-op.
+ set: [["fission.bfcacheInParent", true]],
+ });
+
+ let onFocus = waitForEvent(EVENT_FOCUS, "input");
+ EventUtils.synthesizeKey("VK_TAB", {}, browser.ownerGlobal);
+ let evt = await onFocus;
+ testStates(evt.accessible, STATE_FOCUSED);
+
+ onFocus = waitForEvent(EVENT_FOCUS, "buttonInputDoc");
+ let url = snippetToURL(`<input id="input" type="button" value="button">`, {
+ contentDocBodyAttrs: { id: "buttonInputDoc" },
+ });
+ browser.loadURI(Services.io.newURI(url), {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ evt = await onFocus;
+ testStates(evt.accessible, STATE_FOCUSED);
+
+ onFocus = waitForEvent(EVENT_FOCUS, "input");
+ browser.goBack();
+ evt = await onFocus;
+ testStates(evt.accessible, STATE_FOCUSED);
+
+ onFocus = waitForEvent(
+ EVENT_FOCUS,
+ event => event.accessible.DOMNode == gURLBar.inputField
+ );
+ EventUtils.synthesizeKey("t", { accelKey: true }, browser.ownerGlobal);
+ evt = await onFocus;
+ testStates(evt.accessible, STATE_FOCUSED);
+
+ onFocus = waitForEvent(EVENT_FOCUS, "input");
+ EventUtils.synthesizeKey("w", { accelKey: true }, browser.ownerGlobal);
+ evt = await onFocus;
+ testStates(evt.accessible, STATE_FOCUSED);
+}
+
+/**
+ * Accessibility loading document events test.
+ */
+addAccessibleTask(`<input id="input">`, runTests);
diff --git a/accessible/tests/browser/events/browser_test_focus_dialog.js b/accessible/tests/browser/events/browser_test_focus_dialog.js
new file mode 100644
index 0000000000..71485a678d
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_focus_dialog.js
@@ -0,0 +1,76 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/states.js */
+/* import-globals-from ../../mochitest/role.js */
+loadScripts(
+ { name: "states.js", dir: MOCHITESTS_DIR },
+ { name: "role.js", dir: MOCHITESTS_DIR }
+);
+
+async function runTests(browser, accDoc) {
+ let onFocus = waitForEvent(EVENT_FOCUS, "button");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("button").focus();
+ });
+ let button = (await onFocus).accessible;
+ testStates(button, STATE_FOCUSED);
+
+ // Bug 1377942 - The target of the focus event changes under different
+ // circumstances.
+ // In e10s the focus event is the new window, in non-e10s it's the doc.
+ onFocus = waitForEvent(EVENT_FOCUS, () => true);
+ let newWin = await BrowserTestUtils.openNewBrowserWindow();
+ // button should be blurred
+ await onFocus;
+ testStates(button, 0, 0, STATE_FOCUSED);
+
+ onFocus = waitForEvent(EVENT_FOCUS, "button");
+ await BrowserTestUtils.closeWindow(newWin);
+ testStates((await onFocus).accessible, STATE_FOCUSED);
+
+ onFocus = waitForEvent(EVENT_FOCUS, "body2");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("editabledoc")
+ .contentWindow.document.body.focus();
+ });
+ testStates((await onFocus).accessible, STATE_FOCUSED);
+
+ onFocus = waitForEvent(EVENT_FOCUS, "body2");
+ newWin = await BrowserTestUtils.openNewBrowserWindow();
+ await BrowserTestUtils.closeWindow(newWin);
+ testStates((await onFocus).accessible, STATE_FOCUSED);
+
+ let onShow = waitForEvent(EVENT_SHOW, "alertdialog");
+ onFocus = waitForEvent(EVENT_FOCUS, "alertdialog");
+ await SpecialPowers.spawn(browser, [], () => {
+ let alertDialog = content.document.getElementById("alertdialog");
+ alertDialog.style.display = "block";
+ alertDialog.focus();
+ });
+ await onShow;
+ testStates((await onFocus).accessible, STATE_FOCUSED);
+}
+
+/**
+ * Accessible dialog focus testing
+ */
+addAccessibleTask(
+ `
+ <button id="button">button</button>
+ <iframe id="editabledoc"
+ src="${snippetToURL("", {
+ contentDocBodyAttrs: { id: "body2", contentEditable: "true" },
+ })}">
+ </iframe>
+ <div id="alertdialog" style="display: none" tabindex="-1" role="alertdialog" aria-labelledby="title2" aria-describedby="desc2">
+ <div id="title2">Blah blah</div>
+ <div id="desc2">Woof woof woof.</div>
+ <button>Close</button>
+ </div>`,
+ runTests
+);
diff --git a/accessible/tests/browser/events/browser_test_focus_urlbar.js b/accessible/tests/browser/events/browser_test_focus_urlbar.js
new file mode 100644
index 0000000000..68b2b07f3c
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_focus_urlbar.js
@@ -0,0 +1,438 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/states.js */
+/* import-globals-from ../../mochitest/role.js */
+loadScripts(
+ { name: "states.js", dir: MOCHITESTS_DIR },
+ { name: "role.js", dir: MOCHITESTS_DIR }
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ UrlbarProvider: "resource:///modules/UrlbarUtils.sys.mjs",
+ UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.sys.mjs",
+ UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+ UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
+});
+
+function isEventForAutocompleteItem(event) {
+ return event.accessible.role == ROLE_COMBOBOX_OPTION;
+}
+
+function isEventForButton(event) {
+ return event.accessible.role == ROLE_PUSHBUTTON;
+}
+
+function isEventForOneOffEngine(event) {
+ let parent = event.accessible.parent;
+ return (
+ event.accessible.role == ROLE_PUSHBUTTON &&
+ parent &&
+ parent.role == ROLE_GROUPING &&
+ parent.name
+ );
+}
+
+function isEventForMenuPopup(event) {
+ return event.accessible.role == ROLE_MENUPOPUP;
+}
+
+function isEventForMenuItem(event) {
+ return event.accessible.role == ROLE_MENUITEM;
+}
+
+function isEventForResultButton(event) {
+ let parent = event.accessible.parent;
+ return (
+ event.accessible.role == ROLE_PUSHBUTTON &&
+ parent?.role == ROLE_COMBOBOX_LIST
+ );
+}
+
+/**
+ * A test provider.
+ */
+class TipTestProvider extends UrlbarProvider {
+ constructor(matches) {
+ super();
+ this._matches = matches;
+ }
+ get name() {
+ return "TipTestProvider";
+ }
+ get type() {
+ return UrlbarUtils.PROVIDER_TYPE.PROFILE;
+ }
+ isActive(context) {
+ return true;
+ }
+ isRestricting(context) {
+ return true;
+ }
+ async startQuery(context, addCallback) {
+ this._context = context;
+ for (const match of this._matches) {
+ addCallback(this, match);
+ }
+ }
+}
+
+// Check that the URL bar manages accessibility focus appropriately.
+async function runTests() {
+ registerCleanupFunction(async function () {
+ await UrlbarTestUtils.promisePopupClose(window);
+ await PlacesUtils.history.clear();
+ });
+
+ await PlacesTestUtils.addVisits([
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example1.com/blah",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example2.com/blah",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example1.com/",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example2.com/",
+ ]);
+
+ // Ensure initial state.
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ let focused = waitForEvent(
+ EVENT_FOCUS,
+ event => event.accessible.role == ROLE_ENTRY
+ );
+ gURLBar.focus();
+ let event = await focused;
+ let textBox = event.accessible;
+ // Ensure the URL bar is ready for a new URL to be typed.
+ // Sometimes, when this test runs, the existing text isn't selected when the
+ // URL bar is focused. Pressing escape twice ensures that the popup is
+ // closed and that the existing text is selected.
+ EventUtils.synthesizeKey("KEY_Escape");
+ EventUtils.synthesizeKey("KEY_Escape");
+
+ info("Ensuring no focus change when first text is typed");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: "example",
+ fireInputEvent: true,
+ });
+ // Wait a tick for a11y events to fire.
+ await TestUtils.waitForTick();
+ testStates(textBox, STATE_FOCUSED);
+
+ info("Ensuring no focus change on backspace");
+ EventUtils.synthesizeKey("KEY_Backspace");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ // Wait a tick for a11y events to fire.
+ await TestUtils.waitForTick();
+ testStates(textBox, STATE_FOCUSED);
+
+ info("Ensuring no focus change on text selection and delete");
+ EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
+ EventUtils.synthesizeKey("KEY_Delete");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ // Wait a tick for a11y events to fire.
+ await TestUtils.waitForTick();
+ testStates(textBox, STATE_FOCUSED);
+
+ info("Ensuring autocomplete focus on down arrow (1)");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring focus of another autocomplete item on down arrow");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring previous arrow selection state doesn't get stale on input");
+ focused = waitForEvent(EVENT_FOCUS, textBox);
+ EventUtils.sendString("z");
+ await focused;
+ EventUtils.synthesizeKey("KEY_Backspace");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ testStates(textBox, STATE_FOCUSED);
+
+ info("Ensuring focus of another autocomplete item on down arrow");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ if (AppConstants.platform == "macosx") {
+ info("Ensuring focus of another autocomplete item on ctrl-n");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("n", { ctrlKey: true });
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring focus of another autocomplete item on ctrl-p");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+ }
+
+ info("Ensuring focus of another autocomplete item on up arrow");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring text box focus on left arrow");
+ focused = waitForEvent(EVENT_FOCUS, textBox);
+ EventUtils.synthesizeKey("KEY_ArrowLeft");
+ await focused;
+ testStates(textBox, STATE_FOCUSED);
+
+ gURLBar.view.close();
+ // On Mac, down arrow when not at the end of the field moves to the end.
+ // Move back to the end so the next press of down arrow opens the popup.
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+
+ info("Ensuring autocomplete focus on down arrow (2)");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring autocomplete focus on arrow up for search settings button");
+ focused = waitForEvent(EVENT_FOCUS, isEventForButton);
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring text box focus when text is typed");
+ focused = waitForEvent(EVENT_FOCUS, textBox);
+ EventUtils.sendString("z");
+ await focused;
+ testStates(textBox, STATE_FOCUSED);
+ EventUtils.synthesizeKey("KEY_Backspace");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ info("Ensuring autocomplete focus on down arrow (3)");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring text box focus on backspace");
+ focused = waitForEvent(EVENT_FOCUS, textBox);
+ EventUtils.synthesizeKey("KEY_Backspace");
+ await focused;
+ testStates(textBox, STATE_FOCUSED);
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ info("Ensuring autocomplete focus on arrow down (4)");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ // Arrow down to the last result.
+ const resultCount = UrlbarTestUtils.getResultCount(window);
+ while (UrlbarTestUtils.getSelectedRowIndex(window) != resultCount - 1) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+
+ info("Ensuring one-off search button focus on arrow down");
+ focused = waitForEvent(EVENT_FOCUS, isEventForOneOffEngine);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring autocomplete focus on arrow up");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring text box focus on text selection");
+ focused = waitForEvent(EVENT_FOCUS, textBox);
+ EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
+ await focused;
+ testStates(textBox, STATE_FOCUSED);
+
+ if (AppConstants.platform == "macosx") {
+ // On Mac, ctrl-n after arrow left/right does not re-open the popup.
+ // Type some text so the next press of ctrl-n opens the popup.
+ EventUtils.sendString("ple");
+
+ info("Ensuring autocomplete focus on ctrl-n");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("n", { ctrlKey: true });
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+ }
+
+ if (
+ AppConstants.platform == "macosx" &&
+ Services.prefs.getBoolPref("widget.macos.native-context-menus", false)
+ ) {
+ // With native context menus, we do not observe accessibility events and we
+ // cannot send synthetic key events to the menu.
+ info("Opening and closing context native context menu");
+ let contextMenu = gURLBar.querySelector("menupopup");
+ let popupshown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(gURLBar.querySelector("moz-input-box"), {
+ type: "contextmenu",
+ });
+ await popupshown;
+ let popuphidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.hidePopup();
+ await popuphidden;
+ } else {
+ info(
+ "Ensuring context menu gets menu event on launch, and item focus on down"
+ );
+ let menuEvent = waitForEvent(
+ nsIAccessibleEvent.EVENT_MENUPOPUP_START,
+ isEventForMenuPopup
+ );
+ EventUtils.synthesizeMouseAtCenter(gURLBar.querySelector("moz-input-box"), {
+ type: "contextmenu",
+ });
+ await menuEvent;
+
+ focused = waitForEvent(EVENT_FOCUS, isEventForMenuItem);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ focused = waitForEvent(EVENT_FOCUS, textBox);
+ let closed = waitForEvent(
+ nsIAccessibleEvent.EVENT_MENUPOPUP_END,
+ isEventForMenuPopup
+ );
+ EventUtils.synthesizeKey("KEY_Escape");
+ await closed;
+ await focused;
+ }
+ info("Ensuring address bar is focused after context menu is dismissed.");
+ testStates(textBox, STATE_FOCUSED);
+}
+
+// We test TIP results in their own test so the spoofed results don't interfere
+// with the main test.
+async function runTipTests() {
+ let matches = [
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ { url: "http://mozilla.org/a" }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.TIP,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ helpUrl: "http://example.com/",
+ type: "test",
+ titleL10n: { id: "urlbar-search-tips-confirm" },
+ buttons: [
+ {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ url: "http://example.com/",
+ l10n: { id: "urlbar-search-tips-confirm" },
+ },
+ ],
+ }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ { url: "http://mozilla.org/b" }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ { url: "http://mozilla.org/c" }
+ ),
+ ];
+
+ // Ensure the tip appears in the expected position.
+ matches[1].suggestedIndex = 2;
+
+ let provider = new TipTestProvider(matches);
+ UrlbarProvidersManager.registerProvider(provider);
+
+ registerCleanupFunction(async function () {
+ UrlbarProvidersManager.unregisterProvider(provider);
+ });
+
+ let focused = waitForEvent(
+ EVENT_FOCUS,
+ event => event.accessible.role == ROLE_ENTRY
+ );
+ gURLBar.focus();
+ let event = await focused;
+ let textBox = event.accessible;
+
+ EventUtils.synthesizeKey("KEY_Escape");
+ EventUtils.synthesizeKey("KEY_Escape");
+
+ info("Ensuring no focus change when first text is typed");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: "example",
+ fireInputEvent: true,
+ });
+ // Wait a tick for a11y events to fire.
+ await TestUtils.waitForTick();
+ testStates(textBox, STATE_FOCUSED);
+
+ info("Ensuring autocomplete focus on down arrow (1)");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring the tip button is focused on down arrow");
+ info("Also ensuring that the tip button is a part of a labelled group");
+ focused = waitForEvent(EVENT_FOCUS, isEventForResultButton);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring the help button is focused on tab");
+ info("Also ensuring that the help button is a part of a labelled group");
+ focused = waitForEvent(EVENT_FOCUS, isEventForResultButton);
+ EventUtils.synthesizeKey("KEY_Tab");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring autocomplete focus on down arrow (2)");
+ focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring the help button is focused on shift+tab");
+ focused = waitForEvent(EVENT_FOCUS, isEventForResultButton);
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring text box focus on left arrow, and not back to the tip button");
+ focused = waitForEvent(EVENT_FOCUS, textBox);
+ EventUtils.synthesizeKey("KEY_ArrowLeft");
+ await focused;
+ testStates(textBox, STATE_FOCUSED);
+}
+
+addAccessibleTask(``, runTests);
+addAccessibleTask(``, runTipTests);
diff --git a/accessible/tests/browser/events/browser_test_panel.js b/accessible/tests/browser/events/browser_test_panel.js
new file mode 100644
index 0000000000..f2d74cc5f9
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_panel.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+// Verify we recieve hide and show notifications when the chrome
+// XUL alert is closed or opened. Mac expects both notifications to
+// properly communicate live region changes.
+async function runTests(browser) {
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ // When available, the popup panel makes itself a child of the chrome window.
+ // To verify it isn't accessible without reproducing the entirety of the chrome
+ // window tree, we check instead that the panel is not accessible.
+ ok(!isAccessible(PopupNotifications.panel), "Popup panel is not accessible");
+
+ const panelShown = waitForEvent(EVENT_SHOW, PopupNotifications.panel);
+ const notification = PopupNotifications.show(
+ browser,
+ "test-notification",
+ "hello world",
+ PopupNotifications.panel.id
+ );
+
+ await panelShown;
+
+ ok(isAccessible(PopupNotifications.panel), "Popup panel is accessible");
+ testAccessibleTree(PopupNotifications.panel, {
+ ALERT: [
+ {
+ TEXT_CONTAINER: [
+ { LABEL: [{ TEXT_LEAF: [] }] },
+ { PUSHBUTTON: [] },
+ { PUSHBUTTON: [] },
+ ],
+ },
+ ],
+ });
+ // Verify the popup panel is associated with the chrome window.
+ is(
+ PopupNotifications.panel.ownerGlobal,
+ getMainChromeWindow(window),
+ "Popup panel is associated with the chrome window"
+ );
+
+ const panelHidden = waitForEvent(EVENT_HIDE, PopupNotifications.panel);
+ PopupNotifications.remove(notification);
+
+ await panelHidden;
+
+ ok(!isAccessible(PopupNotifications.panel), "Popup panel is not accessible");
+}
+
+addAccessibleTask(``, runTests);
diff --git a/accessible/tests/browser/events/browser_test_scrolling.js b/accessible/tests/browser/events/browser_test_scrolling.js
new file mode 100644
index 0000000000..f1f4b07120
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_scrolling.js
@@ -0,0 +1,113 @@
+/* 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";
+
+addAccessibleTask(
+ `
+ <div style="height: 100vh" id="one">one</div>
+ <div style="height: 100vh" id="two">two</div>
+ <div style="height: 100vh; width: 200vw; overflow: auto;" id="three">
+ <div style="height: 300%;">three</div>
+ </div>
+ <textarea id="textarea" rows="1">a
+b
+c</textarea>
+ `,
+ async function (browser, accDoc) {
+ let onScrolling = waitForEvents([
+ [EVENT_SCROLLING, accDoc],
+ [EVENT_SCROLLING_END, accDoc],
+ ]);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.location.hash = "#two";
+ });
+ let [scrollEvent1, scrollEndEvent1] = await onScrolling;
+ scrollEvent1.QueryInterface(nsIAccessibleScrollingEvent);
+ ok(
+ scrollEvent1.maxScrollY >= scrollEvent1.scrollY,
+ "scrollY is within max"
+ );
+ scrollEndEvent1.QueryInterface(nsIAccessibleScrollingEvent);
+ ok(
+ scrollEndEvent1.maxScrollY >= scrollEndEvent1.scrollY,
+ "scrollY is within max"
+ );
+
+ onScrolling = waitForEvents([
+ [EVENT_SCROLLING, accDoc],
+ [EVENT_SCROLLING_END, accDoc],
+ ]);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.location.hash = "#three";
+ });
+ let [scrollEvent2, scrollEndEvent2] = await onScrolling;
+ scrollEvent2.QueryInterface(nsIAccessibleScrollingEvent);
+ ok(
+ scrollEvent2.scrollY > scrollEvent1.scrollY,
+ `${scrollEvent2.scrollY} > ${scrollEvent1.scrollY}`
+ );
+ scrollEndEvent2.QueryInterface(nsIAccessibleScrollingEvent);
+ ok(
+ scrollEndEvent2.maxScrollY >= scrollEndEvent2.scrollY,
+ "scrollY is within max"
+ );
+
+ onScrolling = waitForEvents([
+ [EVENT_SCROLLING, accDoc],
+ [EVENT_SCROLLING_END, accDoc],
+ ]);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.scrollTo(10, 0);
+ });
+ let [scrollEvent3, scrollEndEvent3] = await onScrolling;
+ scrollEvent3.QueryInterface(nsIAccessibleScrollingEvent);
+ ok(
+ scrollEvent3.maxScrollX >= scrollEvent3.scrollX,
+ "scrollX is within max"
+ );
+ scrollEndEvent3.QueryInterface(nsIAccessibleScrollingEvent);
+ ok(
+ scrollEndEvent3.maxScrollX >= scrollEndEvent3.scrollX,
+ "scrollY is within max"
+ );
+ ok(
+ scrollEvent3.scrollX > scrollEvent2.scrollX,
+ `${scrollEvent3.scrollX} > ${scrollEvent2.scrollX}`
+ );
+
+ // non-doc scrolling
+ onScrolling = waitForEvents([
+ [EVENT_SCROLLING, "three"],
+ [EVENT_SCROLLING_END, "three"],
+ ]);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("#three").scrollTo(0, 10);
+ });
+ let [scrollEvent4, scrollEndEvent4] = await onScrolling;
+ scrollEvent4.QueryInterface(nsIAccessibleScrollingEvent);
+ ok(
+ scrollEvent4.maxScrollY >= scrollEvent4.scrollY,
+ "scrollY is within max"
+ );
+ scrollEndEvent4.QueryInterface(nsIAccessibleScrollingEvent);
+ ok(
+ scrollEndEvent4.maxScrollY >= scrollEndEvent4.scrollY,
+ "scrollY is within max"
+ );
+
+ // textarea scrolling
+ info("Moving textarea caret to c");
+ onScrolling = waitForEvents([
+ [EVENT_SCROLLING, "textarea"],
+ [EVENT_SCROLLING_END, "textarea"],
+ ]);
+ await invokeContentTask(browser, [], () => {
+ const textareaDom = content.document.getElementById("textarea");
+ textareaDom.focus();
+ textareaDom.selectionStart = 4;
+ });
+ await onScrolling;
+ }
+);
diff --git a/accessible/tests/browser/events/browser_test_selection_urlbar.js b/accessible/tests/browser/events/browser_test_selection_urlbar.js
new file mode 100644
index 0000000000..8f8fdb92f7
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_selection_urlbar.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+});
+
+// Check that the URL bar manages accessibility
+// selection notifications appropriately on startup (new window).
+async function runTests() {
+ let focused = waitForEvent(
+ EVENT_FOCUS,
+ event => event.accessible.role == ROLE_ENTRY
+ );
+ info("Creating new window");
+ let newWin = await BrowserTestUtils.openNewBrowserWindow();
+ let bookmark = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ title: "addons",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ url: Services.io.newURI("http://www.addons.mozilla.org/"),
+ });
+
+ registerCleanupFunction(async function () {
+ await BrowserTestUtils.closeWindow(newWin);
+ await PlacesUtils.bookmarks.remove(bookmark);
+ });
+ info("Focusing window");
+ newWin.focus();
+ await focused;
+
+ // Ensure the URL bar is ready for a new URL to be typed.
+ // Sometimes, when this test runs, the existing text isn't selected when the
+ // URL bar is focused. Pressing escape twice ensures that the popup is
+ // closed and that the existing text is selected.
+ EventUtils.synthesizeKey("KEY_Escape", {}, newWin);
+ EventUtils.synthesizeKey("KEY_Escape", {}, newWin);
+ let caretMoved = waitForEvent(
+ EVENT_TEXT_CARET_MOVED,
+ event => event.accessible.role == ROLE_ENTRY
+ );
+
+ info("Autofilling after typing `a` in new window URL bar.");
+ EventUtils.synthesizeKey("a", {}, newWin);
+ await UrlbarTestUtils.promiseSearchComplete(newWin);
+ Assert.equal(
+ newWin.gURLBar.inputField.value,
+ "addons.mozilla.org/",
+ "autofilled value as expected"
+ );
+
+ info("Ensuring caret moved on text selection");
+ await caretMoved;
+}
+
+addAccessibleTask(``, runTests);
diff --git a/accessible/tests/browser/events/browser_test_textcaret.js b/accessible/tests/browser/events/browser_test_textcaret.js
new file mode 100644
index 0000000000..d5065c81f3
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_textcaret.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Caret move events checker.
+ */
+function caretMoveChecker(target, caretOffset) {
+ return function (event) {
+ let cmEvent = event.QueryInterface(nsIAccessibleCaretMoveEvent);
+ return (
+ cmEvent.accessible == getAccessible(target) &&
+ cmEvent.caretOffset == caretOffset
+ );
+ };
+}
+
+async function checkURLBarCaretEvents() {
+ const kURL = "about:mozilla";
+ let newWin = await BrowserTestUtils.openNewBrowserWindow();
+ BrowserTestUtils.loadURIString(newWin.gBrowser.selectedBrowser, kURL);
+ newWin.gBrowser.selectedBrowser.focus();
+
+ await waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, event => {
+ try {
+ return event.accessible.QueryInterface(nsIAccessibleDocument).URL == kURL;
+ } catch (e) {
+ return false;
+ }
+ });
+ info("Loaded " + kURL);
+
+ let urlbarInputEl = newWin.gURLBar.inputField;
+ let urlbarInput = getAccessible(urlbarInputEl, [nsIAccessibleText]);
+
+ let onCaretMove = waitForEvents([
+ [EVENT_TEXT_CARET_MOVED, caretMoveChecker(urlbarInput, kURL.length)],
+ [EVENT_FOCUS, urlbarInput],
+ ]);
+
+ urlbarInput.caretOffset = -1;
+ await onCaretMove;
+ ok(true, "Caret move in URL bar #1");
+
+ onCaretMove = waitForEvent(
+ EVENT_TEXT_CARET_MOVED,
+ caretMoveChecker(urlbarInput, 0)
+ );
+
+ urlbarInput.caretOffset = 0;
+ await onCaretMove;
+ ok(true, "Caret move in URL bar #2");
+
+ await BrowserTestUtils.closeWindow(newWin);
+}
+
+add_task(checkURLBarCaretEvents);
diff --git a/accessible/tests/browser/events/head.js b/accessible/tests/browser/events/head.js
new file mode 100644
index 0000000000..afc50984bd
--- /dev/null
+++ b/accessible/tests/browser/events/head.js
@@ -0,0 +1,18 @@
+/* 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";
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js.
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR }
+);
diff --git a/accessible/tests/browser/fission/browser.ini b/accessible/tests/browser/fission/browser.ini
new file mode 100644
index 0000000000..c898fb54d2
--- /dev/null
+++ b/accessible/tests/browser/fission/browser.ini
@@ -0,0 +1,20 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_content_tree.js]
+[browser_hidden_iframe.js]
+https_first_disabled = true
+[browser_nested_iframe.js]
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_reframe_root.js]
+[browser_reframe_visibility.js]
+[browser_src_change.js]
+[browser_take_focus.js]
diff --git a/accessible/tests/browser/fission/browser_content_tree.js b/accessible/tests/browser/fission/browser_content_tree.js
new file mode 100644
index 0000000000..1592ae6a1a
--- /dev/null
+++ b/accessible/tests/browser/fission/browser_content_tree.js
@@ -0,0 +1,75 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ `<table id="table">
+ <tr>
+ <td>cell1</td>
+ <td>cell2</td>
+ </tr>
+ </table>
+ <ul id="ul">
+ <li id="li">item1</li>
+ </ul>`,
+ async function (browser, iframeDocAcc, contentDocAcc) {
+ ok(iframeDocAcc, "IFRAME document accessible is present");
+ (gIsRemoteIframe ? isnot : is)(
+ browser.browsingContext.currentWindowGlobal.osPid,
+ browser.browsingContext.children[0].currentWindowGlobal.osPid,
+ `Content and IFRAME documents are in ${
+ gIsRemoteIframe ? "separate processes" : "same process"
+ }.`
+ );
+
+ const tree = {
+ DOCUMENT: [
+ {
+ INTERNAL_FRAME: [
+ {
+ DOCUMENT: [
+ {
+ TABLE: [
+ {
+ ROW: [
+ { CELL: [{ TEXT_LEAF: [] }] },
+ { CELL: [{ TEXT_LEAF: [] }] },
+ ],
+ },
+ ],
+ },
+ {
+ LIST: [
+ {
+ LISTITEM: [{ LISTITEM_MARKER: [] }, { TEXT_LEAF: [] }],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(contentDocAcc, tree);
+
+ const iframeAcc = contentDocAcc.getChildAt(0);
+ is(
+ iframeAcc.getChildAt(0),
+ iframeDocAcc,
+ "Document for the IFRAME matches IFRAME's first child."
+ );
+
+ is(
+ iframeDocAcc.parent,
+ iframeAcc,
+ "IFRAME document's parent matches the IFRAME."
+ );
+ },
+ { topLevel: false, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/fission/browser_hidden_iframe.js b/accessible/tests/browser/fission/browser_hidden_iframe.js
new file mode 100644
index 0000000000..61414b611d
--- /dev/null
+++ b/accessible/tests/browser/fission/browser_hidden_iframe.js
@@ -0,0 +1,70 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/states.js */
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "states.js", dir: MOCHITESTS_DIR });
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ `<input id="textbox" value="hello"/>`,
+ async function (browser, contentDocAcc) {
+ info(
+ "Check that the IFRAME and the IFRAME document are not accessible initially."
+ );
+ let iframeAcc = findAccessibleChildByID(contentDocAcc, DEFAULT_IFRAME_ID);
+ let iframeDocAcc = findAccessibleChildByID(
+ contentDocAcc,
+ DEFAULT_IFRAME_DOC_BODY_ID
+ );
+ ok(!iframeAcc, "IFRAME is hidden and should not be accessible");
+ ok(!iframeDocAcc, "IFRAME document is hidden and should not be accessible");
+
+ info(
+ "Show the IFRAME and check that it's now available in the accessibility tree."
+ );
+
+ const events = [[EVENT_REORDER, contentDocAcc]];
+
+ const onEvents = waitForEvents(events);
+ await SpecialPowers.spawn(browser, [DEFAULT_IFRAME_ID], contentId => {
+ content.document.getElementById(contentId).style.display = "";
+ });
+ await onEvents;
+
+ iframeAcc = findAccessibleChildByID(contentDocAcc, DEFAULT_IFRAME_ID);
+ ok(!isDefunct(iframeAcc), "IFRAME should be accessible");
+
+ // Wait for the child iframe to layout itself. This can happen during or
+ // after the reorder event, depending on timing.
+ iframeDocAcc = await TestUtils.waitForCondition(() => {
+ return findAccessibleChildByID(contentDocAcc, DEFAULT_IFRAME_DOC_BODY_ID);
+ });
+
+ is(iframeAcc.childCount, 1, "IFRAME accessible should have a single child");
+ ok(iframeDocAcc, "IFRAME document exists");
+ ok(!isDefunct(iframeDocAcc), "IFRAME document should be accessible");
+ is(
+ iframeAcc.firstChild,
+ iframeDocAcc,
+ "An accessible for a IFRAME document is the child of the IFRAME accessible"
+ );
+ is(
+ iframeDocAcc.parent,
+ iframeAcc,
+ "IFRAME document's parent matches the IFRAME."
+ );
+ },
+ {
+ topLevel: false,
+ iframe: true,
+ remoteIframe: true,
+ iframeAttrs: {
+ style: "display: none;",
+ },
+ skipFissionDocLoad: true,
+ }
+);
diff --git a/accessible/tests/browser/fission/browser_nested_iframe.js b/accessible/tests/browser/fission/browser_nested_iframe.js
new file mode 100644
index 0000000000..d6600a2d5e
--- /dev/null
+++ b/accessible/tests/browser/fission/browser_nested_iframe.js
@@ -0,0 +1,164 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+const NESTED_IFRAME_DOC_BODY_ID = "nested-iframe-body";
+const NESTED_IFRAME_ID = "nested-iframe";
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const nestedURL = new URL(`http://example.com/document-builder.sjs`);
+nestedURL.searchParams.append(
+ "html",
+ `<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Nested Iframe Frame Test</title>
+ </head>
+ <body id="${NESTED_IFRAME_DOC_BODY_ID}">
+ <table id="table">
+ <tr>
+ <td>cell1</td>
+ <td>cell2</td>
+ </tr>
+ </table>
+ <ul id="ul">
+ <li id="li">item1</li>
+ </ul>
+ </body>
+ </html>`
+);
+
+function getOsPid(browsingContext) {
+ return browsingContext.currentWindowGlobal.osPid;
+}
+
+addAccessibleTask(
+ `<iframe id="${NESTED_IFRAME_ID}" src="${nestedURL.href}"/>`,
+ async function (browser, iframeDocAcc, contentDocAcc) {
+ ok(iframeDocAcc, "IFRAME document accessible is present");
+ let nestedDocAcc = findAccessibleChildByID(
+ iframeDocAcc,
+ NESTED_IFRAME_DOC_BODY_ID
+ );
+ let waitForNestedDocLoad = false;
+ if (nestedDocAcc) {
+ const state = {};
+ nestedDocAcc.getState(state, {});
+ if (state.value & STATE_BUSY) {
+ info("Nested IFRAME document accessible is present but busy");
+ waitForNestedDocLoad = true;
+ } else {
+ ok(true, "Nested IFRAME document accessible is present and ready");
+ }
+ } else {
+ info("Nested IFRAME document accessible is not present yet");
+ waitForNestedDocLoad = true;
+ }
+ if (waitForNestedDocLoad) {
+ info("Waiting for doc load complete on nested iframe document");
+ nestedDocAcc = (
+ await waitForEvent(
+ EVENT_DOCUMENT_LOAD_COMPLETE,
+ NESTED_IFRAME_DOC_BODY_ID
+ )
+ ).accessible;
+ }
+
+ if (gIsRemoteIframe) {
+ isnot(
+ getOsPid(browser.browsingContext),
+ getOsPid(browser.browsingContext.children[0]),
+ `Content and IFRAME documents are in separate processes.`
+ );
+ isnot(
+ getOsPid(browser.browsingContext),
+ getOsPid(browser.browsingContext.children[0].children[0]),
+ `Content and nested IFRAME documents are in separate processes.`
+ );
+ isnot(
+ getOsPid(browser.browsingContext.children[0]),
+ getOsPid(browser.browsingContext.children[0].children[0]),
+ `IFRAME and nested IFRAME documents are in separate processes.`
+ );
+ } else {
+ is(
+ getOsPid(browser.browsingContext),
+ getOsPid(browser.browsingContext.children[0]),
+ `Content and IFRAME documents are in same processes.`
+ );
+ if (gFissionBrowser) {
+ isnot(
+ getOsPid(browser.browsingContext.children[0]),
+ getOsPid(browser.browsingContext.children[0].children[0]),
+ `IFRAME and nested IFRAME documents are in separate processes.`
+ );
+ } else {
+ is(
+ getOsPid(browser.browsingContext),
+ getOsPid(browser.browsingContext.children[0].children[0]),
+ `Content and nested IFRAME documents are in same processes.`
+ );
+ }
+ }
+
+ const tree = {
+ DOCUMENT: [
+ {
+ INTERNAL_FRAME: [
+ {
+ DOCUMENT: [
+ {
+ INTERNAL_FRAME: [
+ {
+ DOCUMENT: [
+ {
+ TABLE: [
+ {
+ ROW: [
+ { CELL: [{ TEXT_LEAF: [] }] },
+ { CELL: [{ TEXT_LEAF: [] }] },
+ ],
+ },
+ ],
+ },
+ {
+ LIST: [
+ {
+ LISTITEM: [
+ { LISTITEM_MARKER: [] },
+ { TEXT_LEAF: [] },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(contentDocAcc, tree);
+
+ const nestedIframeAcc = iframeDocAcc.getChildAt(0);
+ is(
+ nestedIframeAcc.getChildAt(0),
+ nestedDocAcc,
+ "Document for nested IFRAME matches."
+ );
+
+ is(
+ nestedDocAcc.parent,
+ nestedIframeAcc,
+ "Nested IFRAME document's parent matches the nested IFRAME."
+ );
+ },
+ { topLevel: false, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/fission/browser_reframe_root.js b/accessible/tests/browser/fission/browser_reframe_root.js
new file mode 100644
index 0000000000..d7123109f4
--- /dev/null
+++ b/accessible/tests/browser/fission/browser_reframe_root.js
@@ -0,0 +1,95 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/states.js */
+/* import-globals-from ../../mochitest/role.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+addAccessibleTask(
+ `<input id="textbox" value="hello"/>`,
+ async function (browser, iframeDocAcc, contentDocAcc) {
+ info(
+ "Check that the IFRAME and the IFRAME document are accessible initially."
+ );
+ let iframeAcc = findAccessibleChildByID(contentDocAcc, DEFAULT_IFRAME_ID);
+ ok(!isDefunct(iframeAcc), "IFRAME should be accessible");
+ ok(!isDefunct(iframeDocAcc), "IFRAME document should be accessible");
+
+ info("Move the IFRAME under a new hidden root.");
+ let onEvents = waitForEvent(EVENT_REORDER, contentDocAcc);
+ await SpecialPowers.spawn(browser, [DEFAULT_IFRAME_ID], id => {
+ const doc = content.document;
+ const root = doc.createElement("div");
+ root.style.display = "none";
+ doc.body.appendChild(root);
+ root.appendChild(doc.getElementById(id));
+ });
+ await onEvents;
+
+ ok(
+ isDefunct(iframeAcc),
+ "IFRAME accessible should be defunct when hidden."
+ );
+ ok(
+ isDefunct(iframeDocAcc),
+ "IFRAME document's accessible should be defunct when the IFRAME is hidden."
+ );
+ ok(
+ !findAccessibleChildByID(contentDocAcc, DEFAULT_IFRAME_ID),
+ "No accessible for an IFRAME present."
+ );
+ ok(
+ !findAccessibleChildByID(contentDocAcc, DEFAULT_IFRAME_DOC_BODY_ID),
+ "No accessible for the IFRAME document present."
+ );
+
+ info("Move the IFRAME back under the content document's body.");
+ onEvents = waitForEvents([
+ [EVENT_REORDER, contentDocAcc],
+ [
+ EVENT_STATE_CHANGE,
+ event => {
+ const scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent);
+ const id = getAccessibleDOMNodeID(event.accessible);
+ return (
+ id === DEFAULT_IFRAME_DOC_BODY_ID &&
+ scEvent.state === STATE_BUSY &&
+ scEvent.isEnabled === false
+ );
+ },
+ ],
+ ]);
+ await SpecialPowers.spawn(browser, [DEFAULT_IFRAME_ID], id => {
+ content.document.body.appendChild(content.document.getElementById(id));
+ });
+ await onEvents;
+
+ iframeAcc = findAccessibleChildByID(contentDocAcc, DEFAULT_IFRAME_ID);
+ const newiframeDocAcc = iframeAcc.firstChild;
+
+ ok(!isDefunct(iframeAcc), "IFRAME should be accessible");
+ is(iframeAcc.childCount, 1, "IFRAME accessible should have a single child");
+ ok(!isDefunct(newiframeDocAcc), "IFRAME document should be accessible");
+ ok(
+ isDefunct(iframeDocAcc),
+ "Original IFRAME document accessible should be defunct."
+ );
+ isnot(
+ iframeAcc.firstChild,
+ iframeDocAcc,
+ "A new accessible is created for a IFRAME document."
+ );
+ is(
+ iframeAcc.firstChild,
+ newiframeDocAcc,
+ "A new accessible for a IFRAME document is the child of the IFRAME accessible"
+ );
+ },
+ { topLevel: false, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/fission/browser_reframe_visibility.js b/accessible/tests/browser/fission/browser_reframe_visibility.js
new file mode 100644
index 0000000000..8aee02037c
--- /dev/null
+++ b/accessible/tests/browser/fission/browser_reframe_visibility.js
@@ -0,0 +1,116 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/states.js */
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "states.js", dir: MOCHITESTS_DIR });
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ `<input id="textbox" value="hello"/>`,
+ async function (browser, iframeDocAcc, contentDocAcc) {
+ info(
+ "Check that the IFRAME and the IFRAME document are accessible initially."
+ );
+ let iframeAcc = findAccessibleChildByID(contentDocAcc, DEFAULT_IFRAME_ID);
+ ok(!isDefunct(iframeAcc), "IFRAME should be accessible");
+ ok(!isDefunct(iframeDocAcc), "IFRAME document should be accessible");
+
+ info(
+ "Hide the IFRAME and check that it's gone along with the IFRAME document."
+ );
+ let onEvents = waitForEvent(EVENT_REORDER, contentDocAcc);
+ await SpecialPowers.spawn(browser, [DEFAULT_IFRAME_ID], contentId => {
+ content.document.getElementById(contentId).style.display = "none";
+ });
+ await onEvents;
+
+ ok(
+ isDefunct(iframeAcc),
+ "IFRAME accessible should be defunct when hidden."
+ );
+ if (gIsRemoteIframe) {
+ ok(
+ !isDefunct(iframeDocAcc),
+ "IFRAME document's accessible is not defunct when the IFRAME is hidden and fission is enabled."
+ );
+ } else {
+ ok(
+ isDefunct(iframeDocAcc),
+ "IFRAME document's accessible is defunct when the IFRAME is hidden and fission is not enabled."
+ );
+ }
+ ok(
+ !findAccessibleChildByID(contentDocAcc, DEFAULT_IFRAME_ID),
+ "No accessible for an IFRAME present."
+ );
+ ok(
+ !findAccessibleChildByID(contentDocAcc, DEFAULT_IFRAME_DOC_BODY_ID),
+ "No accessible for the IFRAME document present."
+ );
+
+ info(
+ "Show the IFRAME and check that a new accessible is created for it as " +
+ "well as the IFRAME document."
+ );
+
+ const events = [[EVENT_REORDER, contentDocAcc]];
+ if (!gIsRemoteIframe) {
+ events.push([
+ EVENT_STATE_CHANGE,
+ event => {
+ const scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent);
+ const id = getAccessibleDOMNodeID(event.accessible);
+ return (
+ id === DEFAULT_IFRAME_DOC_BODY_ID &&
+ scEvent.state === STATE_BUSY &&
+ scEvent.isEnabled === false
+ );
+ },
+ ]);
+ }
+ onEvents = waitForEvents(events);
+ await SpecialPowers.spawn(browser, [DEFAULT_IFRAME_ID], contentId => {
+ content.document.getElementById(contentId).style.display = "block";
+ });
+ await onEvents;
+
+ iframeAcc = findAccessibleChildByID(contentDocAcc, DEFAULT_IFRAME_ID);
+ const newiframeDocAcc = iframeAcc.firstChild;
+
+ ok(!isDefunct(iframeAcc), "IFRAME should be accessible");
+ is(iframeAcc.childCount, 1, "IFRAME accessible should have a single child");
+ ok(newiframeDocAcc, "IFRAME document exists");
+ ok(!isDefunct(newiframeDocAcc), "IFRAME document should be accessible");
+ if (gIsRemoteIframe) {
+ ok(
+ !isDefunct(iframeDocAcc),
+ "Original IFRAME document accessible should not be defunct when fission is enabled."
+ );
+ is(
+ iframeAcc.firstChild,
+ iframeDocAcc,
+ "Existing accessible is used for a IFRAME document."
+ );
+ } else {
+ ok(
+ isDefunct(iframeDocAcc),
+ "Original IFRAME document accessible should be defunct when fission is not enabled."
+ );
+ isnot(
+ iframeAcc.firstChild,
+ iframeDocAcc,
+ "A new accessible is created for a IFRAME document."
+ );
+ }
+ is(
+ iframeAcc.firstChild,
+ newiframeDocAcc,
+ "A new accessible for a IFRAME document is the child of the IFRAME accessible"
+ );
+ },
+ { topLevel: false, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/fission/browser_src_change.js b/accessible/tests/browser/fission/browser_src_change.js
new file mode 100644
index 0000000000..e97cda3cc3
--- /dev/null
+++ b/accessible/tests/browser/fission/browser_src_change.js
@@ -0,0 +1,62 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ `<input id="textbox" value="hello"/>`,
+ async function (browser, iframeDocAcc, contentDocAcc) {
+ info(
+ "Check that the IFRAME and the IFRAME document are accessible initially."
+ );
+ let iframeAcc = findAccessibleChildByID(contentDocAcc, DEFAULT_IFRAME_ID);
+ ok(isAccessible(iframeAcc), "IFRAME should be accessible");
+ ok(isAccessible(iframeDocAcc), "IFRAME document should be accessible");
+
+ info("Replace src URL for the IFRAME with one with different origin.");
+ const onDocLoad = waitForEvent(
+ EVENT_DOCUMENT_LOAD_COMPLETE,
+ DEFAULT_IFRAME_DOC_BODY_ID
+ );
+
+ await SpecialPowers.spawn(
+ browser,
+ [DEFAULT_IFRAME_ID, CURRENT_CONTENT_DIR],
+ (id, olddir) => {
+ const { src } = content.document.getElementById(id);
+ content.document.getElementById(id).src = src.replace(
+ olddir,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.net/browser/accessible/tests/browser/"
+ );
+ }
+ );
+ const newiframeDocAcc = (await onDocLoad).accessible;
+
+ ok(isAccessible(iframeAcc), "IFRAME should be accessible");
+ ok(
+ isAccessible(newiframeDocAcc),
+ "new IFRAME document should be accessible"
+ );
+ isnot(
+ iframeDocAcc,
+ newiframeDocAcc,
+ "A new accessible is created for a IFRAME document."
+ );
+ is(
+ iframeAcc.firstChild,
+ newiframeDocAcc,
+ "An IFRAME has a new accessible for a IFRAME document as a child."
+ );
+ is(
+ newiframeDocAcc.parent,
+ iframeAcc,
+ "A new accessible for a IFRAME document has an IFRAME as a parent."
+ );
+ },
+ { topLevel: false, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/fission/browser_take_focus.js b/accessible/tests/browser/fission/browser_take_focus.js
new file mode 100644
index 0000000000..7ec037566d
--- /dev/null
+++ b/accessible/tests/browser/fission/browser_take_focus.js
@@ -0,0 +1,73 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+addAccessibleTask(
+ `<div role="group"><input id="textbox" value="hello"/></div>`,
+ async function (browser, iframeDocAcc, contentDocAcc) {
+ const textbox = findAccessibleChildByID(iframeDocAcc, "textbox");
+ const iframe = findAccessibleChildByID(contentDocAcc, "default-iframe-id");
+ const iframeDoc = findAccessibleChildByID(
+ contentDocAcc,
+ "default-iframe-body-id"
+ );
+ const root = getRootAccessible(document);
+
+ testStates(textbox, STATE_FOCUSABLE, 0, STATE_FOCUSED);
+
+ let onFocus = waitForEvent(EVENT_FOCUS, textbox);
+ textbox.takeFocus();
+ await onFocus;
+
+ testStates(textbox, STATE_FOCUSABLE | STATE_FOCUSED, 0);
+
+ is(
+ getAccessibleDOMNodeID(contentDocAcc.focusedChild),
+ "textbox",
+ "correct focusedChild from top doc"
+ );
+
+ is(
+ getAccessibleDOMNodeID(iframeDocAcc.focusedChild),
+ "textbox",
+ "correct focusedChild from iframe"
+ );
+
+ is(
+ getAccessibleDOMNodeID(root.focusedChild),
+ "textbox",
+ "correct focusedChild from root"
+ );
+
+ ok(!iframe.focusedChild, "correct focusedChild from iframe (null)");
+
+ onFocus = waitForEvent(EVENT_FOCUS, iframeDoc);
+ iframeDoc.takeFocus();
+ await onFocus;
+
+ is(
+ getAccessibleDOMNodeID(contentDocAcc.focusedChild),
+ "default-iframe-body-id",
+ "correct focusedChild of child doc from top doc"
+ );
+ is(
+ getAccessibleDOMNodeID(iframe.focusedChild),
+ "default-iframe-body-id",
+ "correct focusedChild of child doc from iframe"
+ );
+ is(
+ getAccessibleDOMNodeID(root.focusedChild),
+ "default-iframe-body-id",
+ "correct focusedChild of child doc from root"
+ );
+ },
+ { topLevel: false, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/fission/head.js b/accessible/tests/browser/fission/head.js
new file mode 100644
index 0000000000..afc50984bd
--- /dev/null
+++ b/accessible/tests/browser/fission/head.js
@@ -0,0 +1,18 @@
+/* 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";
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js.
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR }
+);
diff --git a/accessible/tests/browser/general/browser.ini b/accessible/tests/browser/general/browser.ini
new file mode 100644
index 0000000000..02b84f346e
--- /dev/null
+++ b/accessible/tests/browser/general/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ !/accessible/tests/browser/shared-head.js
+ head.js
+ !/accessible/tests/mochitest/*.js
+skip-if = a11y_checks
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_test_doc_creation.js]
+[browser_test_urlbar.js]
diff --git a/accessible/tests/browser/general/browser_test_doc_creation.js b/accessible/tests/browser/general/browser_test_doc_creation.js
new file mode 100644
index 0000000000..7ee07f63fd
--- /dev/null
+++ b/accessible/tests/browser/general/browser_test_doc_creation.js
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const tab1URL = `data:text/html,
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf-8"/>
+ <title>First tab to be loaded</title>
+ </head>
+ <body>
+ <butotn>JUST A BUTTON</butotn>
+ </body>
+ </html>`;
+
+const tab2URL = `data:text/html,
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf-8"/>
+ <title>Second tab to be loaded</title>
+ </head>
+ <body>
+ <butotn>JUST A BUTTON</butotn>
+ </body>
+ </html>`;
+
+// Checking that, if there are open windows before accessibility was started,
+// root accessibles for open windows are created so that all root accessibles
+// are stored in application accessible children array.
+add_task(async function testDocumentCreation() {
+ let tab1 = await openNewTab(tab1URL);
+ let tab2 = await openNewTab(tab2URL);
+ let accService = await initAccessibilityService();
+
+ info("Verifying that each tab content document is in accessible cache.");
+ for (const browser of [...gBrowser.browsers]) {
+ await SpecialPowers.spawn(browser, [], async () => {
+ let accServiceContent = Cc[
+ "@mozilla.org/accessibilityService;1"
+ ].getService(Ci.nsIAccessibilityService);
+ Assert.ok(
+ !!accServiceContent.getAccessibleFromCache(content.document),
+ "Document accessible is in cache."
+ );
+ });
+ }
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+
+ accService = null; // eslint-disable-line no-unused-vars
+ await shutdownAccessibilityService();
+});
diff --git a/accessible/tests/browser/general/browser_test_urlbar.js b/accessible/tests/browser/general/browser_test_urlbar.js
new file mode 100644
index 0000000000..5cfd66ee3a
--- /dev/null
+++ b/accessible/tests/browser/general/browser_test_urlbar.js
@@ -0,0 +1,40 @@
+/* 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 { UrlbarTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlbarTestUtils.sys.mjs"
+);
+
+// Checking that the awesomebar popup gets COMBOBOX_LIST role instead of
+// LISTBOX, since its parent is a <panel> (see Bug 1422465)
+add_task(async function testAutocompleteRichResult() {
+ let tab = await openNewTab("data:text/html;charset=utf-8,");
+ let accService = await initAccessibilityService();
+
+ info("Opening the URL bar and entering a key to show the urlbar panel");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: "a",
+ });
+
+ info("Waiting for accessibility to be created for the results list");
+ let resultsView;
+ resultsView = gURLBar.view.panel.querySelector(".urlbarView-results");
+ await TestUtils.waitForCondition(() =>
+ accService.getAccessibleFor(resultsView)
+ );
+
+ info("Confirming that the special case is handled in XULListboxAccessible");
+ let accessible = accService.getAccessibleFor(resultsView);
+ is(accessible.role, ROLE_COMBOBOX_LIST, "Right role");
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+registerCleanupFunction(async function () {
+ await shutdownAccessibilityService();
+});
diff --git a/accessible/tests/browser/general/head.js b/accessible/tests/browser/general/head.js
new file mode 100644
index 0000000000..841436043c
--- /dev/null
+++ b/accessible/tests/browser/general/head.js
@@ -0,0 +1,71 @@
+/* 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";
+
+/* exported initAccessibilityService, openNewTab, shutdownAccessibilityService */
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+const nsIAccessibleRole = Ci.nsIAccessibleRole; // eslint-disable-line no-unused-vars
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+async function openNewTab(url) {
+ const forceNewProcess = true;
+
+ return BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url,
+ forceNewProcess,
+ });
+}
+
+async function initAccessibilityService() {
+ info("Create accessibility service.");
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+
+ await new Promise(resolve => {
+ if (Services.appinfo.accessibilityEnabled) {
+ resolve();
+ return;
+ }
+
+ let observe = (subject, topic, data) => {
+ if (data === "1") {
+ Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+ resolve();
+ }
+ };
+ Services.obs.addObserver(observe, "a11y-init-or-shutdown");
+ });
+
+ return accService;
+}
+
+function shutdownAccessibilityService() {
+ forceGC();
+
+ return new Promise(resolve => {
+ if (!Services.appinfo.accessibilityEnabled) {
+ resolve();
+ return;
+ }
+
+ let observe = (subject, topic, data) => {
+ if (data === "0") {
+ Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+ resolve();
+ }
+ };
+ Services.obs.addObserver(observe, "a11y-init-or-shutdown");
+ });
+}
diff --git a/accessible/tests/browser/head.js b/accessible/tests/browser/head.js
new file mode 100644
index 0000000000..e7645f7f1c
--- /dev/null
+++ b/accessible/tests/browser/head.js
@@ -0,0 +1,146 @@
+/* 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";
+
+/* exported initAccService, shutdownAccService, waitForEvent, setE10sPrefs,
+ unsetE10sPrefs, accConsumersChanged */
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+const { CommonUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs"
+);
+
+/**
+ * Set e10s related preferences in the test environment.
+ * @return {Promise} promise that resolves when preferences are set.
+ */
+function setE10sPrefs() {
+ return new Promise(resolve =>
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [["browser.tabs.remote.autostart", true]],
+ },
+ resolve
+ )
+ );
+}
+
+/**
+ * Unset e10s related preferences in the test environment.
+ * @return {Promise} promise that resolves when preferences are unset.
+ */
+function unsetE10sPrefs() {
+ return new Promise(resolve => {
+ SpecialPowers.popPrefEnv(resolve);
+ });
+}
+
+/**
+ * Capture when 'a11y-consumers-changed' event is fired.
+ *
+ * @param {?Object} target
+ * [optional] browser object that indicates that accessibility service
+ * is in content process.
+ * @return {Array}
+ * List of promises where first one is the promise for when the event
+ * observer is added and the second one for when the event is observed.
+ */
+function accConsumersChanged(target) {
+ return target
+ ? [
+ SpecialPowers.spawn(target, [], () =>
+ content.CommonUtils.addAccConsumersChangedObserver()
+ ),
+ SpecialPowers.spawn(target, [], () =>
+ content.CommonUtils.observeAccConsumersChanged()
+ ),
+ ]
+ : [
+ CommonUtils.addAccConsumersChangedObserver(),
+ CommonUtils.observeAccConsumersChanged(),
+ ];
+}
+
+/**
+ * Capture when accessibility service is initialized.
+ *
+ * @param {?Object} target
+ * [optional] browser object that indicates that accessibility service
+ * is expected to be initialized in content process.
+ * @return {Array}
+ * List of promises where first one is the promise for when the event
+ * observer is added and the second one for when the event is observed.
+ */
+function initAccService(target) {
+ return target
+ ? [
+ SpecialPowers.spawn(target, [], () =>
+ content.CommonUtils.addAccServiceInitializedObserver()
+ ),
+ SpecialPowers.spawn(target, [], () =>
+ content.CommonUtils.observeAccServiceInitialized()
+ ),
+ ]
+ : [
+ CommonUtils.addAccServiceInitializedObserver(),
+ CommonUtils.observeAccServiceInitialized(),
+ ];
+}
+
+/**
+ * Capture when accessibility service is shutdown.
+ *
+ * @param {?Object} target
+ * [optional] browser object that indicates that accessibility service
+ * is expected to be shutdown in content process.
+ * @return {Array}
+ * List of promises where first one is the promise for when the event
+ * observer is added and the second one for when the event is observed.
+ */
+function shutdownAccService(target) {
+ return target
+ ? [
+ SpecialPowers.spawn(target, [], () =>
+ content.CommonUtils.addAccServiceShutdownObserver()
+ ),
+ SpecialPowers.spawn(target, [], () =>
+ content.CommonUtils.observeAccServiceShutdown()
+ ),
+ ]
+ : [
+ CommonUtils.addAccServiceShutdownObserver(),
+ CommonUtils.observeAccServiceShutdown(),
+ ];
+}
+
+/**
+ * Simpler verions of waitForEvent defined in
+ * accessible/tests/browser/events.js
+ */
+function waitForEvent(eventType, expectedId) {
+ return new Promise(resolve => {
+ let eventObserver = {
+ observe(subject) {
+ let event = subject.QueryInterface(Ci.nsIAccessibleEvent);
+ let id;
+ try {
+ id = event.accessible.id;
+ } catch (e) {
+ // This can throw NS_ERROR_FAILURE.
+ }
+ if (event.eventType === eventType && id === expectedId) {
+ Services.obs.removeObserver(this, "accessible-event");
+ resolve(event);
+ }
+ },
+ };
+ Services.obs.addObserver(eventObserver, "accessible-event");
+ });
+}
diff --git a/accessible/tests/browser/hittest/browser.ini b/accessible/tests/browser/hittest/browser.ini
new file mode 100644
index 0000000000..ae87ecb430
--- /dev/null
+++ b/accessible/tests/browser/hittest/browser.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_test_browser.js]
+[browser_test_general.js]
+[browser_test_scroll_hittest.js]
+[browser_test_shadowroot.js]
+[browser_test_text.js]
+[browser_test_zoom.js]
+[browser_test_zoom_text.js]
diff --git a/accessible/tests/browser/hittest/browser_test_browser.js b/accessible/tests/browser/hittest/browser_test_browser.js
new file mode 100644
index 0000000000..477af42fe9
--- /dev/null
+++ b/accessible/tests/browser/hittest/browser_test_browser.js
@@ -0,0 +1,68 @@
+/* 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";
+
+async function runTests(browser, accDoc) {
+ // Hit testing. See bug #726097
+ await invokeContentTask(browser, [], () =>
+ content.document.getElementById("hittest").scrollIntoView(true)
+ );
+
+ const dpr = await getContentDPR(browser);
+ const hititem = findAccessibleChildByID(accDoc, "hititem");
+ const hittest = findAccessibleChildByID(accDoc, "hittest");
+ const outerDocAcc = accDoc.parent;
+ const rootAcc = CommonUtils.getRootAccessible(document);
+
+ const [hitX, hitY, hitWidth, hitHeight] = Layout.getBounds(hititem, dpr);
+ // "hititem" node has the full screen width, so when we divide it by 2, we are
+ // still way outside the inline content.
+ const tgtX = hitX + hitWidth / 2;
+ const tgtY = hitY + hitHeight / 2;
+
+ let hitAcc = rootAcc.getDeepestChildAtPoint(tgtX, tgtY);
+ is(
+ hitAcc,
+ hititem,
+ `Hit match at ${tgtX},${tgtY} (root doc deepest child). Found: ${prettyName(
+ hitAcc
+ )}`
+ );
+
+ const hitAcc2 = accDoc.getDeepestChildAtPoint(tgtX, tgtY);
+ is(
+ hitAcc,
+ hitAcc2,
+ `Hit match at ${tgtX},${tgtY} (doc deepest child). Found: ${prettyName(
+ hitAcc2
+ )}`
+ );
+
+ hitAcc = outerDocAcc.getChildAtPoint(tgtX, tgtY);
+ is(
+ hitAcc,
+ accDoc,
+ `Hit match at ${tgtX},${tgtY} (outer doc child). Found: ${prettyName(
+ hitAcc
+ )}`
+ );
+
+ hitAcc = accDoc.getChildAtPoint(tgtX, tgtY);
+ is(
+ hitAcc,
+ hittest,
+ `Hit match at ${tgtX},${tgtY} (doc child). Found: ${prettyName(hitAcc)}`
+ );
+}
+
+addAccessibleTask(
+ `
+ <div id="hittest">
+ <div id="hititem"><span role="img">img</span>item</div>
+ </div>
+ `,
+ runTests,
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/hittest/browser_test_general.js b/accessible/tests/browser/hittest/browser_test_general.js
new file mode 100644
index 0000000000..f8f7f26d06
--- /dev/null
+++ b/accessible/tests/browser/hittest/browser_test_general.js
@@ -0,0 +1,339 @@
+/* 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";
+
+async function runTests(browser, accDoc) {
+ await waitForImageMap(browser, accDoc);
+ const dpr = await getContentDPR(browser);
+
+ await testChildAtPoint(
+ dpr,
+ 3,
+ 3,
+ findAccessibleChildByID(accDoc, "list"),
+ findAccessibleChildByID(accDoc, "listitem"),
+ findAccessibleChildByID(accDoc, "inner").firstChild
+ );
+ todo(
+ false,
+ "Bug 746974 - children must match on all platforms. On Windows, " +
+ "ChildAtPoint with eDeepestChild is incorrectly ignoring MustPrune " +
+ "for the graphic."
+ );
+
+ const txt = findAccessibleChildByID(accDoc, "txt");
+ await testChildAtPoint(dpr, 1, 1, txt, txt, txt);
+
+ info(
+ "::MustPrune case, point is outside of textbox accessible but is in document."
+ );
+ await testChildAtPoint(dpr, -1, -1, txt, null, null);
+
+ info("::MustPrune case, point is outside of root accessible.");
+ await testChildAtPoint(dpr, -10000, -10000, txt, null, null);
+
+ info("Not specific case, point is inside of btn accessible.");
+ const btn = findAccessibleChildByID(accDoc, "btn");
+ await testChildAtPoint(dpr, 1, 1, btn, btn, btn);
+
+ info("Not specific case, point is outside of btn accessible.");
+ await testChildAtPoint(dpr, -1, -1, btn, null, null);
+
+ info(
+ "Out of flow accessible testing, do not return out of flow accessible " +
+ "because it's not a child of the accessible even though visually it is."
+ );
+ await invokeContentTask(browser, [], () => {
+ const { CommonUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs"
+ );
+ const doc = content.document;
+ const rectArea = CommonUtils.getNode("area", doc).getBoundingClientRect();
+ const outOfFlow = CommonUtils.getNode("outofflow", doc);
+ outOfFlow.style.left = rectArea.left + "px";
+ outOfFlow.style.top = rectArea.top + "px";
+ });
+
+ const area = findAccessibleChildByID(accDoc, "area");
+ await testChildAtPoint(dpr, 1, 1, area, area, area);
+
+ info("Test image maps. Their children are not in the layout tree.");
+ const imgmap = findAccessibleChildByID(accDoc, "imgmap");
+ const theLetterA = imgmap.firstChild;
+ await hitTest(browser, imgmap, theLetterA, theLetterA);
+ await hitTest(
+ browser,
+ findAccessibleChildByID(accDoc, "container"),
+ imgmap,
+ theLetterA
+ );
+
+ info("hit testing for element contained by zero-width element");
+ const container2Input = findAccessibleChildByID(accDoc, "container2_input");
+ await hitTest(
+ browser,
+ findAccessibleChildByID(accDoc, "container2"),
+ container2Input,
+ container2Input
+ );
+
+ info("hittesting table, row, cells -- rows are not in the layout tree");
+ const table = findAccessibleChildByID(accDoc, "table");
+ const row = findAccessibleChildByID(accDoc, "row");
+ const cell1 = findAccessibleChildByID(accDoc, "cell1");
+
+ await hitTest(browser, table, row, cell1);
+
+ info("Testing that an inaccessible child doesn't break hit testing");
+ const containerWithInaccessibleChild = findAccessibleChildByID(
+ accDoc,
+ "containerWithInaccessibleChild"
+ );
+ const containerWithInaccessibleChildP2 = findAccessibleChildByID(
+ accDoc,
+ "containerWithInaccessibleChild_p2"
+ );
+ await hitTest(
+ browser,
+ containerWithInaccessibleChild,
+ containerWithInaccessibleChildP2,
+ containerWithInaccessibleChildP2.firstChild
+ );
+
+ info("Testing wrapped text");
+ const wrappedTextP = findAccessibleChildByID(accDoc, "wrappedTextP");
+ const wrappedTextA = findAccessibleChildByID(accDoc, "wrappedTextA");
+ await hitTest(browser, wrappedTextP, wrappedTextA, wrappedTextA.firstChild);
+}
+
+addAccessibleTask(
+ `
+ <div role="list" id="list">
+ <div role="listitem" id="listitem"><span title="foo" id="inner">inner</span>item</div>
+ </div>
+
+ <span role="button">button1</span><span role="button" id="btn">button2</span>
+
+ <span role="textbox">textbox1</span><span role="textbox" id="txt">textbox2</span>
+
+ <div id="outofflow" style="width: 10px; height: 10px; position: absolute; left: 0px; top: 0px; background-color: yellow;">
+ </div>
+ <div id="area" style="width: 100px; height: 100px; background-color: blue;"></div>
+
+ <map name="atoz_map">
+ <area id="thelettera" href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,15,15" alt="thelettera" shape="rect"/>
+ </map>
+
+ <div id="container">
+ <img id="imgmap" width="447" height="15" usemap="#atoz_map" src="http://example.com/a11y/accessible/tests/mochitest/letters.gif"/>
+ </div>
+
+ <div id="container2" style="width: 0px">
+ <input id="container2_input">
+ </div>
+
+ <table id="table" border>
+ <tr id="row">
+ <td id="cell1">hello</td>
+ <td id="cell2">world</td>
+ </tr>
+ </table>
+
+ <div id="containerWithInaccessibleChild">
+ <p>hi</p>
+ <p aria-hidden="true">hi</p>
+ <p id="containerWithInaccessibleChild_p2">bye</p>
+ </div>
+
+ <p id="wrappedTextP" style="width: 3ch; font-family: monospace;">
+ <a id="wrappedTextA" href="https://example.com/">a</a>b cd
+ </p>
+ `,
+ runTests,
+ {
+ iframe: true,
+ remoteIframe: true,
+ // Ensure that all hittest elements are in view.
+ iframeAttrs: { style: "width: 600px; height: 600px; padding: 10px;" },
+ }
+);
+
+addAccessibleTask(
+ `
+ <div id="container">
+ <h1 id="a">A</h1><h1 id="b">B</h1>
+ </div>
+ `,
+ async function (browser, accDoc) {
+ const a = findAccessibleChildByID(accDoc, "a");
+ const b = findAccessibleChildByID(accDoc, "b");
+ const dpr = await getContentDPR(browser);
+ // eslint-disable-next-line no-unused-vars
+ const [x, y, w, h] = Layout.getBounds(a, dpr);
+ // The point passed below will be made relative to `b`, but
+ // we'd like to test a point within `a`. Pass `a`s negative
+ // width for an x offset. Pass zero as a y offset,
+ // assuming the headings are on the same line.
+ await testChildAtPoint(dpr, -w, 0, b, null, null);
+ },
+ {
+ iframe: true,
+ remoteIframe: true,
+ // Ensure that all hittest elements are in view.
+ iframeAttrs: { style: "width: 600px; height: 600px; padding: 10px;" },
+ }
+);
+
+addAccessibleTask(
+ `
+ <style>
+ div {
+ width: 50px;
+ height: 50px;
+ position: relative;
+ }
+
+ div > div {
+ width: 30px;
+ height: 30px;
+ position: absolute;
+ opacity: 0.9;
+ }
+ </style>
+ <div id="a" style="background-color: orange;">
+ <div id="aa" style="background-color: purple;"></div>
+ </div>
+ <div id="b" style="background-color: yellowgreen;">
+ <div id="bb" style="top: -30px; background-color: turquoise"></div>
+ </div>`,
+ async function (browser, accDoc) {
+ const a = findAccessibleChildByID(accDoc, "a");
+ const aa = findAccessibleChildByID(accDoc, "aa");
+ const dpr = await getContentDPR(browser);
+ const [, , w, h] = Layout.getBounds(a, dpr);
+ // test upper left of `a`
+ await testChildAtPoint(dpr, 1, 1, a, aa, aa);
+ // test upper right of `a`
+ await testChildAtPoint(dpr, w - 1, 1, a, a, a);
+ // test just outside upper left of `a`
+ await testChildAtPoint(dpr, 1, -1, a, null, null);
+ // test halfway down/left of `a`
+ await testChildAtPoint(dpr, 1, Math.round(h / 2), a, a, a);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: false,
+ remoteIframe: false,
+ // Ensure that all hittest elements are in view.
+ iframeAttrs: { style: "width: 600px; height: 600px; padding: 10px;" },
+ }
+);
+
+/**
+ * Verify that hit testing returns the proper accessible when one acc content
+ * is partially hidden due to overflow:hidden;
+ */
+addAccessibleTask(
+ `
+ <style>
+ div div {
+ overflow: hidden;
+ font-family: monospace;
+ width: 2ch;
+ }
+ </style>
+ <div id="container" style="display: flex; flex-direction: row-reverse;">
+ <div id="aNode">abcde</div><div id="fNode">fghij</div>
+ </div>`,
+ async function (browser, docAcc) {
+ const container = findAccessibleChildByID(docAcc, "container");
+ const aNode = findAccessibleChildByID(docAcc, "aNode");
+ const fNode = findAccessibleChildByID(docAcc, "fNode");
+ const dpr = await getContentDPR(browser);
+ const [, , containerWidth] = Layout.getBounds(container, dpr);
+ const [, , aNodeWidth] = Layout.getBounds(aNode, dpr);
+
+ await testChildAtPoint(
+ dpr,
+ containerWidth - 1,
+ 1,
+ container,
+ aNode,
+ aNode.firstChild
+ );
+ await testChildAtPoint(
+ dpr,
+ containerWidth - aNodeWidth - 1,
+ 1,
+ container,
+ fNode,
+ fNode.firstChild
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Verify that hit testing is appropriately fuzzy when working with generics.
+ * If we match on a generic which contains additional generics and a single text
+ * leaf, we should return the text leaf as the deepest match instead of the
+ * generic itself.
+ */
+addAccessibleTask(
+ `
+ <a href="example.com" id="link">
+ <span style="overflow:hidden;" id="generic"><span aria-hidden="true" id="visible">I am some visible text</span><span id="invisible" style="overflow:hidden; height: 1px; width: 1px; position:absolute; clip: rect(0 0 0 0); display:block;">I am some invisible text</span></span>
+ </a>`,
+ async function (browser, docAcc) {
+ const link = findAccessibleChildByID(docAcc, "link");
+ const generic = findAccessibleChildByID(docAcc, "generic");
+ const invisible = findAccessibleChildByID(docAcc, "invisible");
+ const dpr = await getContentDPR(browser);
+
+ await testChildAtPoint(
+ dpr,
+ 1,
+ 1,
+ link,
+ generic, // Direct Child
+ invisible.firstChild // Deepest Child
+ );
+
+ await testOffsetAtPoint(
+ findAccessibleChildByID(docAcc, "invisible", [Ci.nsIAccessibleText]),
+ 1,
+ 1,
+ COORDTYPE_PARENT_RELATIVE,
+ 0
+ );
+ },
+ { chrome: false, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Verify that hit testing is appropriately fuzzy when working with generics with siblings.
+ * We should return the deepest text leaf as the deepest match instead of the generic itself.
+ */
+addAccessibleTask(
+ `
+<div id="generic"><span aria-hidden="true" id="visible">Mozilla</span><span id="invisible" style="display: block !important;border: 0 !important;clip: rect(0 0 0 0) !important;height: 1px !important;margin: -1px !important;overflow: hidden !important;padding: 0 !important;position: absolute !important;white-space: nowrap !important;width: 1px !important;">hello world<br><div id="extraContainer">Mozilla</div></span><br>I am some other text</div>`,
+ async function (browser, docAcc) {
+ const generic = findAccessibleChildByID(docAcc, "generic");
+ const invisible = findAccessibleChildByID(docAcc, "invisible");
+ const dpr = await getContentDPR(browser);
+
+ await testChildAtPoint(
+ dpr,
+ 1,
+ 1,
+ generic,
+ invisible, // Direct Child
+ invisible.firstChild // Deepest Child
+ );
+ },
+ { chrome: false, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/hittest/browser_test_scroll_hittest.js b/accessible/tests/browser/hittest/browser_test_scroll_hittest.js
new file mode 100644
index 0000000000..246dcd3d09
--- /dev/null
+++ b/accessible/tests/browser/hittest/browser_test_scroll_hittest.js
@@ -0,0 +1,105 @@
+/* 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";
+
+/**
+ * Verify that hit testing returns the proper accessible when one accessible
+ * covers another accessible due to scroll clipping. See Bug 1819741.
+ */
+addAccessibleTask(
+ `
+<div id="container" style="height: 100%; position: absolute; flex-direction: column; display: flex;">
+ <div id="title-bar" style="height: 500px; background-color: red;"></div>
+ <div id="message-container" style="overflow-y: hidden; display: flex;">
+ <div style="overflow-y: auto;" id="message-scrollable">
+ <p style="white-space: pre-line;">
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dictum luctus molestie. Nam in libero mi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
+
+ Praesent aliquet semper libero, eu ullamcorper tortor vestibulum ac. Sed non pharetra sem. Quisque sodales ipsum a ipsum condimentum porttitor. Integer luctus pellentesque ipsum, eu dignissim nunc fermentum in.
+
+ Etiam blandit nisl vitae dolor molestie faucibus. In euismod, massa vitae commodo bibendum, urna augue pharetra nibh, et sagittis libero est in ligula. Mauris tincidunt risus ornare, rutrum augue in, blandit ligula. Aenean ultrices vel risus sit amet varius.
+
+ Vivamus pretium ultricies nisi a cursus. Integer cursus quam a metus ultricies, vel pulvinar nunc varius. Quisque facilisis lorem eget ipsum vehicula, laoreet congue lorem viverra.
+
+ Praesent dignissim, diam sed semper ultricies, diam ex laoreet justo, ac euismod massa metus pharetra nunc. Vestibulum sapien erat, consequat at eleifend id, suscipit sit amet mi.
+
+ Curabitur sed mauris vitae justo rutrum convallis ac sed justo. Ut nec est sed nisi feugiat egestas. Mauris accumsan mi eget nibh fermentum, in dignissim odio feugiat.
+
+ Maecenas augue dolor, gravida ut ultrices ultricies, condimentum et dui. In sed augue fermentum, posuere velit et, pulvinar tellus. Morbi id fermentum quam, at varius arcu.
+
+ Duis elementum vitae sapien id tincidunt. Aliquam velit ligula, sollicitudin eget placerat non, aliquam at erat. Pellentesque non porta metus. Mauris vel finibus sem, nec ullamcorper leo.
+
+ Nulla sit amet lorem vitae diam consectetur porttitor a cursus massa. Sed id ornare lorem. Sed placerat facilisis ipsum et ultricies. Sed eu semper enim, ut aliquet odio.
+
+ Sed nulla ex, pharetra vel porttitor congue, dictum et purus. Suspendisse vel risus sit amet nulla volutpat ullamcorper. Morbi et ullamcorper est. Pellentesque eget porta risus. Nullam non felis elementum, auctor massa et, consectetur neque.
+
+ Fusce sit amet arcu finibus, ornare sem sed, tempus nibh. Donec rutrum odio eget bibendum pulvinar. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
+
+ Phasellus sed risus diam. Vivamus mollis, risus ac feugiat pellentesque, ligula tortor finibus libero, et venenatis turpis lectus et justo. Suspendisse euismod mi at lectus sagittis dignissim. Mauris a ornare enim.
+ </p>
+ </div>
+ </div>
+ <div id="footer-bar" style="height: 500px; background-color: blue;"></div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const container = findAccessibleChildByID(docAcc, "container");
+ const scrollable = findAccessibleChildByID(docAcc, "message-scrollable");
+ const titleBar = findAccessibleChildByID(docAcc, "title-bar");
+ const footerBar = findAccessibleChildByID(docAcc, "footer-bar");
+ const dpr = await getContentDPR(browser);
+ const [, , , titleBarHeight] = Layout.getBounds(titleBar, dpr);
+ const [, , , scrollableHeight] = Layout.getBounds(scrollable, dpr);
+
+ // Verify that the child at this point is not the underlying paragraph.
+ info(
+ "Testing that the deepest child at this point is the overlaid section, not the paragraph beneath it."
+ );
+ await testChildAtPoint(
+ dpr,
+ 1,
+ titleBarHeight - 1,
+ container,
+ titleBar,
+ titleBar
+ );
+ await testChildAtPoint(
+ dpr,
+ 1,
+ titleBarHeight + scrollableHeight + 1,
+ container,
+ footerBar,
+ footerBar
+ );
+
+ await invokeContentTask(browser, [], () => {
+ // Scroll the text down.
+ let elem = content.document.getElementById("message-scrollable");
+ elem.scrollTo(0, elem.scrollHeight);
+ });
+ await waitForContentPaint(browser);
+
+ info(
+ "Testing that the deepest child at this point is still the overlaid section, after scrolling the paragraph."
+ );
+ await testChildAtPoint(
+ dpr,
+ 1,
+ titleBarHeight - 1,
+ container,
+ titleBar,
+ titleBar
+ );
+ await testChildAtPoint(
+ dpr,
+ 1,
+ titleBarHeight + scrollableHeight + 1,
+ container,
+ footerBar,
+ footerBar
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/hittest/browser_test_shadowroot.js b/accessible/tests/browser/hittest/browser_test_shadowroot.js
new file mode 100644
index 0000000000..94a5ce071a
--- /dev/null
+++ b/accessible/tests/browser/hittest/browser_test_shadowroot.js
@@ -0,0 +1,61 @@
+/* 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";
+
+async function runTests(browser, accDoc) {
+ const dpr = await getContentDPR(browser);
+ let componentAcc = findAccessibleChildByID(accDoc, "component1");
+ await testChildAtPoint(
+ dpr,
+ 1,
+ 1,
+ componentAcc,
+ componentAcc.firstChild,
+ componentAcc.firstChild
+ );
+
+ componentAcc = findAccessibleChildByID(accDoc, "component2");
+ await testChildAtPoint(
+ dpr,
+ 1,
+ 1,
+ componentAcc,
+ componentAcc.firstChild,
+ componentAcc.firstChild
+ );
+}
+
+addAccessibleTask(
+ `
+ <div role="group" class="components" id="component1" style="display: inline-block;">
+ <!--
+ <div role="button" id="component-child"
+ style="width: 100px; height: 100px; background-color: pink;">
+ </div>
+ -->
+ </div>
+ <div role="group" class="components" id="component2" style="display: inline-block;">
+ <!--
+ <button>Hello world</button>
+ -->
+ </div>
+ <script>
+ // This routine adds the comment children of each 'component' to its
+ // shadow root.
+ var components = document.querySelectorAll(".components");
+ for (var i = 0; i < components.length; i++) {
+ var component = components[i];
+ var shadow = component.attachShadow({mode: "open"});
+ for (var child = component.firstChild; child; child = child.nextSibling) {
+ if (child.nodeType === 8)
+ // eslint-disable-next-line no-unsanitized/property
+ shadow.innerHTML = child.data;
+ }
+ }
+ </script>
+ `,
+ runTests,
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/hittest/browser_test_text.js b/accessible/tests/browser/hittest/browser_test_text.js
new file mode 100644
index 0000000000..130f077f29
--- /dev/null
+++ b/accessible/tests/browser/hittest/browser_test_text.js
@@ -0,0 +1,84 @@
+/* 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";
+
+addAccessibleTask(
+ `
+a
+<div id="noChars" style="width: 5px; height: 5px;"><p></p></div>
+<p id="twoText"><span>a</span><span>b</span></p>
+<div id="iframeAtEnd" style="width: 20px; height: 20px;">
+ a
+ <iframe width="1" height="1"></iframe>
+</div>
+<button id="pointBeforeText">
+ <div style="display: flex;">
+ <div style="width: 100px; background-color: red;" role="none"></div>
+ test
+ <div style="width: 100px; background-color: blue;" role="none"></div>
+ </div>
+</button>
+ `,
+ async function (browser, docAcc) {
+ const dpr = await getContentDPR(browser);
+ // Test getOffsetAtPoint on a container containing no characters. The inner
+ // container does not include the requested point, but the outer one does.
+ const noChars = findAccessibleChildByID(docAcc, "noChars", [
+ Ci.nsIAccessibleText,
+ ]);
+ let [x, y] = Layout.getBounds(noChars, dpr);
+ await testOffsetAtPoint(noChars, x, y, COORDTYPE_SCREEN_RELATIVE, -1);
+
+ // Test that the correct offset is returned for a point in a second text
+ // leaf.
+ const twoText = findAccessibleChildByID(docAcc, "twoText", [
+ Ci.nsIAccessibleText,
+ ]);
+ const text2 = twoText.getChildAt(1);
+ [x, y] = Layout.getBounds(text2, dpr);
+ await testOffsetAtPoint(twoText, x, y, COORDTYPE_SCREEN_RELATIVE, 1);
+
+ // Test offsetAtPoint when there is an iframe at the end of the container.
+ const iframeAtEnd = findAccessibleChildByID(docAcc, "iframeAtEnd", [
+ Ci.nsIAccessibleText,
+ ]);
+ let width;
+ let height;
+ [x, y, width, height] = Layout.getBounds(iframeAtEnd, dpr);
+ x += width - 1;
+ y += height - 1;
+ await testOffsetAtPoint(iframeAtEnd, x, y, COORDTYPE_SCREEN_RELATIVE, -1);
+
+ // Test that 0 is returned if the point is within the container but before
+ // the rectangle at offset 0. This is buggy behavior that some users have
+ // unfortunately come to rely on (bug 1816601).
+ const pointBeforeText = findAccessibleChildByID(docAcc, "pointBeforeText", [
+ Ci.nsIAccessibleText,
+ ]);
+ [x, y, width, height] = Layout.getBounds(pointBeforeText, dpr);
+ await testOffsetAtPoint(
+ pointBeforeText,
+ x + 1,
+ y + 1,
+ COORDTYPE_SCREEN_RELATIVE,
+ 0
+ );
+ // But this buggy behavior only applies for a point before offset 0, not
+ // a point after the last offset. So it's asymmetrically buggy. :(
+ await testOffsetAtPoint(
+ pointBeforeText,
+ x + width - 1,
+ y + height - 1,
+ COORDTYPE_SCREEN_RELATIVE,
+ -1
+ );
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
diff --git a/accessible/tests/browser/hittest/browser_test_zoom.js b/accessible/tests/browser/hittest/browser_test_zoom.js
new file mode 100644
index 0000000000..84383df483
--- /dev/null
+++ b/accessible/tests/browser/hittest/browser_test_zoom.js
@@ -0,0 +1,38 @@
+/* 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";
+
+async function runTests(browser, accDoc) {
+ if (Services.appinfo.OS !== "Darwin") {
+ const p1 = findAccessibleChildByID(accDoc, "p1");
+ const p2 = findAccessibleChildByID(accDoc, "p2");
+ await hitTest(browser, accDoc, p1, p1.firstChild);
+ await hitTest(browser, accDoc, p2, p2.firstChild);
+
+ await invokeContentTask(browser, [], () => {
+ const { Layout } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs"
+ );
+
+ Layout.zoomDocument(content.document, 2.0);
+ content.document.body.offsetTop; // getBounds doesn't flush layout on its own.
+ });
+
+ await hitTest(browser, accDoc, p1, p1.firstChild);
+ await hitTest(browser, accDoc, p2, p2.firstChild);
+ } else {
+ todo(
+ false,
+ "Bug 746974 - deepest child must be correct on all platforms, disabling on Mac!"
+ );
+ }
+}
+
+addAccessibleTask(`<p id="p1">para 1</p><p id="p2">para 2</p>`, runTests, {
+ iframe: true,
+ remoteIframe: true,
+ // Ensure that all hittest elements are in view.
+ iframeAttrs: { style: "left: 100px; top: 100px;" },
+});
diff --git a/accessible/tests/browser/hittest/browser_test_zoom_text.js b/accessible/tests/browser/hittest/browser_test_zoom_text.js
new file mode 100644
index 0000000000..9e429c16b3
--- /dev/null
+++ b/accessible/tests/browser/hittest/browser_test_zoom_text.js
@@ -0,0 +1,64 @@
+/* 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";
+
+async function runTests(browser, accDoc) {
+ const expectedLength = await invokeContentTask(browser, [], () => {
+ const { CommonUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs"
+ );
+ const hyperText = CommonUtils.getNode("paragraph", content.document);
+ return Math.floor(hyperText.textContent.length / 2);
+ });
+ const hyperText = findAccessibleChildByID(accDoc, "paragraph", [
+ Ci.nsIAccessibleText,
+ ]);
+ const textNode = hyperText.firstChild;
+
+ let [x, y, width, height] = Layout.getBounds(
+ textNode,
+ await getContentDPR(browser)
+ );
+
+ await testOffsetAtPoint(
+ hyperText,
+ x + width / 2,
+ y + height / 2,
+ COORDTYPE_SCREEN_RELATIVE,
+ expectedLength
+ );
+
+ await invokeContentTask(browser, [], () => {
+ const { Layout } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs"
+ );
+
+ Layout.zoomDocument(content.document, 2.0);
+ content.document.body.offsetTop; // getBounds doesn't flush layout on its own.
+ });
+
+ [x, y, width, height] = Layout.getBounds(
+ textNode,
+ await getContentDPR(browser)
+ );
+
+ await testOffsetAtPoint(
+ hyperText,
+ x + width / 2,
+ y + height / 2,
+ COORDTYPE_SCREEN_RELATIVE,
+ expectedLength
+ );
+}
+
+addAccessibleTask(
+ `<p id="paragraph" style="font-family: monospace;">hello world hello world</p>`,
+ runTests,
+ {
+ iframe: true,
+ remoteIframe: true,
+ iframeAttrs: { style: "width: 600px; height: 600px;" },
+ }
+);
diff --git a/accessible/tests/browser/hittest/head.js b/accessible/tests/browser/hittest/head.js
new file mode 100644
index 0000000000..c2904b1578
--- /dev/null
+++ b/accessible/tests/browser/hittest/head.js
@@ -0,0 +1,113 @@
+/* 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";
+
+/* exported CommonUtils, testChildAtPoint, Layout, hitTest, testOffsetAtPoint */
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js.
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR }
+);
+
+const { CommonUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs"
+);
+
+const { Layout } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs"
+);
+
+function getChildAtPoint(container, x, y, findDeepestChild) {
+ try {
+ return findDeepestChild
+ ? container.getDeepestChildAtPoint(x, y)
+ : container.getChildAtPoint(x, y);
+ } catch (e) {
+ // Failed to get child at point.
+ }
+ info("could not get child at point");
+ return null;
+}
+
+async function testChildAtPoint(dpr, x, y, container, child, grandChild) {
+ const [containerX, containerY] = Layout.getBounds(container, dpr);
+ x += containerX;
+ y += containerY;
+ let actual = null;
+ await untilCacheIs(
+ () => {
+ actual = getChildAtPoint(container, x, y, false);
+ info(`Got direct child match of ${CommonUtils.prettyName(actual)}`);
+ return actual;
+ },
+ child,
+ `Wrong direct child accessible at the point (${x}, ${y}) of ${CommonUtils.prettyName(
+ container
+ )}, sought ${CommonUtils.prettyName(
+ child
+ )} and got ${CommonUtils.prettyName(actual)}`
+ );
+ actual = null;
+ await untilCacheIs(
+ () => {
+ actual = getChildAtPoint(container, x, y, true);
+ info(`Got deepest child match of ${CommonUtils.prettyName(actual)}`);
+ return actual;
+ },
+ grandChild,
+ `Wrong deepest child accessible at the point (${x}, ${y}) of ${CommonUtils.prettyName(
+ container
+ )}, sought ${CommonUtils.prettyName(
+ grandChild
+ )} and got ${CommonUtils.prettyName(actual)}`
+ );
+}
+
+/**
+ * Test if getChildAtPoint returns the given child and grand child accessibles
+ * at coordinates of child accessible (direct and deep hit test).
+ */
+async function hitTest(browser, container, child, grandChild) {
+ const [childX, childY] = await getContentBoundsForDOMElm(
+ browser,
+ getAccessibleDOMNodeID(child)
+ );
+ const x = childX + 1;
+ const y = childY + 1;
+
+ await untilCacheIs(
+ () => getChildAtPoint(container, x, y, false),
+ child,
+ `Wrong direct child accessible at the point (${x}, ${y}) of ${CommonUtils.prettyName(
+ container
+ )}, sought ${CommonUtils.prettyName(child)}`
+ );
+ await untilCacheIs(
+ () => getChildAtPoint(container, x, y, true),
+ grandChild,
+ `Wrong deepest child accessible at the point (${x}, ${y}) of ${CommonUtils.prettyName(
+ container
+ )}, sought ${CommonUtils.prettyName(grandChild)}`
+ );
+}
+
+/**
+ * Test if getOffsetAtPoint returns the given text offset at given coordinates.
+ */
+async function testOffsetAtPoint(hyperText, x, y, coordType, expectedOffset) {
+ await untilCacheIs(
+ () => hyperText.getOffsetAtPoint(x, y, coordType),
+ expectedOffset,
+ `Wrong offset at given point (${x}, ${y}) for ${prettyName(hyperText)}`
+ );
+}
diff --git a/accessible/tests/browser/mac/browser.ini b/accessible/tests/browser/mac/browser.ini
new file mode 100644
index 0000000000..f88cfc56ca
--- /dev/null
+++ b/accessible/tests/browser/mac/browser.ini
@@ -0,0 +1,58 @@
+[DEFAULT]
+subsuite = a11y
+skip-if = os != 'mac'
+support-files =
+ head.js
+ doc_aria_tabs.html
+ doc_textmarker_test.html
+ doc_rich_listbox.xhtml
+ doc_menulist.xhtml
+ doc_tree.xhtml
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+ !/accessible/tests/mochitest/moz.png
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_app.js]
+https_first_disabled = true
+[browser_aria_current.js]
+[browser_aria_expanded.js]
+[browser_details_summary.js]
+[browser_label_title.js]
+[browser_range.js]
+[browser_roles_elements.js]
+[browser_table.js]
+[browser_selectables.js]
+[browser_radio_position.js]
+[browser_toggle_radio_check.js]
+[browser_link.js]
+[browser_aria_haspopup.js]
+[browser_required.js]
+[browser_popupbutton.js]
+[browser_mathml.js]
+[browser_input.js]
+[browser_focus.js]
+[browser_text_leaf.js]
+[browser_webarea.js]
+[browser_text_basics.js]
+[browser_text_input.js]
+skip-if =
+ os == "mac" # Bug 1778821
+[browser_rotor.js]
+[browser_rootgroup.js]
+[browser_text_selection.js]
+[browser_navigate.js]
+[browser_outline.js]
+[browser_outline_xul.js]
+[browser_hierarchy.js]
+[browser_menulist.js]
+[browser_rich_listbox.js]
+[browser_live_regions.js]
+[browser_aria_busy.js]
+[browser_aria_controls_flowto.js]
+[browser_attributed_text.js]
+[browser_bounds.js]
+[browser_heading.js]
diff --git a/accessible/tests/browser/mac/browser_app.js b/accessible/tests/browser/mac/browser_app.js
new file mode 100644
index 0000000000..7bb69e273a
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_app.js
@@ -0,0 +1,351 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+function getMacAccessible(accOrElmOrID) {
+ return new Promise(resolve => {
+ let intervalId = setInterval(() => {
+ let acc = getAccessible(accOrElmOrID);
+ if (acc) {
+ clearInterval(intervalId);
+ resolve(
+ acc.nativeInterface.QueryInterface(Ci.nsIAccessibleMacInterface)
+ );
+ }
+ }, 10);
+ });
+}
+
+/**
+ * Test a11yUtils announcements are exposed to VO
+ */
+add_task(async () => {
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "data:text/html,"
+ );
+ const alert = document.getElementById("a11y-announcement");
+ ok(alert, "Found alert to send announcements");
+
+ const alerted = waitForMacEvent("AXAnnouncementRequested", (iface, data) => {
+ return data.AXAnnouncementKey == "hello world";
+ });
+
+ A11yUtils.announce({
+ raw: "hello world",
+ });
+ await alerted;
+ await BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Test browser tabs
+ */
+add_task(async () => {
+ let newTabs = await Promise.all([
+ BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "data:text/html,<title>Two</title>"
+ ),
+ BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "data:text/html,<title>Three</title>"
+ ),
+ BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "data:text/html,<title>Four</title>"
+ ),
+ ]);
+
+ // Mochitests spawn with a tab, and we've opened 3 more for a total of 4 tabs
+ is(gBrowser.tabs.length, 4, "We now have 4 open tabs");
+
+ let tablist = await getMacAccessible("tabbrowser-tabs");
+ is(
+ tablist.getAttributeValue("AXRole"),
+ "AXTabGroup",
+ "Correct role for tablist"
+ );
+
+ let tabMacAccs = tablist.getAttributeValue("AXTabs");
+ is(tabMacAccs.length, 4, "4 items in AXTabs");
+
+ let selectedTabs = tablist.getAttributeValue("AXSelectedChildren");
+ is(selectedTabs.length, 1, "one selected tab");
+
+ let tab = selectedTabs[0];
+ is(tab.getAttributeValue("AXRole"), "AXRadioButton", "Correct role for tab");
+ is(
+ tab.getAttributeValue("AXSubrole"),
+ "AXTabButton",
+ "Correct subrole for tab"
+ );
+ is(tab.getAttributeValue("AXTitle"), "Four", "Correct title for tab");
+
+ let tabToSelect = tabMacAccs[2];
+ is(
+ tabToSelect.getAttributeValue("AXTitle"),
+ "Three",
+ "Correct title for tab"
+ );
+
+ let actions = tabToSelect.actionNames;
+ ok(true, actions);
+ ok(actions.includes("AXPress"), "Has switch action");
+
+ // When tab is clicked selection of tab group changes,
+ // and focus goes to the web area. Wait for both.
+ let evt = Promise.all([
+ waitForMacEvent("AXSelectedChildrenChanged"),
+ waitForMacEvent(
+ "AXFocusedUIElementChanged",
+ iface => iface.getAttributeValue("AXRole") == "AXWebArea"
+ ),
+ ]);
+ tabToSelect.performAction("AXPress");
+ await evt;
+
+ selectedTabs = tablist.getAttributeValue("AXSelectedChildren");
+ is(selectedTabs.length, 1, "one selected tab");
+ is(
+ selectedTabs[0].getAttributeValue("AXTitle"),
+ "Three",
+ "Correct title for tab"
+ );
+
+ // Close all open tabs
+ await Promise.all(newTabs.map(t => BrowserTestUtils.removeTab(t)));
+});
+
+/**
+ * Test ignored invisible items in root
+ */
+add_task(async () => {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "about:license",
+ },
+ async browser => {
+ let root = await getMacAccessible(document);
+ let rootChildCount = () => root.getAttributeValue("AXChildren").length;
+
+ // With no popups, the root accessible has 5 visible children:
+ // 1. Tab bar (#TabsToolbar)
+ // 2. Navigation bar (#nav-bar)
+ // 3. Content area (#tabbrowser-tabpanels)
+ // 4. Some fullscreen pointer grabber (#fullscreen-and-pointerlock-wrapper)
+ // 5. Accessibility announcements dialog (#a11y-announcement)
+ let baseRootChildCount = 5;
+ is(
+ rootChildCount(),
+ baseRootChildCount,
+ "Root with no popups has 5 children"
+ );
+
+ // Open a context menu
+ const menu = document.getElementById("contentAreaContextMenu");
+ if (
+ Services.prefs.getBoolPref("widget.macos.native-context-menus", false)
+ ) {
+ // Native context menu - do not expect accessibility notifications.
+ let popupshown = BrowserTestUtils.waitForPopupEvent(menu, "shown");
+ EventUtils.synthesizeMouseAtCenter(document.body, {
+ type: "contextmenu",
+ });
+ await popupshown;
+
+ is(
+ rootChildCount(),
+ baseRootChildCount,
+ "Native context menus do not show up in the root children"
+ );
+
+ // Close context menu
+ let popuphidden = BrowserTestUtils.waitForPopupEvent(menu, "hidden");
+ menu.hidePopup();
+ await popuphidden;
+ } else {
+ // Non-native menu
+ EventUtils.synthesizeMouseAtCenter(document.body, {
+ type: "contextmenu",
+ });
+ await waitForMacEvent("AXMenuOpened");
+
+ // Now root has 1 more child
+ is(rootChildCount(), baseRootChildCount + 1, "Root has 1 more child");
+
+ // Close context menu
+ let closed = waitForMacEvent("AXMenuClosed", "contentAreaContextMenu");
+ EventUtils.synthesizeKey("KEY_Escape");
+ await BrowserTestUtils.waitForPopupEvent(menu, "hidden");
+ await closed;
+ }
+
+ // We're back to base child count
+ is(rootChildCount(), baseRootChildCount, "Root has original child count");
+
+ // Open site identity popup
+ document.getElementById("identity-icon-box").click();
+ const identityPopup = document.getElementById("identity-popup");
+ await BrowserTestUtils.waitForPopupEvent(identityPopup, "shown");
+
+ // Now root has another child
+ is(rootChildCount(), baseRootChildCount + 1, "Root has another child");
+
+ // Close popup
+ EventUtils.synthesizeKey("KEY_Escape");
+ await BrowserTestUtils.waitForPopupEvent(identityPopup, "hidden");
+
+ // We're back to the base child count
+ is(rootChildCount(), baseRootChildCount, "Root has the base child count");
+ }
+ );
+});
+
+/**
+ * Tests for location bar
+ */
+add_task(async () => {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ url: "http://example.com",
+ },
+ async browser => {
+ let input = await getMacAccessible("urlbar-input");
+ is(
+ input.getAttributeValue("AXValue"),
+ "example.com",
+ "Location bar has correct value"
+ );
+ }
+ );
+});
+
+/**
+ * Test context menu
+ */
+add_task(async () => {
+ if (Services.prefs.getBoolPref("widget.macos.native-context-menus", false)) {
+ ok(true, "We cannot inspect native context menu contents; skip this test.");
+ return;
+ }
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: 'data:text/html,<a id="exampleLink" href="https://example.com">link</a>',
+ },
+ async browser => {
+ if (!Services.search.isInitialized) {
+ let aStatus = await Services.search.init();
+ Assert.ok(Components.isSuccessCode(aStatus));
+ Assert.ok(Services.search.isInitialized);
+ }
+
+ const hasContainers =
+ Services.prefs.getBoolPref("privacy.userContext.enabled") &&
+ !!ContextualIdentityService.getPublicIdentities().length;
+ info(`${hasContainers ? "Do" : "Don't"} expect containers item.`);
+ const hasInspectA11y =
+ Services.prefs.getBoolPref("devtools.everOpened", false) ||
+ Services.prefs.getIntPref("devtools.selfxss.count", 0) > 0;
+ info(`${hasInspectA11y ? "Do" : "Don't"} expect inspect a11y item.`);
+
+ // synthesize a right click on the link to open the link context menu
+ let menu = document.getElementById("contentAreaContextMenu");
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#exampleLink",
+ { type: "contextmenu" },
+ browser
+ );
+ await waitForMacEvent("AXMenuOpened");
+
+ menu = await getMacAccessible(menu);
+ let menuChildren = menu.getAttributeValue("AXChildren");
+ const expectedChildCount = 12 + +hasContainers + +hasInspectA11y;
+ is(
+ menuChildren.length,
+ expectedChildCount,
+ `Context menu on link contains ${expectedChildCount} items.`
+ );
+ // items at indicies 3, 9, and 11 are the splitters when containers exist
+ // everything else should be a menu item, otherwise indicies of splitters are
+ // 3, 8, and 10
+ const splitterIndicies = hasContainers ? [4, 9, 11] : [3, 8, 10];
+ for (let i = 0; i < menuChildren.length; i++) {
+ if (splitterIndicies.includes(i)) {
+ is(
+ menuChildren[i].getAttributeValue("AXRole"),
+ "AXSplitter",
+ "found splitter in menu"
+ );
+ } else {
+ is(
+ menuChildren[i].getAttributeValue("AXRole"),
+ "AXMenuItem",
+ "found menu item in menu"
+ );
+ }
+ }
+
+ // check the containers sub menu in depth if it exists
+ if (hasContainers) {
+ is(
+ menuChildren[1].getAttributeValue("AXVisibleChildren"),
+ null,
+ "Submenu 1 has no visible chldren when hidden"
+ );
+
+ // focus the first submenu
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ await waitForMacEvent("AXMenuOpened");
+
+ // after the submenu is opened, refetch it
+ menu = document.getElementById("contentAreaContextMenu");
+ menu = await getMacAccessible(menu);
+ menuChildren = menu.getAttributeValue("AXChildren");
+
+ // verify submenu-menuitem's attributes
+ is(
+ menuChildren[1].getAttributeValue("AXChildren").length,
+ 1,
+ "Submenu 1 has one child when open"
+ );
+ const subMenu = menuChildren[1].getAttributeValue("AXChildren")[0];
+ is(
+ subMenu.getAttributeValue("AXRole"),
+ "AXMenu",
+ "submenu has role of menu"
+ );
+ const subMenuChildren = subMenu.getAttributeValue("AXChildren");
+ is(subMenuChildren.length, 4, "sub menu has 4 children");
+ is(
+ subMenu.getAttributeValue("AXVisibleChildren").length,
+ 4,
+ "submenu has 4 visible children"
+ );
+
+ // close context menu
+ EventUtils.synthesizeKey("KEY_Escape");
+ await waitForMacEvent("AXMenuClosed");
+ }
+
+ EventUtils.synthesizeKey("KEY_Escape");
+ await waitForMacEvent("AXMenuClosed");
+ }
+ );
+});
diff --git a/accessible/tests/browser/mac/browser_aria_busy.js b/accessible/tests/browser/mac/browser_aria_busy.js
new file mode 100644
index 0000000000..e75d334e29
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_aria_busy.js
@@ -0,0 +1,44 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test aria-busy
+ */
+addAccessibleTask(
+ `<div id="section" role="group">Hello</div>`,
+ async (browser, accDoc) => {
+ let section = getNativeInterface(accDoc, "section");
+
+ ok(!section.getAttributeValue("AXElementBusy"), "section is not busy");
+
+ let busyChanged = waitForMacEvent("AXElementBusyChanged", "section");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("section")
+ .setAttribute("aria-busy", "true");
+ });
+ await busyChanged;
+
+ ok(section.getAttributeValue("AXElementBusy"), "section is busy");
+
+ busyChanged = waitForMacEvent("AXElementBusyChanged", "section");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("section")
+ .setAttribute("aria-busy", "false");
+ });
+ await busyChanged;
+
+ ok(!section.getAttributeValue("AXElementBusy"), "section is not busy");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_aria_controls_flowto.js b/accessible/tests/browser/mac/browser_aria_controls_flowto.js
new file mode 100644
index 0000000000..5950a60399
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_aria_controls_flowto.js
@@ -0,0 +1,92 @@
+/* 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";
+
+/**
+ * Test aria-controls
+ */
+addAccessibleTask(
+ `<button aria-controls="info" id="info-button">Show info</button>
+ <div id="info">Information.</div>
+ <div id="more-info">More information.</div>`,
+ async (browser, accDoc) => {
+ const getAriaControls = id =>
+ JSON.stringify(
+ getNativeInterface(accDoc, id)
+ .getAttributeValue("AXARIAControls")
+ .map(e => e.getAttributeValue("AXDOMIdentifier"))
+ );
+
+ await untilCacheIs(
+ () => getAriaControls("info-button"),
+ JSON.stringify(["info"]),
+ "Info-button has correct initial controls"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("info-button")
+ .setAttribute("aria-controls", "info more-info");
+ });
+
+ await untilCacheIs(
+ () => getAriaControls("info-button"),
+ JSON.stringify(["info", "more-info"]),
+ "Info-button has correct controls after mutation"
+ );
+ }
+);
+
+function getLinkedUIElements(accDoc, id) {
+ return JSON.stringify(
+ getNativeInterface(accDoc, id)
+ .getAttributeValue("AXLinkedUIElements")
+ .map(e => e.getAttributeValue("AXDOMIdentifier"))
+ );
+}
+
+/**
+ * Test aria-flowto
+ */
+addAccessibleTask(
+ `<button aria-flowto="info" id="info-button">Show info</button>
+ <div id="info">Information.</div>
+ <div id="more-info">More information.</div>`,
+ async (browser, accDoc) => {
+ await untilCacheIs(
+ () => getLinkedUIElements(accDoc, "info-button"),
+ JSON.stringify(["info"]),
+ "Info-button has correct initial linked elements"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("info-button")
+ .setAttribute("aria-flowto", "info more-info");
+ });
+
+ await untilCacheIs(
+ () => getLinkedUIElements(accDoc, "info-button"),
+ JSON.stringify(["info", "more-info"]),
+ "Info-button has correct linked elements after mutation"
+ );
+ }
+);
+
+/**
+ * Test aria-controls
+ */
+addAccessibleTask(
+ `<input type="radio" id="cat-radio" name="animal"><label for="cat">Cat</label>
+ <input type="radio" id="dog-radio" name="animal" aria-flowto="info"><label for="dog">Dog</label>
+ <div id="info">Information.</div>`,
+ async (browser, accDoc) => {
+ await untilCacheIs(
+ () => getLinkedUIElements(accDoc, "dog-radio"),
+ JSON.stringify(["cat-radio", "dog-radio", "info"]),
+ "dog-radio has correct linked elements"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_aria_current.js b/accessible/tests/browser/mac/browser_aria_current.js
new file mode 100644
index 0000000000..02c7a71b67
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_aria_current.js
@@ -0,0 +1,58 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test aria-current
+ */
+addAccessibleTask(
+ `<a id="one" href="%23" aria-current="page">One</a><a id="two" href="%23">Two</a>`,
+ async (browser, accDoc) => {
+ let one = getNativeInterface(accDoc, "one");
+ let two = getNativeInterface(accDoc, "two");
+
+ is(
+ one.getAttributeValue("AXARIACurrent"),
+ "page",
+ "Correct aria-current for #one"
+ );
+ is(
+ two.getAttributeValue("AXARIACurrent"),
+ null,
+ "Correct aria-current for #two"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("one")
+ .setAttribute("aria-current", "step");
+ });
+
+ is(
+ one.getAttributeValue("AXARIACurrent"),
+ "step",
+ "Correct aria-current for #one"
+ );
+
+ let stateChanged = waitForEvent(EVENT_STATE_CHANGE, "one");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("one").removeAttribute("aria-current");
+ });
+ await stateChanged;
+
+ is(
+ one.getAttributeValue("AXARIACurrent"),
+ null,
+ "Correct aria-current for #one"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_aria_expanded.js b/accessible/tests/browser/mac/browser_aria_expanded.js
new file mode 100644
index 0000000000..48fb615266
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_aria_expanded.js
@@ -0,0 +1,45 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/states.js */
+loadScripts({ name: "states.js", dir: MOCHITESTS_DIR });
+
+// Test aria-expanded on a button
+addAccessibleTask(
+ `hello world<br>
+ <button aria-expanded="false" id="b">I am a button</button><br>
+ goodbye`,
+ async (browser, accDoc) => {
+ let button = getNativeInterface(accDoc, "b");
+ is(button.getAttributeValue("AXExpanded"), 0, "button is not expanded");
+
+ let stateChanged = Promise.all([
+ waitForStateChange("b", STATE_EXPANDED, true),
+ waitForStateChange("b", STATE_COLLAPSED, false),
+ ]);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("b")
+ .setAttribute("aria-expanded", "true");
+ });
+ await stateChanged;
+ is(button.getAttributeValue("AXExpanded"), 1, "button is expanded");
+
+ stateChanged = Promise.all([
+ waitForStateChange("b", STATE_EXPANDED, false),
+ waitForStateChange("b", EXT_STATE_EXPANDABLE, false, true),
+ ]);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("b").removeAttribute("aria-expanded");
+ });
+ await stateChanged;
+
+ ok(
+ !button.attributeNames.includes("AXExpanded"),
+ "button has no expanded attr"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_aria_haspopup.js b/accessible/tests/browser/mac/browser_aria_haspopup.js
new file mode 100644
index 0000000000..57f1e50f65
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_aria_haspopup.js
@@ -0,0 +1,320 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test aria-haspopup
+ */
+addAccessibleTask(
+ `
+ <button aria-haspopup="false" id="false">action</button>
+
+ <button aria-haspopup="menu" id="menu">action</button>
+
+ <button aria-haspopup="listbox" id="listbox">action</button>
+
+ <button aria-haspopup="tree" id="tree">action</button>
+
+ <button aria-haspopup="grid" id="grid">action</button>
+
+ <button aria-haspopup="dialog" id="dialog">action</button>
+
+ `,
+ async (browser, accDoc) => {
+ // FALSE
+ let falseID = getNativeInterface(accDoc, "false");
+ is(
+ falseID.getAttributeValue("AXHasPopup"),
+ 0,
+ "Correct AXHasPopup val for button with false"
+ );
+ is(
+ falseID.getAttributeValue("AXPopupValue"),
+ null,
+ "Correct AXPopupValue val for button with false"
+ );
+ let attrChanged = waitForEvent(EVENT_STATE_CHANGE, "false");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("false")
+ .setAttribute("aria-haspopup", "true");
+ });
+ await attrChanged;
+
+ is(
+ falseID.getAttributeValue("AXPopupValue"),
+ "true",
+ "Correct AXPopupValue after change for false"
+ );
+ is(
+ falseID.getAttributeValue("AXHasPopup"),
+ 1,
+ "Correct AXHasPopup val for button with true"
+ );
+
+ let stateChanged = waitForEvent(EVENT_STATE_CHANGE, "false");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("false").removeAttribute("aria-haspopup");
+ });
+ await stateChanged;
+
+ is(
+ falseID.getAttributeValue("AXPopupValue"),
+ null,
+ "Correct AXPopupValue after remove for false"
+ );
+ is(
+ falseID.getAttributeValue("AXHasPopup"),
+ 0,
+ "Correct AXHasPopup val for button after remove"
+ );
+
+ // MENU
+ let menuID = getNativeInterface(accDoc, "menu");
+ is(
+ menuID.getAttributeValue("AXPopupValue"),
+ "menu",
+ "Correct AXPopupValue val for button with menu"
+ );
+ is(
+ menuID.getAttributeValue("AXHasPopup"),
+ 1,
+ "Correct AXHasPopup val for button with menu"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("menu")
+ .setAttribute("aria-haspopup", "true");
+ });
+
+ await untilCacheIs(
+ () => menuID.getAttributeValue("AXPopupValue"),
+ "true",
+ "Correct AXPopupValue after change for menu"
+ );
+ is(
+ menuID.getAttributeValue("AXHasPopup"),
+ 1,
+ "Correct AXHasPopup val for button with menu"
+ );
+
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "menu");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("menu").removeAttribute("aria-haspopup");
+ });
+ await stateChanged;
+
+ await untilCacheIs(
+ () => menuID.getAttributeValue("AXPopupValue"),
+ null,
+ "Correct AXPopupValue after remove for menu"
+ );
+ is(
+ menuID.getAttributeValue("AXHasPopup"),
+ 0,
+ "Correct AXHasPopup val for button after remove"
+ );
+
+ // LISTBOX
+ let listboxID = getNativeInterface(accDoc, "listbox");
+ is(
+ listboxID.getAttributeValue("AXPopupValue"),
+ "listbox",
+ "Correct AXPopupValue for button with listbox"
+ );
+ is(
+ listboxID.getAttributeValue("AXHasPopup"),
+ 1,
+ "Correct AXHasPopup for button with listbox"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("listbox")
+ .setAttribute("aria-haspopup", "true");
+ });
+
+ await untilCacheIs(
+ () => listboxID.getAttributeValue("AXPopupValue"),
+ "true",
+ "Correct AXPopupValue after change for listbox"
+ );
+ is(
+ listboxID.getAttributeValue("AXHasPopup"),
+ 1,
+ "Correct AXHasPopup for button with listbox"
+ );
+
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "listbox");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("listbox")
+ .removeAttribute("aria-haspopup");
+ });
+ await stateChanged;
+
+ is(
+ listboxID.getAttributeValue("AXPopupValue"),
+ null,
+ "Correct AXPopupValue after remove for listbox"
+ );
+ is(
+ listboxID.getAttributeValue("AXHasPopup"),
+ 0,
+ "Correct AXHasPopup for button with listbox"
+ );
+
+ // TREE
+ let treeID = getNativeInterface(accDoc, "tree");
+ is(
+ treeID.getAttributeValue("AXPopupValue"),
+ "tree",
+ "Correct AXPopupValue for button with tree"
+ );
+ is(
+ treeID.getAttributeValue("AXHasPopup"),
+ 1,
+ "Correct AXHasPopup for button with tree"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("tree")
+ .setAttribute("aria-haspopup", "true");
+ });
+
+ await untilCacheIs(
+ () => treeID.getAttributeValue("AXPopupValue"),
+ "true",
+ "Correct AXPopupValue after change for tree"
+ );
+ is(
+ treeID.getAttributeValue("AXHasPopup"),
+ 1,
+ "Correct AXHasPopup for button with tree"
+ );
+
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "tree");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("tree").removeAttribute("aria-haspopup");
+ });
+ await stateChanged;
+
+ is(
+ treeID.getAttributeValue("AXPopupValue"),
+ null,
+ "Correct AXPopupValue after remove for tree"
+ );
+ is(
+ treeID.getAttributeValue("AXHasPopup"),
+ 0,
+ "Correct AXHasPopup for button with tree after remove"
+ );
+
+ // GRID
+ let gridID = getNativeInterface(accDoc, "grid");
+ is(
+ gridID.getAttributeValue("AXPopupValue"),
+ "grid",
+ "Correct AXPopupValue for button with grid"
+ );
+ is(
+ gridID.getAttributeValue("AXHasPopup"),
+ 1,
+ "Correct AXHasPopup for button with grid"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("grid")
+ .setAttribute("aria-haspopup", "true");
+ });
+
+ await untilCacheIs(
+ () => gridID.getAttributeValue("AXPopupValue"),
+ "true",
+ "Correct AXPopupValue after change for grid"
+ );
+ is(
+ gridID.getAttributeValue("AXHasPopup"),
+ 1,
+ "Correct AXHasPopup for button with grid"
+ );
+
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "grid");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("grid").removeAttribute("aria-haspopup");
+ });
+ await stateChanged;
+
+ is(
+ gridID.getAttributeValue("AXPopupValue"),
+ null,
+ "Correct AXPopupValue after remove for grid"
+ );
+ is(
+ gridID.getAttributeValue("AXHasPopup"),
+ 0,
+ "Correct AXHasPopup for button with grid after remove"
+ );
+
+ // DIALOG
+ let dialogID = getNativeInterface(accDoc, "dialog");
+ is(
+ dialogID.getAttributeValue("AXPopupValue"),
+ "dialog",
+ "Correct AXPopupValue for button with dialog"
+ );
+ is(
+ dialogID.getAttributeValue("AXHasPopup"),
+ 1,
+ "Correct AXHasPopup for button with dialog"
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("dialog")
+ .setAttribute("aria-haspopup", "true");
+ });
+
+ await untilCacheIs(
+ () => dialogID.getAttributeValue("AXPopupValue"),
+ "true",
+ "Correct AXPopupValue after change for dialog"
+ );
+ is(
+ dialogID.getAttributeValue("AXHasPopup"),
+ 1,
+ "Correct AXHasPopup for button with dialog"
+ );
+
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "dialog");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("dialog")
+ .removeAttribute("aria-haspopup");
+ });
+ await stateChanged;
+
+ is(
+ dialogID.getAttributeValue("AXPopupValue"),
+ null,
+ "Correct AXPopupValue after remove for dialog"
+ );
+ is(
+ dialogID.getAttributeValue("AXHasPopup"),
+ 0,
+ "Correct AXHasPopup for button with dialog after remove"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_attributed_text.js b/accessible/tests/browser/mac/browser_attributed_text.js
new file mode 100644
index 0000000000..6f6200751c
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_attributed_text.js
@@ -0,0 +1,144 @@
+/* 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";
+
+// Test read-only attributed strings
+addAccessibleTask(
+ `<h1>hello <a href="#" id="a1">world</a></h1>
+ <p>this <b style="color: red; background-color: yellow;" aria-invalid="spelling">is</b> <span style="text-decoration: underline dotted green;">a</span> <a href="#" id="a2">test</a></p>`,
+ async (browser, accDoc) => {
+ let macDoc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ let range = macDoc.getParameterizedAttributeValue(
+ "AXTextMarkerRangeForUnorderedTextMarkers",
+ [
+ macDoc.getAttributeValue("AXStartTextMarker"),
+ macDoc.getAttributeValue("AXEndTextMarker"),
+ ]
+ );
+
+ let attributedText = macDoc.getParameterizedAttributeValue(
+ "AXAttributedStringForTextMarkerRange",
+ range
+ );
+
+ let attributesList = attributedText.map(
+ ({
+ string,
+ AXForegroundColor,
+ AXBackgroundColor,
+ AXUnderline,
+ AXUnderlineColor,
+ AXHeadingLevel,
+ AXFont,
+ AXLink,
+ AXMarkedMisspelled,
+ }) => [
+ string,
+ AXForegroundColor,
+ AXBackgroundColor,
+ AXUnderline,
+ AXUnderlineColor,
+ AXHeadingLevel,
+ AXFont.AXFontSize,
+ AXLink ? AXLink.getAttributeValue("AXDOMIdentifier") : null,
+ AXMarkedMisspelled,
+ ]
+ );
+
+ Assert.deepEqual(attributesList, [
+ // string, fg color, bg color, underline, underline color, heading level, font size, link id, misspelled
+ ["hello ", "#000000", "#ffffff", null, null, 1, 32, null, null],
+ ["world", "#0000ee", "#ffffff", 1, "#0000ee", 1, 32, "a1", null],
+ ["this ", "#000000", "#ffffff", null, null, null, 16, null, null],
+ ["is", "#ff0000", "#ffff00", null, null, null, 16, null, 1],
+ [" ", "#000000", "#ffffff", null, null, null, 16, null, null],
+ ["a", "#000000", "#ffffff", 1, "#008000", null, 16, null, null],
+ [" ", "#000000", "#ffffff", null, null, null, 16, null, null],
+ ["test", "#0000ee", "#ffffff", 1, "#0000ee", null, 16, "a2", null],
+ ]);
+
+ // Test different NSRange parameters for AXAttributedStringForRange
+ let worldLeaf = findAccessibleChildByID(accDoc, "a1").firstChild;
+ let wordStaticText = worldLeaf.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ attributedText = wordStaticText.getParameterizedAttributeValue(
+ "AXAttributedStringForRange",
+ NSRange(4, 1)
+ );
+ is(attributedText.length, 1, "Last character is in single attribute run");
+ is(attributedText[0].string, "d", "Last character matches");
+
+ attributedText = wordStaticText.getParameterizedAttributeValue(
+ "AXAttributedStringForRange",
+ NSRange(5, 1)
+ );
+ is(attributedText.length, 0, "Range is past accessible bounds");
+ }
+);
+
+// Test misspelling in text area
+addAccessibleTask(
+ `<textarea id="t">hello worlf, i love you</textarea>`,
+ async (browser, accDoc) => {
+ let textArea = getNativeInterface(accDoc, "t");
+ let spellDone = waitForEvent(EVENT_TEXT_ATTRIBUTE_CHANGED, "t");
+ textArea.setAttributeValue("AXFocused", true);
+
+ let attributedText = [];
+
+ // For some internal reason we get several text attribute change events
+ // before the attributed text returned provides the misspelling attributes.
+ while (true) {
+ await spellDone;
+
+ let range = textArea.getAttributeValue("AXVisibleCharacterRange");
+ attributedText = textArea.getParameterizedAttributeValue(
+ "AXAttributedStringForRange",
+ NSRange(...range)
+ );
+
+ if (attributedText.length != 3) {
+ spellDone = waitForEvent(EVENT_TEXT_ATTRIBUTE_CHANGED, "t");
+ } else {
+ break;
+ }
+ }
+
+ ok(attributedText[1].AXMarkedMisspelled);
+ }
+);
+
+// Test getting a span of attributed text that includes an empty input element.
+addAccessibleTask(`hello <input id="input"> world`, async (browser, accDoc) => {
+ let macDoc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ let range = macDoc.getParameterizedAttributeValue(
+ "AXTextMarkerRangeForUnorderedTextMarkers",
+ [
+ macDoc.getAttributeValue("AXStartTextMarker"),
+ macDoc.getAttributeValue("AXEndTextMarker"),
+ ]
+ );
+
+ let attributedText = macDoc.getParameterizedAttributeValue(
+ "AXAttributedStringForTextMarkerRange",
+ range
+ );
+
+ let text = macDoc.getParameterizedAttributeValue(
+ "AXStringForTextMarkerRange",
+ range
+ );
+
+ is(attributedText.length, 1, "Empty input does not break up attribute run.");
+ is(attributedText[0].string, `hello world `, "Attributed string is correct");
+ is(text, `hello world `, "Unattributed string is correct");
+});
diff --git a/accessible/tests/browser/mac/browser_bounds.js b/accessible/tests/browser/mac/browser_bounds.js
new file mode 100644
index 0000000000..09343d7c9d
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_bounds.js
@@ -0,0 +1,77 @@
+/* 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";
+
+/**
+ * Test position, size for onscreen content
+ */
+addAccessibleTask(
+ `I am some extra content<br>
+ <div id="hello" style="display:inline;">hello</div><br>
+ <div id="world" style="display:inline;">hello world<br>I am some text</div>`,
+ async (browser, accDoc) => {
+ const hello = getNativeInterface(accDoc, "hello");
+ const world = getNativeInterface(accDoc, "world");
+ ok(hello.getAttributeValue("AXFrame"), "Hello's frame attr is not null");
+ ok(world.getAttributeValue("AXFrame"), "World's frame attr is not null");
+
+ // AXSize and AXPosition are composed of AXFrame components, so we
+ // test them here instead of calling AXFrame directly.
+ const [helloWidth, helloHeight] = hello.getAttributeValue("AXSize");
+ const [worldWidth, worldHeight] = world.getAttributeValue("AXSize");
+ ok(helloWidth > 0, "Hello has a positive width");
+ ok(helloHeight > 0, "Hello has a positive height");
+ ok(worldWidth > 0, "World has a positive width");
+ ok(worldHeight > 0, "World has a positive height");
+ ok(helloHeight < worldHeight, "Hello has a smaller height than world");
+ ok(helloWidth < worldWidth, "Hello has a smaller width than world");
+
+ // Note: these are mac screen coords, so our origin is bottom left
+ const [helloX, helloY] = hello.getAttributeValue("AXPosition");
+ const [worldX, worldY] = world.getAttributeValue("AXPosition");
+ ok(helloX > 0, "Hello has a positive X");
+ ok(helloY > 0, "Hello has a positive Y");
+ ok(worldX > 0, "World has a positive X");
+ ok(worldY > 0, "World has a positive Y");
+ ok(helloY > worldY, "Hello has a larger Y than world");
+ ok(helloX == worldX, "Hello and world have the same X");
+ }
+);
+
+/**
+ * Test position, size for offscreen content
+ */
+addAccessibleTask(
+ `I am some extra content<br>
+ <div id="hello" style="display:inline; position:absolute; left:-2000px;">hello</div><br>
+ <div id="world" style="display:inline; position:absolute; left:-2000px;">hello world<br>I am some text</div>`,
+ async (browser, accDoc) => {
+ const hello = getNativeInterface(accDoc, "hello");
+ const world = getNativeInterface(accDoc, "world");
+ ok(hello.getAttributeValue("AXFrame"), "Hello's frame attr is not null");
+ ok(world.getAttributeValue("AXFrame"), "World's frame attr is not null");
+
+ // AXSize and AXPosition are composed of AXFrame components, so we
+ // test them here instead of calling AXFrame directly.
+ const [helloWidth, helloHeight] = hello.getAttributeValue("AXSize");
+ const [worldWidth, worldHeight] = world.getAttributeValue("AXSize");
+ ok(helloWidth > 0, "Hello has a positive width");
+ ok(helloHeight > 0, "Hello has a positive height");
+ ok(worldWidth > 0, "World has a positive width");
+ ok(worldHeight > 0, "World has a positive height");
+ ok(helloHeight < worldHeight, "Hello has a smaller height than world");
+ ok(helloWidth < worldWidth, "Hello has a smaller width than world");
+
+ // Note: these are mac screen coords, so our origin is bottom left
+ const [helloX, helloY] = hello.getAttributeValue("AXPosition");
+ const [worldX, worldY] = world.getAttributeValue("AXPosition");
+ ok(helloX < 0, "Hello has a negative X");
+ ok(helloY > 0, "Hello has a positive Y");
+ ok(worldX < 0, "World has a negative X");
+ ok(worldY > 0, "World has a positive Y");
+ ok(helloY > worldY, "Hello has a larger Y than world");
+ ok(helloX == worldX, "Hello and world have the same X");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_details_summary.js b/accessible/tests/browser/mac/browser_details_summary.js
new file mode 100644
index 0000000000..6157707f79
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_details_summary.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/. */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test details/summary
+ */
+addAccessibleTask(
+ `<details id="details"><summary id="summary">Foo</summary><p>Bar</p></details>`,
+ async (browser, accDoc) => {
+ let details = getNativeInterface(accDoc, "details");
+ is(
+ details.getAttributeValue("AXRole"),
+ "AXGroup",
+ "Correct role for details"
+ );
+ is(
+ details.getAttributeValue("AXSubrole"),
+ "AXDetails",
+ "Correct subrole for details"
+ );
+
+ let detailsChildren = details.getAttributeValue("AXChildren");
+ is(detailsChildren.length, 1, "collapsed details has only one child");
+
+ let summary = detailsChildren[0];
+ is(
+ summary.getAttributeValue("AXRole"),
+ "AXButton",
+ "Correct role for summary"
+ );
+ is(
+ summary.getAttributeValue("AXSubrole"),
+ "AXSummary",
+ "Correct subrole for summary"
+ );
+ is(summary.getAttributeValue("AXExpanded"), 0, "Summary is collapsed");
+
+ let actions = summary.actionNames;
+ ok(actions.includes("AXPress"), "Summary Has press action");
+
+ let stateChanged = waitForStateChange("summary", STATE_EXPANDED, true);
+ summary.performAction("AXPress");
+ // The reorder gecko event notifies us of a tree change.
+ await stateChanged;
+ is(summary.getAttributeValue("AXExpanded"), 1, "Summary is expanded");
+
+ detailsChildren = details.getAttributeValue("AXChildren");
+ is(detailsChildren.length, 2, "collapsed details has only one child");
+
+ stateChanged = waitForStateChange("summary", STATE_EXPANDED, false);
+ summary.performAction("AXPress");
+ // The reorder gecko event notifies us of a tree change.
+ await stateChanged;
+ is(summary.getAttributeValue("AXExpanded"), 0, "Summary is collapsed 2");
+
+ detailsChildren = details.getAttributeValue("AXChildren");
+ is(detailsChildren.length, 1, "collapsed details has only one child");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_focus.js b/accessible/tests/browser/mac/browser_focus.js
new file mode 100644
index 0000000000..6bceb06c6c
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_focus.js
@@ -0,0 +1,44 @@
+/* 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";
+
+/**
+ * Test focusability
+ */
+addAccessibleTask(
+ `
+ <div role="button" id="ariabutton">hello</div> <button id="button">world</button>
+ `,
+ async (browser, accDoc) => {
+ let ariabutton = getNativeInterface(accDoc, "ariabutton");
+ let button = getNativeInterface(accDoc, "button");
+
+ is(
+ ariabutton.getAttributeValue("AXFocused"),
+ 0,
+ "aria button is not focused"
+ );
+
+ is(button.getAttributeValue("AXFocused"), 0, "button is not focused");
+
+ ok(
+ !ariabutton.isAttributeSettable("AXFocused"),
+ "aria button should not be focusable"
+ );
+
+ ok(button.isAttributeSettable("AXFocused"), "button is focusable");
+
+ let evt = waitForMacEvent(
+ "AXFocusedUIElementChanged",
+ iface => iface.getAttributeValue("AXDOMIdentifier") == "button"
+ );
+
+ button.setAttributeValue("AXFocused", true);
+
+ await evt;
+
+ is(button.getAttributeValue("AXFocused"), 1, "button is focused");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_heading.js b/accessible/tests/browser/mac/browser_heading.js
new file mode 100644
index 0000000000..fd8c12883d
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_heading.js
@@ -0,0 +1,77 @@
+/* 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";
+
+/**
+ * Test whether line break code in text content will be removed
+ * and extra whitespaces will be trimmed.
+ */
+addAccessibleTask(
+ `
+ <h1 id="single-line-content">We’re building a richer search experience</h1>
+ <h1 id="multi-lines-content">
+We’re building a
+richest
+search experience
+ </h1>
+ `,
+ async (browser, accDoc) => {
+ const singleLineContentHeading = getNativeInterface(
+ accDoc,
+ "single-line-content"
+ );
+ is(
+ singleLineContentHeading.getAttributeValue("AXTitle"),
+ "We’re building a richer search experience"
+ );
+
+ const multiLinesContentHeading = getNativeInterface(
+ accDoc,
+ "multi-lines-content"
+ );
+ is(
+ multiLinesContentHeading.getAttributeValue("AXTitle"),
+ "We’re building a richest search experience"
+ );
+ }
+);
+
+/**
+ * Test AXTitle/AXDescription attributes of heading elements
+ */
+addAccessibleTask(
+ `
+ <h1 id="a">Hello <a href="#">world</a></h1>
+ <h1 id="b">Hello</h1>
+ <h1 id="c" aria-label="Goodbye">Hello</h1>
+ `,
+ async (browser, accDoc) => {
+ const a = getNativeInterface(accDoc, "a");
+ is(
+ a.getAttributeValue("AXTitle"),
+ "Hello world",
+ "Correct AXTitle for 'a'"
+ );
+ ok(
+ !a.getAttributeValue("AXDescription"),
+ "'a' Should not have AXDescription"
+ );
+
+ const b = getNativeInterface(accDoc, "b");
+ is(b.getAttributeValue("AXTitle"), "Hello", "Correct AXTitle for 'b'");
+ ok(
+ !b.getAttributeValue("AXDescription"),
+ "'b' Should not have AXDescription"
+ );
+
+ const c = getNativeInterface(accDoc, "c");
+ is(
+ c.getAttributeValue("AXDescription"),
+ "Goodbye",
+ "Correct AXDescription for 'c'"
+ );
+ ok(!c.getAttributeValue("AXTitle"), "'c' Should not have AXTitle");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_hierarchy.js b/accessible/tests/browser/mac/browser_hierarchy.js
new file mode 100644
index 0000000000..8a97e55c07
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_hierarchy.js
@@ -0,0 +1,75 @@
+/* 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";
+
+/**
+ * Test AXIndexForChildUIElement
+ */
+addAccessibleTask(
+ `<p id="p">Hello <a href="#" id="link">strange</a> world`,
+ (browser, accDoc) => {
+ let p = getNativeInterface(accDoc, "p");
+
+ let children = p.getAttributeValue("AXChildren");
+ is(children.length, 3, "p has 3 children");
+ is(
+ children[1].getAttributeValue("AXDOMIdentifier"),
+ "link",
+ "second child is link"
+ );
+
+ let index = p.getParameterizedAttributeValue(
+ "AXIndexForChildUIElement",
+ children[1]
+ );
+ is(index, 1, "link is second child");
+ }
+);
+
+/**
+ * Test textbox with more than one child
+ */
+addAccessibleTask(
+ `<div id="textbox" role="textbox">Hello <a href="#">strange</a> world</div>`,
+ (browser, accDoc) => {
+ let textbox = getNativeInterface(accDoc, "textbox");
+
+ is(
+ textbox.getAttributeValue("AXChildren").length,
+ 3,
+ "textbox has 3 children"
+ );
+ }
+);
+
+/**
+ * Test textbox with one child
+ */
+addAccessibleTask(
+ `<div id="textbox" role="textbox">Hello </div>`,
+ async (browser, accDoc) => {
+ let textbox = getNativeInterface(accDoc, "textbox");
+
+ is(
+ textbox.getAttributeValue("AXChildren").length,
+ 0,
+ "textbox with one child is pruned"
+ );
+
+ let reorder = waitForEvent(EVENT_REORDER, "textbox");
+ await SpecialPowers.spawn(browser, [], () => {
+ let link = content.document.createElement("a");
+ link.textContent = "World";
+ content.document.getElementById("textbox").appendChild(link);
+ });
+ await reorder;
+
+ is(
+ textbox.getAttributeValue("AXChildren").length,
+ 2,
+ "textbox with two child is not pruned"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_input.js b/accessible/tests/browser/mac/browser_input.js
new file mode 100644
index 0000000000..7fa20a9d4b
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_input.js
@@ -0,0 +1,225 @@
+/* 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 selectedTextEventPromises(stateChangeType) {
+ return [
+ waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => {
+ return (
+ info.AXTextStateChangeType == stateChangeType &&
+ elem.getAttributeValue("AXDOMIdentifier") == "body"
+ );
+ }),
+ waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => {
+ return (
+ info.AXTextStateChangeType == stateChangeType &&
+ elem.getAttributeValue("AXDOMIdentifier") == "input"
+ );
+ }),
+ ];
+}
+
+async function testInput(browser, accDoc) {
+ let input = getNativeInterface(accDoc, "input");
+
+ is(input.getAttributeValue("AXDescription"), "Name", "Correct input label");
+ is(input.getAttributeValue("AXTitle"), "", "Correct input title");
+ is(input.getAttributeValue("AXValue"), "Elmer Fudd", "Correct input value");
+ is(
+ input.getAttributeValue("AXNumberOfCharacters"),
+ 10,
+ "Correct length of value"
+ );
+
+ ok(input.attributeNames.includes("AXSelectedText"), "Has AXSelectedText");
+ ok(
+ input.attributeNames.includes("AXSelectedTextRange"),
+ "Has AXSelectedTextRange"
+ );
+
+ let evt = Promise.all([
+ waitForMacEvent("AXFocusedUIElementChanged", "input"),
+ ...selectedTextEventPromises(AXTextStateChangeTypeSelectionMove),
+ ]);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("input").focus();
+ });
+ await evt;
+
+ evt = Promise.all(
+ selectedTextEventPromises(AXTextStateChangeTypeSelectionExtend)
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ let elm = content.document.getElementById("input");
+ if (elm.setSelectionRange) {
+ elm.setSelectionRange(6, 9);
+ } else {
+ let r = new content.Range();
+ let textNode = elm.firstElementChild.firstChild;
+ r.setStart(textNode, 6);
+ r.setEnd(textNode, 9);
+
+ let s = content.getSelection();
+ s.removeAllRanges();
+ s.addRange(r);
+ }
+ });
+ await evt;
+
+ is(
+ input.getAttributeValue("AXSelectedText"),
+ "Fud",
+ "Correct text is selected"
+ );
+
+ Assert.deepEqual(
+ input.getAttributeValue("AXSelectedTextRange"),
+ [6, 3],
+ "correct range selected"
+ );
+
+ ok(
+ input.isAttributeSettable("AXSelectedTextRange"),
+ "AXSelectedTextRange is settable"
+ );
+
+ evt = Promise.all(
+ selectedTextEventPromises(AXTextStateChangeTypeSelectionExtend)
+ );
+ input.setAttributeValue("AXSelectedTextRange", NSRange(1, 7));
+ await evt;
+
+ Assert.deepEqual(
+ input.getAttributeValue("AXSelectedTextRange"),
+ [1, 7],
+ "correct range selected"
+ );
+
+ is(
+ input.getAttributeValue("AXSelectedText"),
+ "lmer Fu",
+ "Correct text is selected"
+ );
+
+ let domSelection = await SpecialPowers.spawn(browser, [], () => {
+ let elm = content.document.querySelector("input#input");
+ if (elm) {
+ return elm.value.substring(elm.selectionStart, elm.selectionEnd);
+ }
+
+ return content.getSelection().toString();
+ });
+
+ is(domSelection, "lmer Fu", "correct DOM selection");
+
+ is(
+ input.getParameterizedAttributeValue("AXStringForRange", NSRange(3, 5)),
+ "er Fu",
+ "AXStringForRange works"
+ );
+}
+
+/**
+ * Input selection test
+ */
+addAccessibleTask(
+ `<input aria-label="Name" id="input" value="Elmer Fudd">`,
+ testInput
+);
+
+/**
+ * contenteditable selection test
+ */
+addAccessibleTask(
+ `<div aria-label="Name" tabindex="0" role="textbox" aria-multiline="true" id="input" contenteditable>
+ <p>Elmer Fudd</p>
+ </div>`,
+ testInput
+);
+
+/**
+ * test contenteditable with selection that extends past editable part
+ */
+addAccessibleTask(
+ `<span aria-label="Name"
+ tabindex="0"
+ role="textbox"
+ id="input"
+ contenteditable>Elmer Fudd</span> <span id="notinput">is the name</span>`,
+ async (browser, accDoc) => {
+ let evt = Promise.all([
+ waitForMacEvent("AXFocusedUIElementChanged", "input"),
+ waitForMacEvent("AXSelectedTextChanged", "body"),
+ waitForMacEvent("AXSelectedTextChanged", "input"),
+ ]);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("input").focus();
+ });
+ await evt;
+
+ evt = waitForEvent(EVENT_TEXT_CARET_MOVED);
+ await SpecialPowers.spawn(browser, [], () => {
+ let input = content.document.getElementById("input");
+ let notinput = content.document.getElementById("notinput");
+
+ let r = new content.Range();
+ r.setStart(input.firstChild, 4);
+ r.setEnd(notinput.firstChild, 6);
+
+ let s = content.getSelection();
+ s.removeAllRanges();
+ s.addRange(r);
+ });
+ await evt;
+
+ let input = getNativeInterface(accDoc, "input");
+
+ is(
+ input.getAttributeValue("AXSelectedText"),
+ "r Fudd",
+ "Correct text is selected in #input"
+ );
+
+ is(
+ stringForRange(
+ input,
+ input.getAttributeValue("AXSelectedTextMarkerRange")
+ ),
+ "r Fudd is the",
+ "Correct text is selected in document"
+ );
+ }
+);
+
+/**
+ * test nested content editables and their ancestor getters.
+ */
+addAccessibleTask(
+ `<div id="outer" role="textbox" contenteditable="true">
+ <p id="p">Bob <a href="#" id="link">Loblaw's</a></p>
+ <div id="inner" role="textbox" contenteditable="true">
+ Law <a href="#" id="inner_link">Blog</a>
+ </div>
+ </div>`,
+ (browser, accDoc) => {
+ let link = getNativeInterface(accDoc, "link");
+ let innerLink = getNativeInterface(accDoc, "inner_link");
+
+ let idmatches = (elem, id) => {
+ is(elem.getAttributeValue("AXDOMIdentifier"), id, "Matches ID");
+ };
+
+ idmatches(link.getAttributeValue("AXEditableAncestor"), "outer");
+ idmatches(link.getAttributeValue("AXFocusableAncestor"), "outer");
+ idmatches(link.getAttributeValue("AXHighestEditableAncestor"), "outer");
+
+ idmatches(innerLink.getAttributeValue("AXEditableAncestor"), "inner");
+ idmatches(innerLink.getAttributeValue("AXFocusableAncestor"), "inner");
+ idmatches(
+ innerLink.getAttributeValue("AXHighestEditableAncestor"),
+ "outer"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_label_title.js b/accessible/tests/browser/mac/browser_label_title.js
new file mode 100644
index 0000000000..2532247e0f
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_label_title.js
@@ -0,0 +1,111 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test different labeling/titling schemes for text fields
+ */
+addAccessibleTask(
+ `<label for="n1">Label</label> <input id="n1">
+ <label for="n2">Two</label> <label for="n2">Labels</label> <input id="n2">
+ <input aria-label="ARIA Label" id="n3">`,
+ (browser, accDoc) => {
+ let n1 = getNativeInterface(accDoc, "n1");
+ let n1Label = n1.getAttributeValue("AXTitleUIElement");
+ // XXX: In Safari the label is an AXText with an AXValue,
+ // here it is an AXGroup witth an AXTitle
+ is(n1Label.getAttributeValue("AXTitle"), "Label");
+
+ let n2 = getNativeInterface(accDoc, "n2");
+ is(n2.getAttributeValue("AXDescription"), "TwoLabels");
+
+ let n3 = getNativeInterface(accDoc, "n3");
+ is(n3.getAttributeValue("AXDescription"), "ARIA Label");
+ }
+);
+
+/**
+ * Test to see that named groups get labels
+ */
+addAccessibleTask(
+ `<fieldset id="fieldset"><legend>Fields</legend><input aria-label="hello"></fieldset>`,
+ (browser, accDoc) => {
+ let fieldset = getNativeInterface(accDoc, "fieldset");
+ is(fieldset.getAttributeValue("AXDescription"), "Fields");
+ }
+);
+
+/**
+ * Test to see that list items don't get titled groups
+ */
+addAccessibleTask(
+ `<ul style="list-style: none;"><li id="unstyled-item">Hello</li></ul>
+ <ul><li id="styled-item">World</li></ul>`,
+ (browser, accDoc) => {
+ let unstyledItem = getNativeInterface(accDoc, "unstyled-item");
+ is(unstyledItem.getAttributeValue("AXTitle"), "");
+
+ let styledItem = getNativeInterface(accDoc, "unstyled-item");
+ is(styledItem.getAttributeValue("AXTitle"), "");
+ }
+);
+
+/**
+ * Test that we fire a title changed notification
+ */
+addAccessibleTask(
+ `<div id="elem" aria-label="Hello world"></div>`,
+ async (browser, accDoc) => {
+ let elem = getNativeInterface(accDoc, "elem");
+ is(elem.getAttributeValue("AXTitle"), "Hello world");
+ let evt = waitForMacEvent("AXTitleChanged", "elem");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("elem")
+ .setAttribute("aria-label", "Hello universe");
+ });
+ await evt;
+ is(elem.getAttributeValue("AXTitle"), "Hello universe");
+ }
+);
+
+/**
+ * Test articles supply only labels not titles
+ */
+addAccessibleTask(
+ `<article id="article" aria-label="Hello world"></article>`,
+ async (browser, accDoc) => {
+ let article = getNativeInterface(accDoc, "article");
+ is(article.getAttributeValue("AXDescription"), "Hello world");
+ ok(!article.getAttributeValue("AXTitle"));
+ }
+);
+
+/**
+ * Test text and number inputs supply only labels not titles
+ */
+addAccessibleTask(
+ `<label for="input">Your favorite number?</label><input type="text" name="input" value="11" id="input" aria-label="The best number you know of">`,
+ async (browser, accDoc) => {
+ let input = getNativeInterface(accDoc, "input");
+ is(input.getAttributeValue("AXDescription"), "The best number you know of");
+ ok(!input.getAttributeValue("AXTitle"));
+ let evt = waitForEvent(EVENT_SHOW, "input");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("input").setAttribute("type", "number");
+ });
+ await evt;
+ input = getNativeInterface(accDoc, "input");
+ is(input.getAttributeValue("AXDescription"), "The best number you know of");
+ ok(!input.getAttributeValue("AXTitle"));
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_link.js b/accessible/tests/browser/mac/browser_link.js
new file mode 100644
index 0000000000..3ec62f4c6d
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_link.js
@@ -0,0 +1,231 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+});
+
+/**
+ * Test visited link properties.
+ */
+addAccessibleTask(
+ `
+ <a id="link" href="http://www.example.com/">I am a non-visited link</a><br>
+ `,
+ async (browser, accDoc) => {
+ let link = getNativeInterface(accDoc, "link");
+ let stateChanged = waitForEvent(EVENT_STATE_CHANGE, "link");
+
+ is(link.getAttributeValue("AXVisited"), 0, "Link has not been visited");
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await PlacesTestUtils.addVisits(["http://www.example.com/"]);
+
+ await stateChanged;
+ is(link.getAttributeValue("AXVisited"), 1, "Link has been visited");
+
+ // Ensure history is cleared before running
+ await PlacesUtils.history.clear();
+ }
+);
+
+function waitForLinkedChange(id, isEnabled) {
+ return waitForEvent(EVENT_STATE_CHANGE, e => {
+ e.QueryInterface(nsIAccessibleStateChangeEvent);
+ return (
+ e.state == STATE_LINKED &&
+ !e.isExtraState &&
+ isEnabled == e.isEnabled &&
+ id == getAccessibleDOMNodeID(e.accessible)
+ );
+ });
+}
+
+/**
+ * Test linked vs unlinked anchor tags
+ */
+addAccessibleTask(
+ `
+ <a id="link1" href="#">I am a link link</a>
+ <a id="link2" onclick="console.log('hi')">I am a link-ish link</a>
+ <a id="link3">I am a non-link link</a>
+ `,
+ async (browser, accDoc) => {
+ let link1 = getNativeInterface(accDoc, "link1");
+ is(
+ link1.getAttributeValue("AXRole"),
+ "AXLink",
+ "a[href] gets correct link role"
+ );
+ ok(
+ link1.attributeNames.includes("AXVisited"),
+ "Link has visited attribute"
+ );
+ ok(link1.attributeNames.includes("AXURL"), "Link has URL attribute");
+
+ let link2 = getNativeInterface(accDoc, "link2");
+ is(
+ link2.getAttributeValue("AXRole"),
+ "AXLink",
+ "a[onclick] gets correct link role"
+ );
+ ok(
+ link2.attributeNames.includes("AXVisited"),
+ "Link has visited attribute"
+ );
+ ok(link2.attributeNames.includes("AXURL"), "Link has URL attribute");
+
+ let link3 = getNativeInterface(accDoc, "link3");
+ is(
+ link3.getAttributeValue("AXRole"),
+ "AXGroup",
+ "bare <a> gets correct group role"
+ );
+
+ let stateChanged = waitForLinkedChange("link1", false);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("link1").removeAttribute("href");
+ });
+ await stateChanged;
+ is(
+ link1.getAttributeValue("AXRole"),
+ "AXGroup",
+ "<a> stripped from href gets group role"
+ );
+
+ stateChanged = waitForLinkedChange("link2", false);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("link2").removeAttribute("onclick");
+ });
+ await stateChanged;
+ is(
+ link2.getAttributeValue("AXRole"),
+ "AXGroup",
+ "<a> stripped from onclick gets group role"
+ );
+
+ stateChanged = waitForLinkedChange("link3", true);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("link3")
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ .setAttribute("href", "http://example.com");
+ });
+ await stateChanged;
+ is(
+ link3.getAttributeValue("AXRole"),
+ "AXLink",
+ "href added to bare a gets link role"
+ );
+
+ ok(
+ link3.attributeNames.includes("AXVisited"),
+ "Link has visited attribute"
+ );
+ ok(link3.attributeNames.includes("AXURL"), "Link has URL attribute");
+ }
+);
+
+/**
+ * Test anchors and linked ui elements attr
+ */
+addAccessibleTask(
+ `
+ <a id="link0" href="http://example.com">I am a link</a>
+ <a id="link1" href="#">I am a link with an empty anchor</a>
+ <a id="link2" href="#hello">I am a link with no corresponding element</a>
+ <a id="link3" href="#world">I am a link with a corresponding element</a>
+ <a id="link4" href="#empty">I jump to an empty element</a>
+ <a id="link5" href="#namedElem">I jump to a named element</a>
+ <a id="link6" href="#emptyNamed">I jump to an empty named element</a>
+ <h1 id="world">I am that element</h1>
+ <h2 id="empty"></h2>
+ <a name="namedElem">I have a name</a>
+ <a name="emptyNamed"></a>
+ <h3>I have no name and no ID</h3>
+ <h4></h4>
+ `,
+ async (browser, accDoc) => {
+ let link0 = getNativeInterface(accDoc, "link0");
+ let link1 = getNativeInterface(accDoc, "link1");
+ let link2 = getNativeInterface(accDoc, "link2");
+ let link3 = getNativeInterface(accDoc, "link3");
+ let link4 = getNativeInterface(accDoc, "link4");
+ let link5 = getNativeInterface(accDoc, "link5");
+ let link6 = getNativeInterface(accDoc, "link6");
+
+ is(
+ link0.getAttributeValue("AXLinkedUIElements").length,
+ 0,
+ "Link 0 has no linked UI elements"
+ );
+ is(
+ link1.getAttributeValue("AXLinkedUIElements").length,
+ 0,
+ "Link 1 has no linked UI elements"
+ );
+ is(
+ link2.getAttributeValue("AXLinkedUIElements").length,
+ 0,
+ "Link 2 has no linked UI elements"
+ );
+ is(
+ link3.getAttributeValue("AXLinkedUIElements").length,
+ 1,
+ "Link 3 has one linked UI element"
+ );
+ is(
+ link3
+ .getAttributeValue("AXLinkedUIElements")[0]
+ .getAttributeValue("AXTitle"),
+ "I am that element",
+ "Link 3 is linked to the heading"
+ );
+ is(
+ link4.getAttributeValue("AXLinkedUIElements").length,
+ 1,
+ "Link 4 has one linked UI element"
+ );
+ is(
+ link4
+ .getAttributeValue("AXLinkedUIElements")[0]
+ .getAttributeValue("AXTitle"),
+ null,
+ "Link 4 is linked to the heading"
+ );
+ is(
+ link5.getAttributeValue("AXLinkedUIElements").length,
+ 1,
+ "Link 5 has one linked UI element"
+ );
+ is(
+ link5
+ .getAttributeValue("AXLinkedUIElements")[0]
+ .getAttributeValue("AXTitle"),
+ "I have a name",
+ "Link 5 is linked to a named element"
+ );
+ is(
+ link6.getAttributeValue("AXLinkedUIElements").length,
+ 1,
+ "Link 6 has one linked UI element"
+ );
+ is(
+ link6
+ .getAttributeValue("AXLinkedUIElements")[0]
+ .getAttributeValue("AXTitle"),
+ "",
+ "Link 6 is linked to an empty named element"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_live_regions.js b/accessible/tests/browser/mac/browser_live_regions.js
new file mode 100644
index 0000000000..10a03120f8
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_live_regions.js
@@ -0,0 +1,165 @@
+/* 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";
+
+/**
+ * Test live region creation and removal.
+ */
+addAccessibleTask(
+ `
+ <div id="polite" aria-relevant="removals">Polite region</div>
+ <div id="assertive" aria-live="assertive">Assertive region</div>
+ `,
+ async (browser, accDoc) => {
+ let politeRegion = getNativeInterface(accDoc, "polite");
+ ok(
+ !politeRegion.attributeNames.includes("AXARIALive"),
+ "region is not live"
+ );
+
+ let liveRegionAdded = waitForMacEvent("AXLiveRegionCreated", "polite");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("polite")
+ .setAttribute("aria-atomic", "true");
+ content.document
+ .getElementById("polite")
+ .setAttribute("aria-live", "polite");
+ });
+ await liveRegionAdded;
+ is(
+ politeRegion.getAttributeValue("AXARIALive"),
+ "polite",
+ "region is now live"
+ );
+ ok(politeRegion.getAttributeValue("AXARIAAtomic"), "region is atomic");
+ is(
+ politeRegion.getAttributeValue("AXARIARelevant"),
+ "removals",
+ "region has defined aria-relevant"
+ );
+
+ let assertiveRegion = getNativeInterface(accDoc, "assertive");
+ is(
+ assertiveRegion.getAttributeValue("AXARIALive"),
+ "assertive",
+ "region is assertive"
+ );
+ ok(
+ !assertiveRegion.getAttributeValue("AXARIAAtomic"),
+ "region is not atomic"
+ );
+ is(
+ assertiveRegion.getAttributeValue("AXARIARelevant"),
+ "additions text",
+ "region has default aria-relevant"
+ );
+
+ let liveRegionRemoved = waitForEvent(
+ EVENT_LIVE_REGION_REMOVED,
+ "assertive"
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("assertive").removeAttribute("aria-live");
+ });
+ await liveRegionRemoved;
+ ok(!assertiveRegion.getAttributeValue("AXARIALive"), "region is not live");
+
+ liveRegionAdded = waitForMacEvent("AXLiveRegionCreated", "new-region");
+ await SpecialPowers.spawn(browser, [], () => {
+ let newRegionElm = content.document.createElement("div");
+ newRegionElm.id = "new-region";
+ newRegionElm.setAttribute("aria-live", "assertive");
+ content.document.body.appendChild(newRegionElm);
+ });
+ await liveRegionAdded;
+
+ let newRegion = getNativeInterface(accDoc, "new-region");
+ is(
+ newRegion.getAttributeValue("AXARIALive"),
+ "assertive",
+ "region is assertive"
+ );
+
+ let loadComplete = Promise.all([
+ waitForMacEvent("AXLoadComplete"),
+ waitForMacEvent("AXLiveRegionCreated", "region-1"),
+ waitForMacEvent("AXLiveRegionCreated", "region-2"),
+ waitForMacEvent("AXLiveRegionCreated", "status"),
+ waitForMacEvent("AXLiveRegionCreated", "output"),
+ ]);
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.location = `data:text/html;charset=utf-8,
+ <div id="region-1" aria-live="polite"></div>
+ <div id="region-2" aria-live="assertive"></div>
+ <div id="region-3" aria-live="off"></div>
+ <div id="alert" role="alert"></div>
+ <div id="status" role="status"></div>
+ <output id="output"></output>`;
+ });
+ let webArea = (await loadComplete)[0];
+
+ is(webArea.getAttributeValue("AXRole"), "AXWebArea", "web area yeah");
+ const searchPred = {
+ AXSearchKey: "AXLiveRegionSearchKey",
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+ const liveRegions = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ Assert.deepEqual(
+ liveRegions.map(r => r.getAttributeValue("AXDOMIdentifier")),
+ ["region-1", "region-2", "alert", "status", "output"],
+ "SearchPredicate returned all live regions"
+ );
+ }
+);
+
+/**
+ * Test live region changes
+ */
+addAccessibleTask(
+ `
+ <div id="live" aria-live="polite">
+ The time is <span id="time">4:55pm</span>
+ <p id="p" style="display: none">Georgia on my mind</p>
+ <button id="button" aria-label="Start"></button>
+ </div>
+ `,
+ async (browser, accDoc) => {
+ let liveRegionChanged = waitForMacEvent("AXLiveRegionChanged", "live");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("time").textContent = "4:56pm";
+ });
+ await liveRegionChanged;
+ ok(true, "changed textContent");
+
+ liveRegionChanged = waitForMacEvent("AXLiveRegionChanged", "live");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("p").style.display = "block";
+ });
+ await liveRegionChanged;
+ ok(true, "changed display style to block");
+
+ liveRegionChanged = waitForMacEvent("AXLiveRegionChanged", "live");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("p").style.display = "none";
+ });
+ await liveRegionChanged;
+ ok(true, "changed display style to none");
+
+ liveRegionChanged = waitForMacEvent("AXLiveRegionChanged", "live");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("button")
+ .setAttribute("aria-label", "Stop");
+ });
+ await liveRegionChanged;
+ ok(true, "changed aria-label");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_mathml.js b/accessible/tests/browser/mac/browser_mathml.js
new file mode 100644
index 0000000000..1afaa8399f
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_mathml.js
@@ -0,0 +1,151 @@
+/* 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 testMathAttr(iface, attr, subrole, textLeafValue) {
+ ok(iface.attributeNames.includes(attr), `Object has ${attr} attribute`);
+ let value = iface.getAttributeValue(attr);
+ is(
+ value.getAttributeValue("AXSubrole"),
+ subrole,
+ `${attr} value has correct subrole`
+ );
+
+ if (textLeafValue) {
+ let children = value.getAttributeValue("AXChildren");
+ is(children.length, 1, `${attr} value has one child`);
+
+ is(
+ children[0].getAttributeValue("AXRole"),
+ "AXStaticText",
+ `${attr} value's child is static text`
+ );
+ is(
+ children[0].getAttributeValue("AXValue"),
+ textLeafValue,
+ `${attr} value has correct text`
+ );
+ }
+}
+
+addAccessibleTask(
+ `<math id="math">
+ <msqrt id="sqrt">
+ <mi>-1</mi>
+ </msqrt>
+ </math>`,
+ async (browser, accDoc) => {
+ let math = getNativeInterface(accDoc, "math");
+ is(
+ math.getAttributeValue("AXSubrole"),
+ "AXDocumentMath",
+ "Math element has correct subrole"
+ );
+
+ let sqrt = getNativeInterface(accDoc, "sqrt");
+ is(
+ sqrt.getAttributeValue("AXSubrole"),
+ "AXMathSquareRoot",
+ "msqrt has correct subrole"
+ );
+
+ testMathAttr(sqrt, "AXMathRootRadicand", "AXMathIdentifier", "-1");
+ }
+);
+
+addAccessibleTask(
+ `<math>
+ <mroot id="root">
+ <mi>x</mi>
+ <mn>3</mn>
+ </mroot>
+ </math>`,
+ async (browser, accDoc) => {
+ let root = getNativeInterface(accDoc, "root");
+ is(
+ root.getAttributeValue("AXSubrole"),
+ "AXMathRoot",
+ "mroot has correct subrole"
+ );
+
+ testMathAttr(root, "AXMathRootRadicand", "AXMathIdentifier", "x");
+ testMathAttr(root, "AXMathRootIndex", "AXMathNumber", "3");
+ }
+);
+
+addAccessibleTask(
+ `<math>
+ <mfrac id="fraction">
+ <mi>a</mi>
+ <mi>b</mi>
+ </mfrac>
+ </math>`,
+ async (browser, accDoc) => {
+ let fraction = getNativeInterface(accDoc, "fraction");
+ is(
+ fraction.getAttributeValue("AXSubrole"),
+ "AXMathFraction",
+ "mfrac has correct subrole"
+ );
+ ok(fraction.attributeNames.includes("AXMathFractionNumerator"));
+ ok(fraction.attributeNames.includes("AXMathFractionDenominator"));
+ ok(fraction.attributeNames.includes("AXMathLineThickness"));
+
+ // Bug 1639745
+ todo_is(fraction.getAttributeValue("AXMathLineThickness"), 1);
+
+ testMathAttr(fraction, "AXMathFractionNumerator", "AXMathIdentifier", "a");
+ testMathAttr(
+ fraction,
+ "AXMathFractionDenominator",
+ "AXMathIdentifier",
+ "b"
+ );
+ }
+);
+
+addAccessibleTask(
+ `<math>
+ <msubsup id="subsup">
+ <mo>∫</mo>
+ <mn>0</mn>
+ <mn>1</mn>
+ </msubsup>
+ </math>`,
+ async (browser, accDoc) => {
+ let subsup = getNativeInterface(accDoc, "subsup");
+ is(
+ subsup.getAttributeValue("AXSubrole"),
+ "AXMathSubscriptSuperscript",
+ "msubsup has correct subrole"
+ );
+
+ testMathAttr(subsup, "AXMathSubscript", "AXMathNumber", "0");
+ testMathAttr(subsup, "AXMathSuperscript", "AXMathNumber", "1");
+ testMathAttr(subsup, "AXMathBase", "AXMathOperator", "∫");
+ }
+);
+
+addAccessibleTask(
+ `<math>
+ <munderover id="underover">
+ <mo>∫</mo>
+ <mn>0</mn>
+ <mi>∞</mi>
+ </munderover>
+ </math>`,
+ async (browser, accDoc) => {
+ let underover = getNativeInterface(accDoc, "underover");
+ is(
+ underover.getAttributeValue("AXSubrole"),
+ "AXMathUnderOver",
+ "munderover has correct subrole"
+ );
+
+ testMathAttr(underover, "AXMathUnder", "AXMathNumber", "0");
+ testMathAttr(underover, "AXMathOver", "AXMathIdentifier", "∞");
+ testMathAttr(underover, "AXMathBase", "AXMathOperator", "∫");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_menulist.js b/accessible/tests/browser/mac/browser_menulist.js
new file mode 100644
index 0000000000..b26a0be782
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_menulist.js
@@ -0,0 +1,103 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/attributes.js */
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR },
+ { name: "attributes.js", dir: MOCHITESTS_DIR }
+);
+
+addAccessibleTask(
+ "mac/doc_menulist.xhtml",
+ async (browser, accDoc) => {
+ const menulist = getNativeInterface(accDoc, "defaultZoom");
+
+ let actions = menulist.actionNames;
+ ok(actions.includes("AXPress"), "menu has press action");
+
+ let event = waitForMacEvent("AXMenuOpened");
+ menulist.performAction("AXPress");
+ const menupopup = await event;
+
+ const menuItems = menupopup.getAttributeValue("AXChildren");
+ is(menuItems.length, 4, "Found four children in menulist");
+ is(
+ menuItems[0].getAttributeValue("AXTitle"),
+ "50%",
+ "First item has correct title"
+ );
+ is(
+ menuItems[1].getAttributeValue("AXTitle"),
+ "100%",
+ "Second item has correct title"
+ );
+ is(
+ menuItems[2].getAttributeValue("AXTitle"),
+ "150%",
+ "Third item has correct title"
+ );
+ is(
+ menuItems[3].getAttributeValue("AXTitle"),
+ "200%",
+ "Fourth item has correct title"
+ );
+ },
+ { topLevel: false, chrome: true }
+);
+
+addAccessibleTask(
+ "mac/doc_menulist.xhtml",
+ async (browser, accDoc) => {
+ const menulist = getNativeInterface(accDoc, "defaultZoom");
+
+ const actions = menulist.actionNames;
+ ok(actions.includes("AXPress"), "menu has press action");
+ let event = waitForMacEvent("AXMenuOpened");
+ menulist.performAction("AXPress");
+ await event;
+
+ const menu = menulist.getAttributeValue("AXChildren")[0];
+ ok(menu, "Menulist contains menu");
+ const children = menu.getAttributeValue("AXChildren");
+ is(children.length, 4, "Menu has 4 items");
+
+ // Menu is open, initial focus should land on the first item
+ is(
+ children[0].getAttributeValue("AXSelected"),
+ 1,
+ "First menu item is selected"
+ );
+ // focus the second item, and verify it is selected
+ event = waitForMacEvent("AXFocusedUIElementChanged", (iface, data) => {
+ try {
+ return iface.getAttributeValue("AXTitle") == "100%";
+ } catch (e) {
+ return false;
+ }
+ });
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ await event;
+
+ is(
+ children[0].getAttributeValue("AXSelected"),
+ 0,
+ "First menu item is no longer selected"
+ );
+ is(
+ children[1].getAttributeValue("AXSelected"),
+ 1,
+ "Second menu item is selected"
+ );
+ // press the second item, check for selected event
+ event = waitForMacEvent("AXMenuItemSelected");
+ children[1].performAction("AXPress");
+ await event;
+ },
+ { topLevel: false, chrome: true }
+);
diff --git a/accessible/tests/browser/mac/browser_navigate.js b/accessible/tests/browser/mac/browser_navigate.js
new file mode 100644
index 0000000000..69486676e4
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_navigate.js
@@ -0,0 +1,394 @@
+/* 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";
+
+/**
+ * Test navigation of same/different type content
+ */
+addAccessibleTask(
+ `<h1 id="hello">hello</h1>
+ world<br>
+ <a href="example.com" id="link">I am a link</a>
+ <h1 id="goodbye">goodbye</h1>`,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXSameTypeSearchKey",
+ AXImmediateDescendantsOnly: 0,
+ AXResultsLimit: 1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const hello = getNativeInterface(accDoc, "hello");
+ const goodbye = getNativeInterface(accDoc, "goodbye");
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ searchPred.AXStartElement = hello;
+
+ let sameItem = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(sameItem.length, 1, "Found one item");
+ is(
+ "goodbye",
+ sameItem[0].getAttributeValue("AXTitle"),
+ "Found correct item of same type"
+ );
+
+ searchPred.AXDirection = "AXDirectionPrevious";
+ searchPred.AXStartElement = goodbye;
+ sameItem = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(sameItem.length, 1, "Found one item");
+ is(
+ "hello",
+ sameItem[0].getAttributeValue("AXTitle"),
+ "Found correct item of same type"
+ );
+
+ searchPred.AXSearchKey = "AXDifferentTypeSearchKey";
+ let diffItem = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(diffItem.length, 1, "Found one item");
+ is(
+ "I am a link",
+ diffItem[0].getAttributeValue("AXValue"),
+ "Found correct item of different type"
+ );
+ }
+);
+
+/**
+ * Test navigation of heading levels
+ */
+addAccessibleTask(
+ `
+ <h1 id="a">a</h1>
+ <h2 id="b">b</h2>
+ <h3 id="c">c</h3>
+ <h4 id="d">d</h4>
+ <h5 id="e">e</h5>
+ <h6 id="f">f</h5>
+ <h1 id="g">g</h1>
+ <h2 id="h">h</h2>
+ <h3 id="i">i</h3>
+ <h4 id="j">j</h4>
+ <h5 id="k">k</h5>
+ <h6 id="l">l</h5>
+ this is some regular text that should be ignored
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXHeadingLevel1SearchKey",
+ AXImmediateDescendantsOnly: 0,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ let h1Count = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(2, h1Count, "Found two h1 items");
+
+ let h1s = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const a = getNativeInterface(accDoc, "a");
+ const g = getNativeInterface(accDoc, "g");
+
+ is(
+ a.getAttributeValue("AXValue"),
+ h1s[0].getAttributeValue("AXValue"),
+ "Found correct h1 heading"
+ );
+
+ is(
+ g.getAttributeValue("AXValue"),
+ h1s[1].getAttributeValue("AXValue"),
+ "Found correct h1 heading"
+ );
+
+ searchPred.AXSearchKey = "AXHeadingLevel2SearchKey";
+
+ let h2Count = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(2, h2Count, "Found two h2 items");
+
+ let h2s = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const b = getNativeInterface(accDoc, "b");
+ const h = getNativeInterface(accDoc, "h");
+
+ is(
+ b.getAttributeValue("AXValue"),
+ h2s[0].getAttributeValue("AXValue"),
+ "Found correct h2 heading"
+ );
+
+ is(
+ h.getAttributeValue("AXValue"),
+ h2s[1].getAttributeValue("AXValue"),
+ "Found correct h2 heading"
+ );
+
+ searchPred.AXSearchKey = "AXHeadingLevel3SearchKey";
+
+ let h3Count = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(2, h3Count, "Found two h3 items");
+
+ let h3s = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const c = getNativeInterface(accDoc, "c");
+ const i = getNativeInterface(accDoc, "i");
+
+ is(
+ c.getAttributeValue("AXValue"),
+ h3s[0].getAttributeValue("AXValue"),
+ "Found correct h3 heading"
+ );
+
+ is(
+ i.getAttributeValue("AXValue"),
+ h3s[1].getAttributeValue("AXValue"),
+ "Found correct h3 heading"
+ );
+
+ searchPred.AXSearchKey = "AXHeadingLevel4SearchKey";
+
+ let h4Count = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(2, h4Count, "Found two h4 items");
+
+ let h4s = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const d = getNativeInterface(accDoc, "d");
+ const j = getNativeInterface(accDoc, "j");
+
+ is(
+ d.getAttributeValue("AXValue"),
+ h4s[0].getAttributeValue("AXValue"),
+ "Found correct h4 heading"
+ );
+
+ is(
+ j.getAttributeValue("AXValue"),
+ h4s[1].getAttributeValue("AXValue"),
+ "Found correct h4 heading"
+ );
+
+ searchPred.AXSearchKey = "AXHeadingLevel5SearchKey";
+
+ let h5Count = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(2, h5Count, "Found two h5 items");
+
+ let h5s = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const e = getNativeInterface(accDoc, "e");
+ const k = getNativeInterface(accDoc, "k");
+
+ is(
+ e.getAttributeValue("AXValue"),
+ h5s[0].getAttributeValue("AXValue"),
+ "Found correct h5 heading"
+ );
+
+ is(
+ k.getAttributeValue("AXValue"),
+ h5s[1].getAttributeValue("AXValue"),
+ "Found correct h5 heading"
+ );
+
+ searchPred.AXSearchKey = "AXHeadingLevel6SearchKey";
+
+ let h6Count = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(2, h6Count, "Found two h6 items");
+
+ let h6s = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const f = getNativeInterface(accDoc, "f");
+ const l = getNativeInterface(accDoc, "l");
+
+ is(
+ f.getAttributeValue("AXValue"),
+ h6s[0].getAttributeValue("AXValue"),
+ "Found correct h6 heading"
+ );
+
+ is(
+ l.getAttributeValue("AXValue"),
+ h6s[1].getAttributeValue("AXValue"),
+ "Found correct h6 heading"
+ );
+ }
+);
+
+/*
+ * Test rotor with blockquotes
+ */
+addAccessibleTask(
+ `
+ <blockquote id="first">hello I am a blockquote</blockquote>
+ <blockquote id="second">
+ I am also a blockquote of the same level
+ <br>
+ <blockquote id="third">but I have a different level</blockquote>
+ </blockquote>
+ `,
+ (browser, accDoc) => {
+ let searchPred = {
+ AXSearchKey: "AXBlockquoteSearchKey",
+ AXImmediateDescendantsOnly: 0,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ let bquotes = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(bquotes.length, 3, "Found three blockquotes");
+
+ const first = getNativeInterface(accDoc, "first");
+ const second = getNativeInterface(accDoc, "second");
+ const third = getNativeInterface(accDoc, "third");
+ console.log("values :");
+ console.log(first.getAttributeValue("AXValue"));
+ is(
+ first.getAttributeValue("AXValue"),
+ bquotes[0].getAttributeValue("AXValue"),
+ "Found correct first blockquote"
+ );
+
+ is(
+ second.getAttributeValue("AXValue"),
+ bquotes[1].getAttributeValue("AXValue"),
+ "Found correct second blockquote"
+ );
+
+ is(
+ third.getAttributeValue("AXValue"),
+ bquotes[2].getAttributeValue("AXValue"),
+ "Found correct third blockquote"
+ );
+ }
+);
+
+/*
+ * Test rotor with graphics
+ */
+addAccessibleTask(
+ `
+ <img id="img1" alt="image one" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"><br>
+ <a href="http://example.com">
+ <img id="img2" alt="image two" src="http://example.com/a11y/accessible/tests/mochitest/moz.png">
+ </a>
+ <img src="" id="img3">
+ `,
+ (browser, accDoc) => {
+ let searchPred = {
+ AXSearchKey: "AXGraphicSearchKey",
+ AXImmediateDescendantsOnly: 0,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ let images = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(images.length, 3, "Found three images");
+
+ const img1 = getNativeInterface(accDoc, "img1");
+ const img2 = getNativeInterface(accDoc, "img2");
+ const img3 = getNativeInterface(accDoc, "img3");
+
+ is(
+ img1.getAttributeValue("AXDescription"),
+ images[0].getAttributeValue("AXDescription"),
+ "Found correct image"
+ );
+
+ is(
+ img2.getAttributeValue("AXDescription"),
+ images[1].getAttributeValue("AXDescription"),
+ "Found correct image"
+ );
+
+ is(
+ img3.getAttributeValue("AXDescription"),
+ images[2].getAttributeValue("AXDescription"),
+ "Found correct image"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_outline.js b/accessible/tests/browser/mac/browser_outline.js
new file mode 100644
index 0000000000..ba211fdf4b
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_outline.js
@@ -0,0 +1,566 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/states.js */
+loadScripts({ name: "states.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Test outline, outline rows with computed properties
+ */
+addAccessibleTask(
+ `
+ <h3 id="tree1">
+ Foods
+ </h3>
+ <ul role="tree" aria-labelledby="tree1" id="outline">
+ <li role="treeitem" aria-expanded="false">
+ <span>
+ Fruits
+ </span>
+ <ul>
+ <li role="none">Oranges</li>
+ <li role="treeitem" aria-expanded="true">
+ <span>
+ Apples
+ </span>
+ <ul role="group">
+ <li role="none">Honeycrisp</li>
+ <li role="none">Granny Smith</li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ <li id="vegetables" role="treeitem" aria-expanded="false">
+ <span>
+ Vegetables
+ </span>
+ <ul role="group">
+ <li role="treeitem" aria-expanded="true">
+ <span>
+ Podded Vegetables
+ </span>
+ <ul role="group">
+ <li role="none">Lentil</li>
+ <li role="none">Pea</li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ `,
+ async (browser, accDoc) => {
+ const outline = getNativeInterface(accDoc, "outline");
+ is(
+ outline.getAttributeValue("AXRole"),
+ "AXOutline",
+ "Correct role for outline"
+ );
+
+ const outChildren = outline.getAttributeValue("AXChildren");
+ is(outChildren.length, 2, "Outline has two direct children");
+ is(outChildren[0].getAttributeValue("AXSubrole"), "AXOutlineRow");
+ is(outChildren[1].getAttributeValue("AXSubrole"), "AXOutlineRow");
+
+ const outRows = outline.getAttributeValue("AXRows");
+ is(outRows.length, 4, "Outline has four rows");
+ is(
+ outRows[0].getAttributeValue("AXDisclosing"),
+ 0,
+ "Row is not disclosing"
+ );
+ is(
+ outRows[0].getAttributeValue("AXDisclosedByRow"),
+ null,
+ "Row is direct child of outline"
+ );
+ is(
+ outRows[0].getAttributeValue("AXDisclosedRows").length,
+ 0,
+ "Row has no row children, only group"
+ );
+ is(
+ outRows[0].getAttributeValue("AXDisclosureLevel"),
+ 0,
+ "Row is level zero"
+ );
+
+ is(outRows[1].getAttributeValue("AXDisclosing"), 1, "Row is disclosing");
+ is(
+ outRows[1].getAttributeValue("AXDisclosedByRow"),
+ null,
+ "Row is direct child of group"
+ );
+ is(
+ outRows[1].getAttributeValue("AXDisclosedRows").length,
+ 0,
+ "Row has no row children"
+ );
+ is(
+ outRows[1].getAttributeValue("AXDisclosureLevel"),
+ 0,
+ "Row is level zero"
+ );
+
+ is(
+ outRows[2].getAttributeValue("AXDisclosing"),
+ 0,
+ "Row is not disclosing"
+ );
+ is(
+ outRows[2].getAttributeValue("AXDisclosedByRow"),
+ null,
+ "Row is direct child of outline"
+ );
+ is(
+ outRows[2].getAttributeValue("AXDisclosedRows").length,
+ 1,
+ "Row has one row child"
+ );
+ is(
+ outRows[2].getAttributeValue("AXDisclosureLevel"),
+ 0,
+ "Row is level zero"
+ );
+
+ is(outRows[3].getAttributeValue("AXDisclosing"), 1, "Row is disclosing");
+ is(
+ outRows[3]
+ .getAttributeValue("AXDisclosedByRow")
+ .getAttributeValue("AXDescription"),
+ outRows[2].getAttributeValue("AXDescription"),
+ "Row is direct child of row[2]"
+ );
+ is(
+ outRows[3].getAttributeValue("AXDisclosedRows").length,
+ 0,
+ "Row has no row children"
+ );
+ is(
+ outRows[3].getAttributeValue("AXDisclosureLevel"),
+ 1,
+ "Row is level one"
+ );
+
+ let evt = waitForMacEvent("AXRowExpanded", "vegetables");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("vegetables")
+ .setAttribute("aria-expanded", "true");
+ });
+ await evt;
+ is(
+ outRows[2].getAttributeValue("AXDisclosing"),
+ 1,
+ "Row is disclosing after being expanded"
+ );
+
+ evt = waitForMacEvent("AXRowCollapsed", "vegetables");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("vegetables")
+ .setAttribute("aria-expanded", "false");
+ });
+ await evt;
+ is(
+ outRows[2].getAttributeValue("AXDisclosing"),
+ 0,
+ "Row is not disclosing after being collapsed again"
+ );
+ }
+);
+
+/**
+ * Test outline, outline rows with declared properties
+ */
+addAccessibleTask(
+ `
+ <h3 id="tree1">
+ Foods
+ </h3>
+ <ul role="tree" aria-labelledby="tree1" id="outline">
+ <li role="treeitem"
+ aria-level="1"
+ aria-setsize="2"
+ aria-posinset="1"
+ aria-expanded="false">
+ <span>
+ Fruits
+ </span>
+ <ul>
+ <li role="treeitem"
+ aria-level="3"
+ aria-setsize="2"
+ aria-posinset="1">
+ Oranges
+ </li>
+ <li role="treeitem"
+ aria-level="2"
+ aria-setsize="2"
+ aria-posinset="2"
+ aria-expanded="true">
+ <span>
+ Apples
+ </span>
+ <ul role="group">
+ <li role="treeitem"
+ aria-level="3"
+ aria-setsize="2"
+ aria-posinset="1">
+ Honeycrisp
+ </li>
+ <li role="treeitem"
+ aria-level="3"
+ aria-setsize="2"
+ aria-posinset="2">
+ Granny Smith
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ <li role="treeitem"
+ aria-level="1"
+ aria-setsize="2"
+ aria-posinset="2"
+ aria-expanded="false">
+ <span>
+ Vegetables
+ </span>
+ <ul role="group">
+ <li role="treeitem"
+ aria-level="2"
+ aria-setsize="1"
+ aria-posinset="1"
+ aria-expanded="true">
+ <span>
+ Podded Vegetables
+ </span>
+ <ul role="group">
+ <li role="treeitem"
+ aria-level="3"
+ aria-setsize="2"
+ aria-posinset="1">
+ Lentil
+ </li>
+ <li role="treeitem"
+ aria-level="3"
+ aria-setsize="2"
+ aria-posinset="2">
+ Pea
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ `,
+ async (browser, accDoc) => {
+ const outline = getNativeInterface(accDoc, "outline");
+ is(
+ outline.getAttributeValue("AXRole"),
+ "AXOutline",
+ "Correct role for outline"
+ );
+
+ const outChildren = outline.getAttributeValue("AXChildren");
+ is(outChildren.length, 2, "Outline has two direct children");
+ is(outChildren[0].getAttributeValue("AXSubrole"), "AXOutlineRow");
+ is(outChildren[1].getAttributeValue("AXSubrole"), "AXOutlineRow");
+
+ const outRows = outline.getAttributeValue("AXRows");
+ is(outRows.length, 9, "Outline has nine rows");
+ is(
+ outRows[0].getAttributeValue("AXDisclosing"),
+ 0,
+ "Row is not disclosing"
+ );
+ is(
+ outRows[0].getAttributeValue("AXDisclosedByRow"),
+ null,
+ "Row is direct child of outline"
+ );
+ is(
+ outRows[0].getAttributeValue("AXDisclosedRows").length,
+ 0,
+ "Row has no direct row children, has list"
+ );
+ is(
+ outRows[0].getAttributeValue("AXDisclosureLevel"),
+ 0,
+ "Row is level zero"
+ );
+
+ is(outRows[2].getAttributeValue("AXDisclosing"), 1, "Row is disclosing");
+ is(
+ outRows[2].getAttributeValue("AXDisclosedByRow"),
+ null,
+ "Row is direct child of group"
+ );
+ is(
+ outRows[2].getAttributeValue("AXDisclosedRows").length,
+ 2,
+ "Row has two row children"
+ );
+ is(
+ outRows[2].getAttributeValue("AXDisclosureLevel"),
+ 1,
+ "Row is level one"
+ );
+
+ is(
+ outRows[3].getAttributeValue("AXDisclosing"),
+ 0,
+ "Row is not disclosing"
+ );
+ is(
+ outRows[3]
+ .getAttributeValue("AXDisclosedByRow")
+ .getAttributeValue("AXDescription"),
+ outRows[2].getAttributeValue("AXDescription"),
+ "Row is direct child of row 2"
+ );
+
+ is(
+ outRows[3].getAttributeValue("AXDisclosedRows").length,
+ 0,
+ "Row has no row children"
+ );
+ is(
+ outRows[3].getAttributeValue("AXDisclosureLevel"),
+ 2,
+ "Row is level two"
+ );
+
+ is(
+ outRows[5].getAttributeValue("AXDisclosing"),
+ 0,
+ "Row is not disclosing"
+ );
+ is(
+ outRows[5].getAttributeValue("AXDisclosedByRow"),
+ null,
+ "Row is direct child of outline"
+ );
+ is(
+ outRows[5].getAttributeValue("AXDisclosedRows").length,
+ 1,
+ "Row has no one row child"
+ );
+ is(
+ outRows[5].getAttributeValue("AXDisclosureLevel"),
+ 0,
+ "Row is level zero"
+ );
+
+ is(outRows[6].getAttributeValue("AXDisclosing"), 1, "Row is disclosing");
+ is(
+ outRows[6]
+ .getAttributeValue("AXDisclosedByRow")
+ .getAttributeValue("AXDescription"),
+ outRows[5].getAttributeValue("AXDescription"),
+ "Row is direct child of row 5"
+ );
+ is(
+ outRows[6].getAttributeValue("AXDisclosedRows").length,
+ 2,
+ "Row has two row children"
+ );
+ is(
+ outRows[6].getAttributeValue("AXDisclosureLevel"),
+ 1,
+ "Row is level one"
+ );
+
+ is(
+ outRows[7].getAttributeValue("AXDisclosing"),
+ 0,
+ "Row is not disclosing"
+ );
+ is(
+ outRows[7]
+ .getAttributeValue("AXDisclosedByRow")
+ .getAttributeValue("AXDescription"),
+ outRows[6].getAttributeValue("AXDescription"),
+ "Row is direct child of row 6"
+ );
+ is(
+ outRows[7].getAttributeValue("AXDisclosedRows").length,
+ 0,
+ "Row has no row children"
+ );
+ is(
+ outRows[7].getAttributeValue("AXDisclosureLevel"),
+ 2,
+ "Row is level two"
+ );
+ }
+);
+
+// Test outline that isn't built with li/uls gets correct desc
+addAccessibleTask(
+ `
+ <div role="tree" id="tree" tabindex="0" aria-label="My drive" aria-activedescendant="myfiles">
+ <div id="myfiles" role="treeitem" aria-label="My files" aria-selected="true" aria-expanded="false">My files</div>
+ <div role="treeitem" aria-label="Shared items" aria-selected="false" aria-expanded="false">Shared items</div>
+ </div>
+ `,
+ async (browser, accDoc) => {
+ const tree = getNativeInterface(accDoc, "tree");
+ is(tree.getAttributeValue("AXRole"), "AXOutline", "Correct role for tree");
+
+ const treeItems = tree.getAttributeValue("AXChildren");
+ is(treeItems.length, 2, "Outline has two direct children");
+ is(treeItems[0].getAttributeValue("AXSubrole"), "AXOutlineRow");
+ is(treeItems[1].getAttributeValue("AXSubrole"), "AXOutlineRow");
+
+ const outRows = tree.getAttributeValue("AXRows");
+ is(outRows.length, 2, "Outline has two rows");
+
+ is(
+ outRows[0].getAttributeValue("AXDescription"),
+ "My files",
+ "files labelled correctly"
+ );
+ is(
+ outRows[1].getAttributeValue("AXDescription"),
+ "Shared items",
+ "shared items labelled correctly"
+ );
+ }
+);
+
+// Test outline registers AXDisclosed attr as settable
+addAccessibleTask(
+ `
+ <div role="tree" id="tree" tabindex="0" aria-label="My drive" aria-activedescendant="myfiles">
+ <div id="myfiles" role="treeitem" aria-label="My files" aria-selected="true" aria-expanded="false">My files</div>
+ <div role="treeitem" aria-label="Shared items" aria-selected="false" aria-expanded="true">Shared items</div>
+ </div>
+ `,
+ async (browser, accDoc) => {
+ const tree = getNativeInterface(accDoc, "tree");
+ const treeItems = tree.getAttributeValue("AXChildren");
+
+ is(treeItems.length, 2, "Outline has two direct children");
+ is(treeItems[0].getAttributeValue("AXDisclosing"), 0);
+ is(treeItems[1].getAttributeValue("AXDisclosing"), 1);
+
+ is(treeItems[0].isAttributeSettable("AXDisclosing"), true);
+ is(treeItems[1].isAttributeSettable("AXDisclosing"), true);
+
+ // attempt to change attribute values
+ treeItems[0].setAttributeValue("AXDisclosing", 1);
+ treeItems[0].setAttributeValue("AXDisclosing", 0);
+
+ // verify they're unchanged
+ is(treeItems[0].getAttributeValue("AXDisclosing"), 0);
+ is(treeItems[1].getAttributeValue("AXDisclosing"), 1);
+ }
+);
+
+// Test outline rows correctly expose checkable, checked/unchecked/mixed status
+addAccessibleTask(
+ `
+ <div role="tree" id="tree">
+ <div role="treeitem" aria-checked="false" id="l1">
+ Leaf 1
+ </div>
+ <div role="treeitem" aria-checked="true" id="l2">
+ Leaf 2
+ </div>
+ <div role="treeitem" id="l3">
+ Leaf 3
+ </div>
+ <div role="treeitem" aria-checked="mixed" id="l4">
+ Leaf 4
+ </div>
+ </div>
+
+ `,
+ async (browser, accDoc) => {
+ const tree = getNativeInterface(accDoc, "tree");
+ const treeItems = tree.getAttributeValue("AXChildren");
+
+ is(treeItems.length, 4, "Outline has four direct children");
+ is(
+ treeItems[0].getAttributeValue("AXValue"),
+ 0,
+ "Child one is not checked"
+ );
+ is(treeItems[1].getAttributeValue("AXValue"), 1, "Child two is checked");
+ is(
+ treeItems[2].getAttributeValue("AXValue"),
+ null,
+ "Child three is not checkable and has no val"
+ );
+ is(treeItems[3].getAttributeValue("AXValue"), 2, "Child four is mixed");
+
+ let stateChanged = Promise.all([
+ waitForMacEvent("AXValueChanged", "l1"),
+ waitForStateChange("l1", STATE_CHECKED, true),
+ ]);
+ // We should get a state change event for checked.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("l1")
+ .setAttribute("aria-checked", "true");
+ });
+ await stateChanged;
+ is(treeItems[0].getAttributeValue("AXValue"), 1, "Child one is checked");
+
+ stateChanged = Promise.all([
+ waitForMacEvent("AXValueChanged", "l2"),
+ waitForMacEvent("AXValueChanged", "l2"),
+ waitForStateChange("l2", STATE_CHECKED, false),
+ waitForStateChange("l2", STATE_CHECKABLE, false),
+ ]);
+ // We should get a state change event for both checked and checkable,
+ // and value changes for both.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("l2").removeAttribute("aria-checked");
+ });
+ await stateChanged;
+ is(
+ treeItems[1].getAttributeValue("AXValue"),
+ null,
+ "Child two is not checkable and has no val"
+ );
+
+ stateChanged = Promise.all([
+ waitForMacEvent("AXValueChanged", "l3"),
+ waitForMacEvent("AXValueChanged", "l3"),
+ waitForStateChange("l3", STATE_CHECKED, true),
+ waitForStateChange("l3", STATE_CHECKABLE, true),
+ ]);
+ // We should get a state change event for both checked and checkable,
+ // and value changes for each.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("l3")
+ .setAttribute("aria-checked", "true");
+ });
+ await stateChanged;
+ is(treeItems[2].getAttributeValue("AXValue"), 1, "Child three is checked");
+
+ stateChanged = Promise.all([
+ waitForMacEvent("AXValueChanged", "l4"),
+ waitForMacEvent("AXValueChanged", "l4"),
+ waitForStateChange("l4", STATE_MIXED, false),
+ waitForStateChange("l4", STATE_CHECKABLE, false),
+ ]);
+ // We should get a state change event for both mixed and checkable,
+ // and value changes for each.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("l4").removeAttribute("aria-checked");
+ });
+ await stateChanged;
+ is(
+ treeItems[3].getAttributeValue("AXValue"),
+ null,
+ "Child four is not checkable and has no value"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_outline_xul.js b/accessible/tests/browser/mac/browser_outline_xul.js
new file mode 100644
index 0000000000..66eebebf50
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_outline_xul.js
@@ -0,0 +1,274 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/attributes.js */
+loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ "mac/doc_tree.xhtml",
+ async (browser, accDoc) => {
+ const tree = getNativeInterface(accDoc, "tree");
+ is(
+ tree.getAttributeValue("AXRole"),
+ "AXOutline",
+ "Found tree with role outline"
+ );
+ // XUL trees store all rows as direct children of the outline,
+ // so we should see nine here instead of just three:
+ // (Groceries, Fruits, Veggies)
+ const treeChildren = tree.getAttributeValue("AXChildren");
+ is(treeChildren.length, 9, "Found nine direct children");
+
+ const treeCols = tree.getAttributeValue("AXColumns");
+ is(treeCols.length, 1, "Found one column in tree");
+
+ // Here, we should get only outline rows, not the title
+ const treeRows = tree.getAttributeValue("AXRows");
+ is(treeRows.length, 8, "Found 8 total rows");
+
+ is(
+ treeRows[0].getAttributeValue("AXDescription"),
+ "Fruits",
+ "Located correct first row, row has correct desc"
+ );
+ is(
+ treeRows[0].getAttributeValue("AXDisclosing"),
+ 1,
+ "Fruits is disclosing"
+ );
+ is(
+ treeRows[0].getAttributeValue("AXDisclosedByRow"),
+ null,
+ "Fruits is disclosed by outline"
+ );
+ is(
+ treeRows[0].getAttributeValue("AXDisclosureLevel"),
+ 0,
+ "Fruits is level zero"
+ );
+ let disclosedRows = treeRows[0].getAttributeValue("AXDisclosedRows");
+ is(disclosedRows.length, 2, "Fruits discloses two rows");
+ is(
+ disclosedRows[0].getAttributeValue("AXDescription"),
+ "Apple",
+ "fruits discloses apple"
+ );
+ is(
+ disclosedRows[1].getAttributeValue("AXDescription"),
+ "Orange",
+ "fruits discloses orange"
+ );
+
+ is(
+ treeRows[1].getAttributeValue("AXDescription"),
+ "Apple",
+ "Located correct second row, row has correct desc"
+ );
+ is(
+ treeRows[1].getAttributeValue("AXDisclosing"),
+ 0,
+ "Apple is not disclosing"
+ );
+ is(
+ treeRows[1]
+ .getAttributeValue("AXDisclosedByRow")
+ .getAttributeValue("AXDescription"),
+ "Fruits",
+ "Apple is disclosed by fruits"
+ );
+ is(
+ treeRows[1].getAttributeValue("AXDisclosureLevel"),
+ 1,
+ "Apple is level one"
+ );
+ is(
+ treeRows[1].getAttributeValue("AXDisclosedRows").length,
+ 0,
+ "Apple does not disclose rows"
+ );
+
+ is(
+ treeRows[2].getAttributeValue("AXDescription"),
+ "Orange",
+ "Located correct third row, row has correct desc"
+ );
+ is(
+ treeRows[2].getAttributeValue("AXDisclosing"),
+ 0,
+ "Orange is not disclosing"
+ );
+ is(
+ treeRows[2]
+ .getAttributeValue("AXDisclosedByRow")
+ .getAttributeValue("AXDescription"),
+ "Fruits",
+ "Orange is disclosed by fruits"
+ );
+ is(
+ treeRows[2].getAttributeValue("AXDisclosureLevel"),
+ 1,
+ "Orange is level one"
+ );
+ is(
+ treeRows[2].getAttributeValue("AXDisclosedRows").length,
+ 0,
+ "Orange does not disclose rows"
+ );
+
+ is(
+ treeRows[3].getAttributeValue("AXDescription"),
+ "Veggies",
+ "Located correct fourth row, row has correct desc"
+ );
+ is(
+ treeRows[3].getAttributeValue("AXDisclosing"),
+ 1,
+ "Veggies is disclosing"
+ );
+ is(
+ treeRows[3].getAttributeValue("AXDisclosedByRow"),
+ null,
+ "Veggies is disclosed by outline"
+ );
+ is(
+ treeRows[3].getAttributeValue("AXDisclosureLevel"),
+ 0,
+ "Veggies is level zero"
+ );
+ disclosedRows = treeRows[3].getAttributeValue("AXDisclosedRows");
+ is(disclosedRows.length, 2, "Veggies discloses two rows");
+ is(
+ disclosedRows[0].getAttributeValue("AXDescription"),
+ "Green Veggies",
+ "Veggies discloses green veggies"
+ );
+ is(
+ disclosedRows[1].getAttributeValue("AXDescription"),
+ "Squash",
+ "Veggies discloses squash"
+ );
+
+ is(
+ treeRows[4].getAttributeValue("AXDescription"),
+ "Green Veggies",
+ "Located correct fifth row, row has correct desc"
+ );
+ is(
+ treeRows[4].getAttributeValue("AXDisclosing"),
+ 1,
+ "Green veggies is disclosing"
+ );
+ is(
+ treeRows[4]
+ .getAttributeValue("AXDisclosedByRow")
+ .getAttributeValue("AXDescription"),
+ "Veggies",
+ "Green Veggies is disclosed by veggies"
+ );
+ is(
+ treeRows[4].getAttributeValue("AXDisclosureLevel"),
+ 1,
+ "Green veggies is level one"
+ );
+ disclosedRows = treeRows[4].getAttributeValue("AXDisclosedRows");
+ is(disclosedRows.length, 2, "Green veggies has two rows");
+ is(
+ disclosedRows[0].getAttributeValue("AXDescription"),
+ "Spinach",
+ "Green veggies discloses spinach"
+ );
+ is(
+ disclosedRows[1].getAttributeValue("AXDescription"),
+ "Peas",
+ "Green veggies discloses peas"
+ );
+
+ is(
+ treeRows[5].getAttributeValue("AXDescription"),
+ "Spinach",
+ "Located correct sixth row, row has correct desc"
+ );
+ is(
+ treeRows[5].getAttributeValue("AXDisclosing"),
+ 0,
+ "Spinach is not disclosing"
+ );
+ is(
+ treeRows[5]
+ .getAttributeValue("AXDisclosedByRow")
+ .getAttributeValue("AXDescription"),
+ "Green Veggies",
+ "Spinach is disclosed by green veggies"
+ );
+ is(
+ treeRows[5].getAttributeValue("AXDisclosureLevel"),
+ 2,
+ "Spinach is level two"
+ );
+ is(
+ treeRows[5].getAttributeValue("AXDisclosedRows").length,
+ 0,
+ "Spinach does not disclose rows"
+ );
+
+ is(
+ treeRows[6].getAttributeValue("AXDescription"),
+ "Peas",
+ "Located correct seventh row, row has correct desc"
+ );
+ is(
+ treeRows[6].getAttributeValue("AXDisclosing"),
+ 0,
+ "Peas is not disclosing"
+ );
+ is(
+ treeRows[6]
+ .getAttributeValue("AXDisclosedByRow")
+ .getAttributeValue("AXDescription"),
+ "Green Veggies",
+ "Peas is disclosed by green veggies"
+ );
+ is(
+ treeRows[6].getAttributeValue("AXDisclosureLevel"),
+ 2,
+ "Peas is level two"
+ );
+ is(
+ treeRows[6].getAttributeValue("AXDisclosedRows").length,
+ 0,
+ "Peas does not disclose rows"
+ );
+
+ is(
+ treeRows[7].getAttributeValue("AXDescription"),
+ "Squash",
+ "Located correct eighth row, row has correct desc"
+ );
+ is(
+ treeRows[7].getAttributeValue("AXDisclosing"),
+ 0,
+ "Squash is not disclosing"
+ );
+ is(
+ treeRows[7]
+ .getAttributeValue("AXDisclosedByRow")
+ .getAttributeValue("AXDescription"),
+ "Veggies",
+ "Squash is disclosed by veggies"
+ );
+ is(
+ treeRows[7].getAttributeValue("AXDisclosureLevel"),
+ 1,
+ "Squash is level one"
+ );
+ is(
+ treeRows[7].getAttributeValue("AXDisclosedRows").length,
+ 0,
+ "Squash does not disclose rows"
+ );
+ },
+ { topLevel: false, chrome: true }
+);
diff --git a/accessible/tests/browser/mac/browser_popupbutton.js b/accessible/tests/browser/mac/browser_popupbutton.js
new file mode 100644
index 0000000000..2d5ff1ac35
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_popupbutton.js
@@ -0,0 +1,166 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+// Test dropdown select element
+addAccessibleTask(
+ `<select id="select" aria-label="Choose a number">
+ <option id="one" selected>One</option>
+ <option id="two">Two</option>
+ <option id="three">Three</option>
+ <option id="four" disabled>Four</option>
+ </select>`,
+ async (browser, accDoc) => {
+ // Test combobox
+ let select = getNativeInterface(accDoc, "select");
+ is(
+ select.getAttributeValue("AXRole"),
+ "AXPopUpButton",
+ "select has AXPopupButton role"
+ );
+ ok(select.attributeNames.includes("AXValue"), "select advertises AXValue");
+ is(
+ select.getAttributeValue("AXValue"),
+ "One",
+ "select has correctt initial value"
+ );
+ ok(
+ !select.attributeNames.includes("AXHasPopup"),
+ "select does not advertise AXHasPopup"
+ );
+ is(
+ select.getAttributeValue("AXHasPopup"),
+ null,
+ "select does not provide value for AXHasPopup"
+ );
+
+ ok(select.actionNames.includes("AXPress"), "Selectt has press action");
+ // These four events happen in quick succession when select is pressed
+ let events = Promise.all([
+ waitForMacEvent("AXMenuOpened"),
+ waitForMacEvent("AXSelectedChildrenChanged"),
+ waitForMacEvent(
+ "AXFocusedUIElementChanged",
+ e => e.getAttributeValue("AXRole") == "AXPopUpButton"
+ ),
+ waitForMacEvent(
+ "AXFocusedUIElementChanged",
+ e => e.getAttributeValue("AXRole") == "AXMenuItem"
+ ),
+ ]);
+ select.performAction("AXPress");
+ // Only capture the target of AXMenuOpened (first element)
+ let [menu] = await events;
+
+ is(menu.getAttributeValue("AXRole"), "AXMenu", "dropdown has AXMenu role");
+ is(
+ menu.getAttributeValue("AXSelectedChildren").length,
+ 1,
+ "dropdown has single selected child"
+ );
+
+ let selectedChildren = menu.getAttributeValue("AXSelectedChildren");
+ is(selectedChildren.length, 1, "Only one child is selected");
+ is(selectedChildren[0].getAttributeValue("AXRole"), "AXMenuItem");
+ is(selectedChildren[0].getAttributeValue("AXTitle"), "One");
+
+ let menuParent = menu.getAttributeValue("AXParent");
+ is(
+ menuParent.getAttributeValue("AXRole"),
+ "AXPopUpButton",
+ "dropdown parent is a popup button"
+ );
+
+ let menuItems = menu.getAttributeValue("AXChildren").map(c => {
+ return [
+ c.getAttributeValue("AXMenuItemMarkChar"),
+ c.getAttributeValue("AXRole"),
+ c.getAttributeValue("AXTitle"),
+ c.getAttributeValue("AXEnabled"),
+ ];
+ });
+
+ Assert.deepEqual(
+ menuItems,
+ [
+ ["✓", "AXMenuItem", "One", true],
+ [null, "AXMenuItem", "Two", true],
+ [null, "AXMenuItem", "Three", true],
+ [null, "AXMenuItem", "Four", false],
+ ],
+ "Menu items have correct checkmark on current value, correctt roles, correct titles, and correct AXEnabled value"
+ );
+
+ events = Promise.all([
+ waitForMacEvent("AXSelectedChildrenChanged"),
+ waitForMacEvent("AXFocusedUIElementChanged"),
+ ]);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ let [, menuItem] = await events;
+ is(
+ menuItem.getAttributeValue("AXTitle"),
+ "Two",
+ "Focused menu item has correct title"
+ );
+
+ selectedChildren = menu.getAttributeValue("AXSelectedChildren");
+ is(selectedChildren.length, 1, "Only one child is selected");
+ is(
+ selectedChildren[0].getAttributeValue("AXTitle"),
+ "Two",
+ "Selected child matches focused item"
+ );
+
+ events = Promise.all([
+ waitForMacEvent("AXSelectedChildrenChanged"),
+ waitForMacEvent("AXFocusedUIElementChanged"),
+ ]);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ [, menuItem] = await events;
+ is(
+ menuItem.getAttributeValue("AXTitle"),
+ "Three",
+ "Focused menu item has correct title"
+ );
+
+ selectedChildren = menu.getAttributeValue("AXSelectedChildren");
+ is(selectedChildren.length, 1, "Only one child is selected");
+ is(
+ selectedChildren[0].getAttributeValue("AXTitle"),
+ "Three",
+ "Selected child matches focused item"
+ );
+
+ events = Promise.all([
+ waitForMacEvent("AXMenuClosed"),
+ waitForMacEvent("AXFocusedUIElementChanged"),
+ waitForMacEvent("AXSelectedChildrenChanged"),
+ ]);
+ menuItem.performAction("AXPress");
+ let [, newFocus] = await events;
+ is(
+ newFocus.getAttributeValue("AXRole"),
+ "AXPopUpButton",
+ "Newly focused element is AXPopupButton"
+ );
+ is(
+ newFocus.getAttributeValue("AXDOMIdentifier"),
+ "select",
+ "Should return focus to select"
+ );
+ is(
+ newFocus.getAttributeValue("AXValue"),
+ "Three",
+ "select has correct new value"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_radio_position.js b/accessible/tests/browser/mac/browser_radio_position.js
new file mode 100644
index 0000000000..76f518a91e
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_radio_position.js
@@ -0,0 +1,321 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+function getChildRoles(parent) {
+ return parent
+ .getAttributeValue("AXChildren")
+ .map(c => c.getAttributeValue("AXRole"));
+}
+
+function getLinkedTitles(element) {
+ return element
+ .getAttributeValue("AXLinkedUIElements")
+ .map(c => c.getAttributeValue("AXTitle"));
+}
+
+/**
+ * Test radio group
+ */
+addAccessibleTask(
+ `<div role="radiogroup" id="radioGroup">
+ <div role="radio"
+ id="radioGroupItem1">
+ Regular crust
+ </div>
+ <div role="radio"
+ id="radioGroupItem2">
+ Deep dish
+ </div>
+ <div role="radio"
+ id="radioGroupItem3">
+ Thin crust
+ </div>
+ </div>`,
+ async (browser, accDoc) => {
+ let item1 = getNativeInterface(accDoc, "radioGroupItem1");
+ let item2 = getNativeInterface(accDoc, "radioGroupItem2");
+ let item3 = getNativeInterface(accDoc, "radioGroupItem3");
+ let titleList = ["Regular crust", "Deep dish", "Thin crust"];
+
+ Assert.deepEqual(
+ titleList,
+ [item1, item2, item3].map(c => c.getAttributeValue("AXTitle")),
+ "Title list matches"
+ );
+
+ let linkedElems = item1.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 3, "Item 1 has three linked UI elems");
+ Assert.deepEqual(
+ getLinkedTitles(item1),
+ titleList,
+ "Item one has correctly ordered linked elements"
+ );
+
+ linkedElems = item2.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 3, "Item 2 has three linked UI elems");
+ Assert.deepEqual(
+ getLinkedTitles(item2),
+ titleList,
+ "Item two has correctly ordered linked elements"
+ );
+
+ linkedElems = item3.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 3, "Item 3 has three linked UI elems");
+ Assert.deepEqual(
+ getLinkedTitles(item3),
+ titleList,
+ "Item three has correctly ordered linked elements"
+ );
+ }
+);
+
+/**
+ * Test dynamic add to a radio group
+ */
+addAccessibleTask(
+ `<div role="radiogroup" id="radioGroup">
+ <div role="radio"
+ id="radioGroupItem1">
+ Option One
+ </div>
+ </div>`,
+ async (browser, accDoc) => {
+ let item1 = getNativeInterface(accDoc, "radioGroupItem1");
+ let linkedElems = item1.getAttributeValue("AXLinkedUIElements");
+
+ is(linkedElems.length, 1, "Item 1 has one linked UI elem");
+ is(
+ linkedElems[0].getAttributeValue("AXTitle"),
+ item1.getAttributeValue("AXTitle"),
+ "Item 1 is first element"
+ );
+
+ let reorder = waitForEvent(EVENT_REORDER, "radioGroup");
+ await SpecialPowers.spawn(browser, [], () => {
+ let d = content.document.createElement("div");
+ d.setAttribute("role", "radio");
+ content.document.getElementById("radioGroup").appendChild(d);
+ });
+ await reorder;
+
+ let radioGroup = getNativeInterface(accDoc, "radioGroup");
+ let groupMembers = radioGroup.getAttributeValue("AXChildren");
+ is(groupMembers.length, 2, "Radio group has two members");
+ let item2 = groupMembers[1];
+ item1 = getNativeInterface(accDoc, "radioGroupItem1");
+ let titleList = ["Option One", ""];
+
+ Assert.deepEqual(
+ titleList,
+ [item1, item2].map(c => c.getAttributeValue("AXTitle")),
+ "Title list matches"
+ );
+
+ linkedElems = item1.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 2, "Item 1 has two linked UI elems");
+ Assert.deepEqual(
+ getLinkedTitles(item1),
+ titleList,
+ "Item one has correctly ordered linked elements"
+ );
+
+ linkedElems = item2.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 2, "Item 2 has two linked UI elems");
+ Assert.deepEqual(
+ getLinkedTitles(item2),
+ titleList,
+ "Item two has correctly ordered linked elements"
+ );
+ }
+);
+
+/**
+ * Test input[type=radio] for single group
+ */
+addAccessibleTask(
+ `<input type="radio" id="cat" name="animal"><label for="cat">Cat</label>
+ <input type="radio" id="dog" name="animal"><label for="dog">Dog</label>
+ <input type="radio" id="catdog" name="animal"><label for="catdog">CatDog</label>`,
+ async (browser, accDoc) => {
+ let cat = getNativeInterface(accDoc, "cat");
+ let dog = getNativeInterface(accDoc, "dog");
+ let catdog = getNativeInterface(accDoc, "catdog");
+ let titleList = ["Cat", "Dog", "CatDog"];
+
+ Assert.deepEqual(
+ titleList,
+ [cat, dog, catdog].map(x => x.getAttributeValue("AXTitle")),
+ "Title list matches"
+ );
+
+ let linkedElems = cat.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 3, "Cat has three linked UI elems");
+ Assert.deepEqual(
+ getLinkedTitles(cat),
+ titleList,
+ "Cat has correctly ordered linked elements"
+ );
+
+ linkedElems = dog.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 3, "Dog has three linked UI elems");
+ Assert.deepEqual(
+ getLinkedTitles(dog),
+ titleList,
+ "Dog has correctly ordered linked elements"
+ );
+
+ linkedElems = catdog.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 3, "Catdog has three linked UI elems");
+ Assert.deepEqual(
+ getLinkedTitles(catdog),
+ titleList,
+ "catdog has correctly ordered linked elements"
+ );
+ }
+);
+
+/**
+ * Test input[type=radio] for different groups
+ */
+addAccessibleTask(
+ `<input type="radio" id="cat" name="one"><label for="cat">Cat</label>
+ <input type="radio" id="dog" name="two"><label for="dog">Dog</label>
+ <input type="radio" id="catdog"><label for="catdog">CatDog</label>`,
+ async (browser, accDoc) => {
+ let cat = getNativeInterface(accDoc, "cat");
+ let dog = getNativeInterface(accDoc, "dog");
+ let catdog = getNativeInterface(accDoc, "catdog");
+
+ let linkedElems = cat.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 1, "Cat has one linked UI elem");
+ is(
+ linkedElems[0].getAttributeValue("AXTitle"),
+ cat.getAttributeValue("AXTitle"),
+ "Cat is only element"
+ );
+
+ linkedElems = dog.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 1, "Dog has one linked UI elem");
+ is(
+ linkedElems[0].getAttributeValue("AXTitle"),
+ dog.getAttributeValue("AXTitle"),
+ "Dog is only element"
+ );
+
+ linkedElems = catdog.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 0, "Catdog has no linked UI elem");
+ }
+);
+
+/**
+ * Test input[type=radio] for single group across DOM
+ */
+addAccessibleTask(
+ `<input type="radio" id="cat" name="animal"><label for="cat">Cat</label>
+ <div>
+ <span>
+ <input type="radio" id="dog" name="animal"><label for="dog">Dog</label>
+ </span>
+ </div>
+ <div>
+ <input type="radio" id="catdog" name="animal"><label for="catdog">CatDog</label>
+ </div>`,
+ async (browser, accDoc) => {
+ let cat = getNativeInterface(accDoc, "cat");
+ let dog = getNativeInterface(accDoc, "dog");
+ let catdog = getNativeInterface(accDoc, "catdog");
+ let titleList = ["Cat", "Dog", "CatDog"];
+
+ Assert.deepEqual(
+ titleList,
+ [cat, dog, catdog].map(x => x.getAttributeValue("AXTitle")),
+ "Title list matches"
+ );
+
+ let linkedElems = cat.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 3, "Cat has three linked UI elems");
+ Assert.deepEqual(
+ getLinkedTitles(cat),
+ titleList,
+ "cat has correctly ordered linked elements"
+ );
+
+ linkedElems = dog.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 3, "Dog has three linked UI elems");
+ Assert.deepEqual(
+ getLinkedTitles(dog),
+ titleList,
+ "dog has correctly ordered linked elements"
+ );
+
+ linkedElems = catdog.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 3, "Catdog has three linked UI elems");
+ Assert.deepEqual(
+ getLinkedTitles(catdog),
+ titleList,
+ "catdog has correctly ordered linked elements"
+ );
+ }
+);
+
+/**
+ * Test dynamic add of input[type=radio] in a single group
+ */
+addAccessibleTask(
+ `<div id="container"><input type="radio" id="cat" name="animal"></div>`,
+ async (browser, accDoc) => {
+ let cat = getNativeInterface(accDoc, "cat");
+ let container = getNativeInterface(accDoc, "container");
+
+ let containerChildren = container.getAttributeValue("AXChildren");
+ is(containerChildren.length, 1, "container has one button");
+ is(
+ containerChildren[0].getAttributeValue("AXRole"),
+ "AXRadioButton",
+ "Container child is radio button"
+ );
+
+ let linkedElems = cat.getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 1, "Cat has 1 linked UI elem");
+ is(
+ linkedElems[0].getAttributeValue("AXTitle"),
+ cat.getAttributeValue("AXTitle"),
+ "Cat is first element"
+ );
+ let reorder = waitForEvent(EVENT_REORDER, "container");
+ await SpecialPowers.spawn(browser, [], () => {
+ let input = content.document.createElement("input");
+ input.setAttribute("type", "radio");
+ input.setAttribute("name", "animal");
+ content.document.getElementById("container").appendChild(input);
+ });
+ await reorder;
+
+ container = getNativeInterface(accDoc, "container");
+ containerChildren = container.getAttributeValue("AXChildren");
+
+ is(containerChildren.length, 2, "container has two children");
+
+ Assert.deepEqual(
+ getChildRoles(container),
+ ["AXRadioButton", "AXRadioButton"],
+ "Both children are radio buttons"
+ );
+
+ linkedElems = containerChildren[0].getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 2, "Cat has 2 linked elements");
+
+ linkedElems = containerChildren[1].getAttributeValue("AXLinkedUIElements");
+ is(linkedElems.length, 2, "New button has 2 linked elements");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_range.js b/accessible/tests/browser/mac/browser_range.js
new file mode 100644
index 0000000000..430e41d6ea
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_range.js
@@ -0,0 +1,190 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Verify that the value of a slider input can be incremented/decremented
+ * Test input[type=range]
+ */
+addAccessibleTask(
+ `<input id="range" type="range" min="1" max="100" value="1" step="10">`,
+ async (browser, accDoc) => {
+ let range = getNativeInterface(accDoc, "range");
+ is(range.getAttributeValue("AXRole"), "AXSlider", "Correct AXSlider role");
+ is(range.getAttributeValue("AXValue"), 1, "Correct initial value");
+
+ let actions = range.actionNames;
+ ok(actions.includes("AXDecrement"), "Has decrement action");
+ ok(actions.includes("AXIncrement"), "Has increment action");
+
+ let evt = waitForMacEvent("AXValueChanged");
+ range.performAction("AXIncrement");
+ await evt;
+ is(range.getAttributeValue("AXValue"), 11, "Correct increment value");
+
+ evt = waitForMacEvent("AXValueChanged");
+ range.performAction("AXDecrement");
+ await evt;
+ is(range.getAttributeValue("AXValue"), 1, "Correct decrement value");
+
+ evt = waitForMacEvent("AXValueChanged");
+ // Adjust value via script in content
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("range").value = 41;
+ });
+ await evt;
+ is(
+ range.getAttributeValue("AXValue"),
+ 41,
+ "Correct value from content change"
+ );
+ }
+);
+
+/**
+ * Verify that the value of a slider input can be set directly
+ * Test input[type=range]
+ */
+addAccessibleTask(
+ `<input id="range" type="range" min="1" max="100" value="1" step="10">`,
+ async (browser, accDoc) => {
+ let nextValue = 21;
+ let range = getNativeInterface(accDoc, "range");
+ is(range.getAttributeValue("AXRole"), "AXSlider", "Correct AXSlider role");
+ is(range.getAttributeValue("AXValue"), 1, "Correct initial value");
+
+ ok(range.isAttributeSettable("AXValue"), "Range AXValue is settable.");
+
+ let evt = waitForMacEvent("AXValueChanged");
+ range.setAttributeValue("AXValue", nextValue);
+ await evt;
+ is(range.getAttributeValue("AXValue"), nextValue, "Correct updated value");
+ }
+);
+
+/**
+ * Verify that the value of a number input can be incremented/decremented
+ * Test input[type=number]
+ */
+addAccessibleTask(
+ `<input type="number" value="11" id="number" step=".05">`,
+ async (browser, accDoc) => {
+ let number = getNativeInterface(accDoc, "number");
+ is(
+ number.getAttributeValue("AXRole"),
+ "AXIncrementor",
+ "Correct AXIncrementor role"
+ );
+ is(number.getAttributeValue("AXValue"), 11, "Correct initial value");
+
+ let actions = number.actionNames;
+ ok(actions.includes("AXDecrement"), "Has decrement action");
+ ok(actions.includes("AXIncrement"), "Has increment action");
+
+ let evt = waitForMacEvent("AXValueChanged");
+ number.performAction("AXIncrement");
+ await evt;
+ is(number.getAttributeValue("AXValue"), 11.05, "Correct increment value");
+
+ evt = waitForMacEvent("AXValueChanged");
+ number.performAction("AXDecrement");
+ await evt;
+ is(number.getAttributeValue("AXValue"), 11, "Correct decrement value");
+
+ evt = waitForMacEvent("AXValueChanged");
+ // Adjust value via script in content
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("number").value = 42;
+ });
+ await evt;
+ is(
+ number.getAttributeValue("AXValue"),
+ 42,
+ "Correct value from content change"
+ );
+ }
+);
+
+/**
+ * Test Min, Max, Orientation, ValueDescription
+ */
+addAccessibleTask(
+ `<input type="number" value="11" id="number">`,
+ async (browser, accDoc) => {
+ let nextValue = 21;
+ let number = getNativeInterface(accDoc, "number");
+ is(
+ number.getAttributeValue("AXRole"),
+ "AXIncrementor",
+ "Correct AXIncrementor role"
+ );
+ is(number.getAttributeValue("AXValue"), 11, "Correct initial value");
+
+ ok(number.isAttributeSettable("AXValue"), "Range AXValue is settable.");
+
+ let evt = waitForMacEvent("AXValueChanged");
+ number.setAttributeValue("AXValue", nextValue);
+ await evt;
+ is(number.getAttributeValue("AXValue"), nextValue, "Correct updated value");
+ }
+);
+
+/**
+ * Verify that the value of a number input can be set directly
+ * Test input[type=number]
+ */
+addAccessibleTask(
+ `<div aria-valuetext="High" id="slider" aria-orientation="horizontal" role="slider" aria-valuenow="2" aria-valuemin="0" aria-valuemax="3"></div>`,
+ async (browser, accDoc) => {
+ let slider = getNativeInterface(accDoc, "slider");
+ is(
+ slider.getAttributeValue("AXValueDescription"),
+ "High",
+ "Correct value description"
+ );
+ is(
+ slider.getAttributeValue("AXOrientation"),
+ "AXHorizontalOrientation",
+ "Correct orientation"
+ );
+ is(slider.getAttributeValue("AXMinValue"), 0, "Correct min value");
+ is(slider.getAttributeValue("AXMaxValue"), 3, "Correct max value");
+
+ let evt = waitForMacEvent("AXValueChanged");
+ await invokeContentTask(browser, [], () => {
+ const s = content.document.getElementById("slider");
+ s.setAttribute("aria-valuetext", "Low");
+ });
+ await evt;
+ is(
+ slider.getAttributeValue("AXValueDescription"),
+ "Low",
+ "Correct value description"
+ );
+
+ evt = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, "slider");
+ await invokeContentTask(browser, [], () => {
+ const s = content.document.getElementById("slider");
+ s.setAttribute("aria-orientation", "vertical");
+ s.setAttribute("aria-valuemin", "-1");
+ s.setAttribute("aria-valuemax", "5");
+ });
+ await evt;
+ is(
+ slider.getAttributeValue("AXOrientation"),
+ "AXVerticalOrientation",
+ "Correct orientation"
+ );
+ is(slider.getAttributeValue("AXMinValue"), -1, "Correct min value");
+ is(slider.getAttributeValue("AXMaxValue"), 5, "Correct max value");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_required.js b/accessible/tests/browser/mac/browser_required.js
new file mode 100644
index 0000000000..2109d265ab
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_required.js
@@ -0,0 +1,175 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test required and aria-required attributes on checkboxes
+ * and radio buttons.
+ */
+addAccessibleTask(
+ `
+ <form>
+ <input type="checkbox" id="checkbox" required>
+ <br>
+ <input type="radio" id="radio" required>
+ <br>
+ <input type="checkbox" id="ariaCheckbox" aria-required="true">
+ <br>
+ <input type="radio" id="ariaRadio" aria-required="true">
+ </form>
+ `,
+ async (browser, accDoc) => {
+ // Check initial AXRequired values are correct
+ let radio = getNativeInterface(accDoc, "radio");
+ is(
+ radio.getAttributeValue("AXRequired"),
+ 1,
+ "Correct required val for radio"
+ );
+
+ let ariaRadio = getNativeInterface(accDoc, "ariaRadio");
+ is(
+ ariaRadio.getAttributeValue("AXRequired"),
+ 1,
+ "Correct required val for ariaRadio"
+ );
+
+ let checkbox = getNativeInterface(accDoc, "checkbox");
+ is(
+ checkbox.getAttributeValue("AXRequired"),
+ 1,
+ "Correct required val for checkbox"
+ );
+
+ let ariaCheckbox = getNativeInterface(accDoc, "ariaCheckbox");
+ is(
+ ariaCheckbox.getAttributeValue("AXRequired"),
+ 1,
+ "Correct required val for ariaCheckbox"
+ );
+
+ // Change aria-required, verify AXRequired is updated
+ let stateChanged = waitForEvent(EVENT_STATE_CHANGE, "ariaCheckbox");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("ariaCheckbox")
+ .setAttribute("aria-required", "false");
+ });
+ await stateChanged;
+
+ is(
+ ariaCheckbox.getAttributeValue("AXRequired"),
+ 0,
+ "Correct required after false set for ariaCheckbox"
+ );
+
+ // Change aria-required, verify AXRequired is updated
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "ariaCheckbox");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("ariaCheckbox")
+ .setAttribute("aria-required", "true");
+ });
+ await stateChanged;
+
+ is(
+ ariaCheckbox.getAttributeValue("AXRequired"),
+ 1,
+ "Correct required after true set for ariaCheckbox"
+ );
+
+ // Remove aria-required, verify AXRequired is updated
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "ariaCheckbox");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("ariaCheckbox")
+ .removeAttribute("aria-required");
+ });
+ await stateChanged;
+
+ is(
+ ariaCheckbox.getAttributeValue("AXRequired"),
+ 0,
+ "Correct required after removal for ariaCheckbox"
+ );
+
+ // Change aria-required, verify AXRequired is updated
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "ariaRadio");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("ariaRadio")
+ .setAttribute("aria-required", "false");
+ });
+ await stateChanged;
+
+ is(
+ ariaRadio.getAttributeValue("AXRequired"),
+ 0,
+ "Correct required after false set for ariaRadio"
+ );
+
+ // Change aria-required, verify AXRequired is updated
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "ariaRadio");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("ariaRadio")
+ .setAttribute("aria-required", "true");
+ });
+ await stateChanged;
+
+ is(
+ ariaRadio.getAttributeValue("AXRequired"),
+ 1,
+ "Correct required after true set for ariaRadio"
+ );
+
+ // Remove aria-required, verify AXRequired is updated
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "ariaRadio");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("ariaRadio")
+ .removeAttribute("aria-required");
+ });
+ await stateChanged;
+
+ is(
+ ariaRadio.getAttributeValue("AXRequired"),
+ 0,
+ "Correct required after removal for ariaRadio"
+ );
+
+ // Remove required, verify AXRequired is updated
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "checkbox");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("checkbox").removeAttribute("required");
+ });
+ await stateChanged;
+
+ is(
+ checkbox.getAttributeValue("AXRequired"),
+ 0,
+ "Correct required after removal for checkbox"
+ );
+
+ stateChanged = waitForEvent(EVENT_STATE_CHANGE, "radio");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("radio").removeAttribute("required");
+ });
+ await stateChanged;
+
+ is(
+ checkbox.getAttributeValue("AXRequired"),
+ 0,
+ "Correct required after removal for radio"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_rich_listbox.js b/accessible/tests/browser/mac/browser_rich_listbox.js
new file mode 100644
index 0000000000..97dd6785bb
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_rich_listbox.js
@@ -0,0 +1,73 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/attributes.js */
+loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
+
+addAccessibleTask(
+ "mac/doc_rich_listbox.xhtml",
+ async (browser, accDoc) => {
+ const categories = getNativeInterface(accDoc, "categories");
+ const categoriesChildren = categories.getAttributeValue("AXChildren");
+ is(categoriesChildren.length, 4, "Found listbox and 4 items");
+
+ const general = getNativeInterface(accDoc, "general");
+ is(
+ general.getAttributeValue("AXTitle"),
+ "general",
+ "general has appropriate title"
+ );
+ is(
+ categoriesChildren[0].getAttributeValue("AXTitle"),
+ general.getAttributeValue("AXTitle"),
+ "Found general listitem"
+ );
+ is(
+ general.getAttributeValue("AXEnabled"),
+ 1,
+ "general is enabled, not dimmed"
+ );
+
+ const home = getNativeInterface(accDoc, "home");
+ is(home.getAttributeValue("AXTitle"), "home", "home has appropriate title");
+ is(
+ categoriesChildren[1].getAttributeValue("AXTitle"),
+ home.getAttributeValue("AXTitle"),
+ "Found home listitem"
+ );
+ is(home.getAttributeValue("AXEnabled"), 1, "Home is enabled, not dimmed");
+
+ const search = getNativeInterface(accDoc, "search");
+ is(
+ search.getAttributeValue("AXTitle"),
+ "search",
+ "search has appropriate title"
+ );
+ is(
+ categoriesChildren[2].getAttributeValue("AXTitle"),
+ search.getAttributeValue("AXTitle"),
+ "Found search listitem"
+ );
+ is(
+ search.getAttributeValue("AXEnabled"),
+ 1,
+ "search is enabled, not dimmed"
+ );
+
+ const privacy = getNativeInterface(accDoc, "privacy");
+ is(
+ privacy.getAttributeValue("AXTitle"),
+ "privacy",
+ "privacy has appropriate title"
+ );
+ is(
+ categoriesChildren[3].getAttributeValue("AXTitle"),
+ privacy.getAttributeValue("AXTitle"),
+ "Found privacy listitem"
+ );
+ },
+ { topLevel: false, chrome: true }
+);
diff --git a/accessible/tests/browser/mac/browser_roles_elements.js b/accessible/tests/browser/mac/browser_roles_elements.js
new file mode 100644
index 0000000000..be9b27367e
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_roles_elements.js
@@ -0,0 +1,334 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test different HTML elements for their roles and subroles
+ */
+function testRoleAndSubRole(accDoc, id, axRole, axSubRole, axRoleDescription) {
+ let el = getNativeInterface(accDoc, id);
+ if (axRole) {
+ is(
+ el.getAttributeValue("AXRole"),
+ axRole,
+ "AXRole for " + id + " is " + axRole
+ );
+ }
+ if (axSubRole) {
+ is(
+ el.getAttributeValue("AXSubrole"),
+ axSubRole,
+ "Subrole for " + id + " is " + axSubRole
+ );
+ }
+ if (axRoleDescription) {
+ is(
+ el.getAttributeValue("AXRoleDescription"),
+ axRoleDescription,
+ "Subrole for " + id + " is " + axRoleDescription
+ );
+ }
+}
+
+addAccessibleTask(
+ `
+ <!-- WAI-ARIA landmark roles -->
+ <div id="application" role="application"></div>
+ <div id="banner" role="banner"></div>
+ <div id="complementary" role="complementary"></div>
+ <div id="contentinfo" role="contentinfo"></div>
+ <div id="form" role="form"></div>
+ <div id="main" role="main"></div>
+ <div id="navigation" role="navigation"></div>
+ <div id="search" role="search"></div>
+ <div id="searchbox" role="searchbox"></div>
+
+ <!-- DPub landmarks -->
+ <div id="dPubNavigation" role="doc-index"></div>
+ <div id="dPubRegion" role="doc-introduction"></div>
+
+ <!-- Other WAI-ARIA widget roles -->
+ <div id="alert" role="alert"></div>
+ <div id="alertdialog" role="alertdialog"></div>
+ <div id="article" role="article"></div>
+ <div id="code" role="code"></div>
+ <div id="dialog" role="dialog"></div>
+ <div id="ariaDocument" role="document"></div>
+ <div id="log" role="log"></div>
+ <div id="marquee" role="marquee"></div>
+ <div id="ariaMath" role="math"></div>
+ <div id="note" role="note"></div>
+ <div id="ariaRegion" aria-label="region" role="region"></div>
+ <div id="ariaStatus" role="status"></div>
+ <div id="switch" role="switch"></div>
+ <div id="timer" role="timer"></div>
+ <div id="tooltip" role="tooltip"></div>
+ <input type="radio" role="menuitemradio" id="menuitemradio">
+ <input type="checkbox" role="menuitemcheckbox" id="menuitemcheckbox">
+ <input type="datetime-local" id="datetime">
+
+ <!-- text entries -->
+ <div id="textbox_multiline" role="textbox" aria-multiline="true"></div>
+ <div id="textbox_singleline" role="textbox" aria-multiline="false"></div>
+ <textarea id="textArea"></textarea>
+ <input id="textInput">
+
+ <!-- True HTML5 search box -->
+ <input type="search" id="htmlSearch" />
+
+ <!-- A button morphed into a toggle via ARIA -->
+ <button id="toggle" aria-pressed="false"></button>
+
+ <!-- A button with a 'banana' role description -->
+ <button id="banana" aria-roledescription="banana"></button>
+
+ <!-- Other elements -->
+ <del id="deletion">Deleted text</del>
+ <dl id="dl"><dt id="dt">term</dt><dd id="dd">definition</dd></dl>
+ <hr id="hr" />
+ <ins id="insertion">Inserted text</ins>
+ <meter id="meter" min="0" max="100" value="24">meter text here</meter>
+ <sub id="sub">sub text here</sub>
+ <sup id="sup">sup text here</sup>
+
+ <!-- Some SVG stuff -->
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g id="g">
+ <title>g</title>
+ </g>
+ <rect width="300" height="100" id="rect"
+ style="fill:rgb(0,0,255);stroke-width:1;stroke:rgb(0,0,0)">
+ <title>rect</title>
+ </rect>
+ <circle cx="100" cy="50" r="40" stroke="black" id="circle"
+ stroke-width="2" fill="red">
+ <title>circle</title>
+ </circle>
+ <ellipse cx="300" cy="80" rx="100" ry="50" id="ellipse"
+ style="fill:yellow;stroke:purple;stroke-width:2">
+ <title>ellipse</title>
+ </ellipse>
+ <line x1="0" y1="0" x2="200" y2="200" id="line"
+ style="stroke:rgb(255,0,0);stroke-width:2">
+ <title>line</title>
+ </line>
+ <polygon points="200,10 250,190 160,210" id="polygon"
+ style="fill:lime;stroke:purple;stroke-width:1">
+ <title>polygon</title>
+ </polygon>
+ <polyline points="20,20 40,25 60,40 80,120 120,140 200,180" id="polyline"
+ style="fill:none;stroke:black;stroke-width:3" >
+ <title>polyline</title>
+ </polyline>
+ <path d="M150 0 L75 200 L225 200 Z" id="path">
+ <title>path</title>
+ </path>
+ <image x1="25" y1="80" width="50" height="20" id="image"
+ xlink:href="../moz.png">
+ <title>image</title>
+ </image>
+ </svg>`,
+ (browser, accDoc) => {
+ // WAI-ARIA landmark subroles, regardless of AXRole
+ testRoleAndSubRole(accDoc, "application", null, "AXLandmarkApplication");
+ testRoleAndSubRole(accDoc, "banner", null, "AXLandmarkBanner");
+ testRoleAndSubRole(
+ accDoc,
+ "complementary",
+ null,
+ "AXLandmarkComplementary"
+ );
+ testRoleAndSubRole(accDoc, "contentinfo", null, "AXLandmarkContentInfo");
+ testRoleAndSubRole(accDoc, "form", null, "AXLandmarkForm");
+ testRoleAndSubRole(accDoc, "main", null, "AXLandmarkMain");
+ testRoleAndSubRole(accDoc, "navigation", null, "AXLandmarkNavigation");
+ testRoleAndSubRole(accDoc, "search", null, "AXLandmarkSearch");
+ testRoleAndSubRole(accDoc, "searchbox", null, "AXSearchField");
+
+ // DPub roles map into two categories, sample one of each
+ testRoleAndSubRole(
+ accDoc,
+ "dPubNavigation",
+ "AXGroup",
+ "AXLandmarkNavigation"
+ );
+ testRoleAndSubRole(accDoc, "dPubRegion", "AXGroup", "AXLandmarkRegion");
+
+ // ARIA widget roles
+ testRoleAndSubRole(accDoc, "alert", null, "AXApplicationAlert");
+ testRoleAndSubRole(
+ accDoc,
+ "alertdialog",
+ "AXGroup",
+ "AXApplicationAlertDialog",
+ "alert dialog"
+ );
+ testRoleAndSubRole(accDoc, "article", null, "AXDocumentArticle");
+ testRoleAndSubRole(accDoc, "code", "AXGroup", "AXCodeStyleGroup");
+ testRoleAndSubRole(accDoc, "dialog", null, "AXApplicationDialog", "dialog");
+ testRoleAndSubRole(accDoc, "ariaDocument", null, "AXDocument");
+ testRoleAndSubRole(accDoc, "log", null, "AXApplicationLog");
+ testRoleAndSubRole(accDoc, "marquee", null, "AXApplicationMarquee");
+ testRoleAndSubRole(accDoc, "ariaMath", null, "AXDocumentMath");
+ testRoleAndSubRole(accDoc, "note", null, "AXDocumentNote");
+ testRoleAndSubRole(accDoc, "ariaRegion", null, "AXLandmarkRegion");
+ testRoleAndSubRole(accDoc, "ariaStatus", "AXGroup", "AXApplicationStatus");
+ testRoleAndSubRole(accDoc, "switch", "AXCheckBox", "AXSwitch");
+ testRoleAndSubRole(accDoc, "timer", null, "AXApplicationTimer");
+ testRoleAndSubRole(accDoc, "tooltip", "AXGroup", "AXUserInterfaceTooltip");
+ testRoleAndSubRole(accDoc, "menuitemradio", "AXMenuItem", null);
+ testRoleAndSubRole(accDoc, "menuitemcheckbox", "AXMenuItem", null);
+ testRoleAndSubRole(accDoc, "datetime", "AXGroup", null);
+ // XXX for datetime elements, we spoof the role via the title, since
+ // providing the correct role results in the internal elements being
+ // unreachable by VO
+ is(
+ getNativeInterface(accDoc, "datetime").getAttributeValue("AXTitle"),
+ "date field"
+ );
+
+ // Text boxes
+ testRoleAndSubRole(accDoc, "textbox_multiline", "AXTextArea");
+ testRoleAndSubRole(accDoc, "textbox_singleline", "AXTextField");
+ testRoleAndSubRole(accDoc, "textArea", "AXTextArea");
+ testRoleAndSubRole(accDoc, "textInput", "AXTextField");
+
+ // True HTML5 search field
+ testRoleAndSubRole(accDoc, "htmlSearch", "AXTextField", "AXSearchField");
+
+ // A button morphed into a toggle by ARIA
+ testRoleAndSubRole(accDoc, "toggle", "AXCheckBox", "AXToggle");
+
+ // A banana button
+ testRoleAndSubRole(accDoc, "banana", "AXButton", null, "banana");
+
+ // Other elements
+ testRoleAndSubRole(accDoc, "deletion", "AXGroup", "AXDeleteStyleGroup");
+ testRoleAndSubRole(accDoc, "dl", "AXList", "AXDescriptionList");
+ testRoleAndSubRole(accDoc, "dt", "AXGroup", "AXTerm");
+ testRoleAndSubRole(accDoc, "dd", "AXGroup", "AXDescription");
+ testRoleAndSubRole(accDoc, "hr", "AXSplitter", "AXContentSeparator");
+ testRoleAndSubRole(accDoc, "insertion", "AXGroup", "AXInsertStyleGroup");
+ testRoleAndSubRole(
+ accDoc,
+ "meter",
+ "AXLevelIndicator",
+ null,
+ "level indicator"
+ );
+ testRoleAndSubRole(accDoc, "sub", "AXGroup", "AXSubscriptStyleGroup");
+ testRoleAndSubRole(accDoc, "sup", "AXGroup", "AXSuperscriptStyleGroup");
+
+ // Some SVG stuff
+ testRoleAndSubRole(accDoc, "svg", "AXImage");
+ testRoleAndSubRole(accDoc, "g", "AXGroup");
+ testRoleAndSubRole(accDoc, "rect", "AXImage");
+ testRoleAndSubRole(accDoc, "circle", "AXImage");
+ testRoleAndSubRole(accDoc, "ellipse", "AXImage");
+ testRoleAndSubRole(accDoc, "line", "AXImage");
+ testRoleAndSubRole(accDoc, "polygon", "AXImage");
+ testRoleAndSubRole(accDoc, "polyline", "AXImage");
+ testRoleAndSubRole(accDoc, "path", "AXImage");
+ testRoleAndSubRole(accDoc, "image", "AXImage");
+ }
+);
+
+addAccessibleTask(
+ `
+ <figure id="figure">
+ <img id="img" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" alt="Logo">
+ <p>Non-image figure content</p>
+ <figcaption id="figcaption">Old Mozilla logo</figcaption>
+ </figure>`,
+ (browser, accDoc) => {
+ let figure = getNativeInterface(accDoc, "figure");
+ ok(!figure.getAttributeValue("AXTitle"), "Figure should not have a title");
+ is(
+ figure.getAttributeValue("AXDescription"),
+ "Old Mozilla logo",
+ "Correct figure label"
+ );
+ is(figure.getAttributeValue("AXRole"), "AXGroup", "Correct figure role");
+ is(
+ figure.getAttributeValue("AXRoleDescription"),
+ "figure",
+ "Correct figure role description"
+ );
+
+ let img = getNativeInterface(accDoc, "img");
+ ok(!img.getAttributeValue("AXTitle"), "img should not have a title");
+ is(img.getAttributeValue("AXDescription"), "Logo", "Correct img label");
+ is(img.getAttributeValue("AXRole"), "AXImage", "Correct img role");
+ is(
+ img.getAttributeValue("AXRoleDescription"),
+ "image",
+ "Correct img role description"
+ );
+
+ let figcaption = getNativeInterface(accDoc, "figcaption");
+ ok(
+ !figcaption.getAttributeValue("AXTitle"),
+ "figcaption should not have a title"
+ );
+ ok(
+ !figcaption.getAttributeValue("AXDescription"),
+ "figcaption should not have a label"
+ );
+ is(
+ figcaption.getAttributeValue("AXRole"),
+ "AXGroup",
+ "Correct figcaption role"
+ );
+ is(
+ figcaption.getAttributeValue("AXRoleDescription"),
+ "group",
+ "Correct figcaption role description"
+ );
+ }
+);
+
+addAccessibleTask(`<button>hello world</button>`, async (browser, accDoc) => {
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "web area should be an AXWebArea"
+ );
+ ok(
+ !webArea.attributeNames.includes("AXSubrole"),
+ "AXWebArea should not have a subrole"
+ );
+
+ let roleChanged = waitForMacEvent("AXMozRoleChanged");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.setAttribute("role", "application");
+ });
+ await roleChanged;
+
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "web area should retain AXWebArea role"
+ );
+ ok(
+ !webArea.attributeNames.includes("AXSubrole"),
+ "AXWebArea should not have a subrole"
+ );
+
+ let rootGroup = webArea.getAttributeValue("AXChildren")[0];
+ is(rootGroup.getAttributeValue("AXRole"), "AXGroup");
+ is(rootGroup.getAttributeValue("AXSubrole"), "AXLandmarkApplication");
+});
diff --git a/accessible/tests/browser/mac/browser_rootgroup.js b/accessible/tests/browser/mac/browser_rootgroup.js
new file mode 100644
index 0000000000..a8f4297d64
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_rootgroup.js
@@ -0,0 +1,246 @@
+/* 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";
+
+/**
+ * Test document with no single group child
+ */
+addAccessibleTask(
+ `<p id="p1">hello</p><p>world</p>`,
+ async (browser, accDoc) => {
+ let doc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ let docChildren = doc.getAttributeValue("AXChildren");
+ is(docChildren.length, 1, "The document contains a root group");
+
+ let rootGroup = docChildren[0];
+ is(
+ rootGroup.getAttributeValue("AXIdentifier"),
+ "root-group",
+ "Is generated root group"
+ );
+
+ is(
+ rootGroup.getAttributeValue("AXChildren").length,
+ 2,
+ "Root group has two children"
+ );
+
+ // From bottom-up
+ let p1 = getNativeInterface(accDoc, "p1");
+ rootGroup = p1.getAttributeValue("AXParent");
+ is(
+ rootGroup.getAttributeValue("AXIdentifier"),
+ "root-group",
+ "Is generated root group"
+ );
+ }
+);
+
+/**
+ * Test document with a top-level group
+ */
+addAccessibleTask(
+ `<div role="grouping" id="group"><p>hello</p><p>world</p></div>`,
+ async (browser, accDoc) => {
+ let doc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ let docChildren = doc.getAttributeValue("AXChildren");
+ is(docChildren.length, 1, "The document contains a root group");
+
+ let rootGroup = docChildren[0];
+ is(
+ rootGroup.getAttributeValue("AXDOMIdentifier"),
+ "group",
+ "Root group is a document element"
+ );
+
+ // Adding an 'application' role to the body should
+ // create a root group with an application subrole.
+ let evt = waitForMacEvent("AXMozRoleChanged");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.setAttribute("role", "application");
+ });
+ await evt;
+
+ is(
+ doc.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "doc still has web area role"
+ );
+ is(
+ doc.getAttributeValue("AXRoleDescription"),
+ "HTML Content",
+ "doc has correct role description"
+ );
+ ok(
+ !doc.attributeNames.includes("AXSubrole"),
+ "sub role not available on web area"
+ );
+
+ rootGroup = doc.getAttributeValue("AXChildren")[0];
+ is(
+ rootGroup.getAttributeValue("AXIdentifier"),
+ "root-group",
+ "Is generated root group"
+ );
+ is(
+ rootGroup.getAttributeValue("AXRole"),
+ "AXGroup",
+ "root group has AXGroup role"
+ );
+ is(
+ rootGroup.getAttributeValue("AXSubrole"),
+ "AXLandmarkApplication",
+ "root group has application subrole"
+ );
+ is(
+ rootGroup.getAttributeValue("AXRoleDescription"),
+ "application",
+ "root group has application role description"
+ );
+ }
+);
+
+/**
+ * Test document with body[role=application] and a top-level group
+ */
+addAccessibleTask(
+ `<div role="grouping" id="group"><p>hello</p><p>world</p></div>`,
+ async (browser, accDoc) => {
+ let doc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ is(
+ doc.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "doc still has web area role"
+ );
+ is(
+ doc.getAttributeValue("AXRoleDescription"),
+ "HTML Content",
+ "doc has correct role description"
+ );
+ ok(
+ !doc.attributeNames.includes("AXSubrole"),
+ "sub role not available on web area"
+ );
+
+ let rootGroup = doc.getAttributeValue("AXChildren")[0];
+ is(
+ rootGroup.getAttributeValue("AXIdentifier"),
+ "root-group",
+ "Is generated root group"
+ );
+ is(
+ rootGroup.getAttributeValue("AXRole"),
+ "AXGroup",
+ "root group has AXGroup role"
+ );
+ is(
+ rootGroup.getAttributeValue("AXSubrole"),
+ "AXLandmarkApplication",
+ "root group has application subrole"
+ );
+ is(
+ rootGroup.getAttributeValue("AXRoleDescription"),
+ "application",
+ "root group has application role description"
+ );
+ },
+ { contentDocBodyAttrs: { role: "application" } }
+);
+
+/**
+ * Test document with a single button
+ */
+addAccessibleTask(
+ `<button id="button">I am a button</button>`,
+ async (browser, accDoc) => {
+ let doc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ let docChildren = doc.getAttributeValue("AXChildren");
+ is(docChildren.length, 1, "The document contains a root group");
+
+ let rootGroup = docChildren[0];
+ is(
+ rootGroup.getAttributeValue("AXIdentifier"),
+ "root-group",
+ "Is generated root group"
+ );
+
+ let rootGroupChildren = rootGroup.getAttributeValue("AXChildren");
+ is(rootGroupChildren.length, 1, "Root group has one children");
+
+ is(
+ rootGroupChildren[0].getAttributeValue("AXRole"),
+ "AXButton",
+ "Button is child of root group"
+ );
+
+ // From bottom-up
+ let button = getNativeInterface(accDoc, "button");
+ rootGroup = button.getAttributeValue("AXParent");
+ is(
+ rootGroup.getAttributeValue("AXIdentifier"),
+ "root-group",
+ "Is generated root group"
+ );
+ }
+);
+
+/**
+ * Test document with dialog role and heading
+ */
+addAccessibleTask(
+ `<body role="dialog" aria-labelledby="h">
+ <h1 id="h">
+ We're building a richer search experience
+ </h1>
+ </body>`,
+ async (browser, accDoc) => {
+ let doc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ let docChildren = doc.getAttributeValue("AXChildren");
+ is(docChildren.length, 1, "The document contains a root group");
+
+ let rootGroup = docChildren[0];
+ is(
+ rootGroup.getAttributeValue("AXIdentifier"),
+ "root-group",
+ "Is generated root group"
+ );
+
+ is(rootGroup.getAttributeValue("AXRole"), "AXGroup", "Inherits role");
+
+ is(
+ rootGroup.getAttributeValue("AXSubrole"),
+ "AXApplicationDialog",
+ "Inherits subrole"
+ );
+ let rootGroupChildren = rootGroup.getAttributeValue("AXChildren");
+ is(rootGroupChildren.length, 1, "Root group has one child");
+
+ is(
+ rootGroupChildren[0].getAttributeValue("AXRole"),
+ "AXHeading",
+ "Heading is child of root group"
+ );
+
+ // From bottom-up
+ let heading = getNativeInterface(accDoc, "h");
+ rootGroup = heading.getAttributeValue("AXParent");
+ is(
+ rootGroup.getAttributeValue("AXIdentifier"),
+ "root-group",
+ "Parent is generated root group"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_rotor.js b/accessible/tests/browser/mac/browser_rotor.js
new file mode 100644
index 0000000000..3f13506757
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_rotor.js
@@ -0,0 +1,1752 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/states.js */
+loadScripts({ name: "states.js", dir: MOCHITESTS_DIR });
+
+ChromeUtils.defineESModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+});
+
+/**
+ * Test rotor with heading
+ */
+addAccessibleTask(
+ `<h1 id="hello">hello</h1><br><h2 id="world">world</h2><br>goodbye`,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXHeadingSearchKey",
+ AXImmediateDescendantsOnly: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const headingCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(2, headingCount, "Found two headings");
+
+ const headings = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ const hello = getNativeInterface(accDoc, "hello");
+ const world = getNativeInterface(accDoc, "world");
+ is(
+ hello.getAttributeValue("AXTitle"),
+ headings[0].getAttributeValue("AXTitle"),
+ "Found correct first heading"
+ );
+ is(
+ world.getAttributeValue("AXTitle"),
+ headings[1].getAttributeValue("AXTitle"),
+ "Found correct second heading"
+ );
+ }
+);
+
+/**
+ * Test rotor with heading and empty search text
+ */
+addAccessibleTask(
+ `<h1 id="hello">hello</h1><br><h2 id="world">world</h2><br>goodbye`,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXHeadingSearchKey",
+ AXImmediateDescendantsOnly: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ AXSearchText: "",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const headingCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(headingCount, 2, "Found two headings");
+
+ const headings = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ const hello = getNativeInterface(accDoc, "hello");
+ const world = getNativeInterface(accDoc, "world");
+ is(
+ headings[0].getAttributeValue("AXTitle"),
+ hello.getAttributeValue("AXTitle"),
+ "Found correct first heading"
+ );
+ is(
+ headings[1].getAttributeValue("AXTitle"),
+ world.getAttributeValue("AXTitle"),
+ "Found correct second heading"
+ );
+ }
+);
+
+/**
+ * Test rotor with articles
+ */
+addAccessibleTask(
+ `<article id="google">
+ <h2>Google Chrome</h2>
+ <p>Google Chrome is a web browser developed by Google, released in 2008. Chrome is the world's most popular web browser today!</p>
+ </article>
+
+ <article id="moz">
+ <h2>Mozilla Firefox</h2>
+ <p>Mozilla Firefox is an open-source web browser developed by Mozilla. Firefox has been the second most popular web browser since January, 2018.</p>
+ </article>
+
+ <article id="microsoft">
+ <h2>Microsoft Edge</h2>
+ <p>Microsoft Edge is a web browser developed by Microsoft, released in 2015. Microsoft Edge replaced Internet Explorer.</p>
+ </article> `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXArticleSearchKey",
+ AXImmediateDescendantsOnly: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const articleCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(3, articleCount, "Found three articles");
+
+ const articles = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ const google = getNativeInterface(accDoc, "google");
+ const moz = getNativeInterface(accDoc, "moz");
+ const microsoft = getNativeInterface(accDoc, "microsoft");
+
+ is(
+ google.getAttributeValue("AXTitle"),
+ articles[0].getAttributeValue("AXTitle"),
+ "Found correct first article"
+ );
+ is(
+ moz.getAttributeValue("AXTitle"),
+ articles[1].getAttributeValue("AXTitle"),
+ "Found correct second article"
+ );
+ is(
+ microsoft.getAttributeValue("AXTitle"),
+ articles[2].getAttributeValue("AXTitle"),
+ "Found correct third article"
+ );
+ }
+);
+
+/**
+ * Test rotor with tables
+ */
+addAccessibleTask(
+ `
+ <table id="shapes">
+ <tr>
+ <th>Shape</th>
+ <th>Color</th>
+ <th>Do I like it?</th>
+ </tr>
+ <tr>
+ <td>Triangle</td>
+ <td>Green</td>
+ <td>No</td>
+ </tr>
+ <tr>
+ <td>Square</td>
+ <td>Red</td>
+ <td>Yes</td>
+ </tr>
+ </table>
+ <br>
+ <table id="food">
+ <tr>
+ <th>Grocery Item</th>
+ <th>Quantity</th>
+ </tr>
+ <tr>
+ <td>Onions</td>
+ <td>2</td>
+ </tr>
+ <tr>
+ <td>Yogurt</td>
+ <td>1</td>
+ </tr>
+ <tr>
+ <td>Spinach</td>
+ <td>1</td>
+ </tr>
+ <tr>
+ <td>Cherries</td>
+ <td>12</td>
+ </tr>
+ <tr>
+ <td>Carrots</td>
+ <td>5</td>
+ </tr>
+ </table>
+ <br>
+ <div role="table" id="ariaTable">
+ <div role="row">
+ <div role="cell">
+ I am a tiny aria table
+ </div>
+ </div>
+ </div>
+ <br>
+ <table role="grid" id="grid">
+ <tr>
+ <th>A</th>
+ <th>B</th>
+ <th>C</th>
+ <th>D</th>
+ <th>E</th>
+ </tr>
+ <tr>
+ <th>F</th>
+ <th>G</th>
+ <th>H</th>
+ <th>I</th>
+ <th>J</th>
+ </tr>
+ </table>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXTableSearchKey",
+ AXImmediateDescendantsOnly: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const tableCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(4, tableCount, "Found four tables");
+
+ const tables = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ const shapes = getNativeInterface(accDoc, "shapes");
+ const food = getNativeInterface(accDoc, "food");
+ const ariaTable = getNativeInterface(accDoc, "ariaTable");
+ const grid = getNativeInterface(accDoc, "grid");
+
+ is(
+ shapes.getAttributeValue("AXColumnCount"),
+ tables[0].getAttributeValue("AXColumnCount"),
+ "Found correct first table"
+ );
+ is(
+ food.getAttributeValue("AXColumnCount"),
+ tables[1].getAttributeValue("AXColumnCount"),
+ "Found correct second table"
+ );
+ is(
+ ariaTable.getAttributeValue("AXColumnCount"),
+ tables[2].getAttributeValue("AXColumnCount"),
+ "Found correct third table"
+ );
+ is(
+ grid.getAttributeValue("AXColumnCount"),
+ tables[3].getAttributeValue("AXColumnCount"),
+ "Found correct fourth table"
+ );
+ }
+);
+
+/**
+ * Test rotor with landmarks
+ */
+addAccessibleTask(
+ `
+ <header id="header">
+ <h1>This is a heading within a header</h1>
+ </header>
+
+ <nav id="nav">
+ <a href="example.com">I am a link in a nav</a>
+ </nav>
+
+ <main id="main">
+ I am some text in a main element
+ </main>
+
+ <footer id="footer">
+ <h2>Heading in footer</h2>
+ </footer>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXLandmarkSearchKey",
+ AXImmediateDescendantsOnly: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const landmarkCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(4, landmarkCount, "Found four landmarks");
+
+ const landmarks = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ const header = getNativeInterface(accDoc, "header");
+ const nav = getNativeInterface(accDoc, "nav");
+ const main = getNativeInterface(accDoc, "main");
+ const footer = getNativeInterface(accDoc, "footer");
+
+ is(
+ header.getAttributeValue("AXSubrole"),
+ landmarks[0].getAttributeValue("AXSubrole"),
+ "Found correct first landmark"
+ );
+ is(
+ nav.getAttributeValue("AXSubrole"),
+ landmarks[1].getAttributeValue("AXSubrole"),
+ "Found correct second landmark"
+ );
+ is(
+ main.getAttributeValue("AXSubrole"),
+ landmarks[2].getAttributeValue("AXSubrole"),
+ "Found correct third landmark"
+ );
+ is(
+ footer.getAttributeValue("AXSubrole"),
+ landmarks[3].getAttributeValue("AXSubrole"),
+ "Found correct fourth landmark"
+ );
+ }
+);
+
+/**
+ * Test rotor with aria landmarks
+ */
+addAccessibleTask(
+ `
+ <div id="banner" role="banner">
+ <h1>This is a heading within a banner</h1>
+ </div>
+
+ <div id="nav" role="navigation">
+ <a href="example.com">I am a link in a nav</a>
+ </div>
+
+ <div id="main" role="main">
+ I am some text in a main element
+ </div>
+
+ <div id="contentinfo" role="contentinfo">
+ <h2>Heading in contentinfo</h2>
+ </div>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXLandmarkSearchKey",
+ AXImmediateDescendantsOnly: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const landmarkCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(4, landmarkCount, "Found four landmarks");
+
+ const landmarks = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ const banner = getNativeInterface(accDoc, "banner");
+ const nav = getNativeInterface(accDoc, "nav");
+ const main = getNativeInterface(accDoc, "main");
+ const contentinfo = getNativeInterface(accDoc, "contentinfo");
+
+ is(
+ banner.getAttributeValue("AXSubrole"),
+ landmarks[0].getAttributeValue("AXSubrole"),
+ "Found correct first landmark"
+ );
+ is(
+ nav.getAttributeValue("AXSubrole"),
+ landmarks[1].getAttributeValue("AXSubrole"),
+ "Found correct second landmark"
+ );
+ is(
+ main.getAttributeValue("AXSubrole"),
+ landmarks[2].getAttributeValue("AXSubrole"),
+ "Found correct third landmark"
+ );
+ is(
+ contentinfo.getAttributeValue("AXSubrole"),
+ landmarks[3].getAttributeValue("AXSubrole"),
+ "Found correct fourth landmark"
+ );
+ }
+);
+
+/**
+ * Test rotor with buttons
+ */
+addAccessibleTask(
+ `
+ <button id="button">hello world</button><br>
+
+ <input type="button" value="another kinda button" id="input"><br>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXButtonSearchKey",
+ AXImmediateDescendantsOnly: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const buttonCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(2, buttonCount, "Found two buttons");
+
+ const buttons = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ const button = getNativeInterface(accDoc, "button");
+ const input = getNativeInterface(accDoc, "input");
+
+ is(
+ button.getAttributeValue("AXRole"),
+ buttons[0].getAttributeValue("AXRole"),
+ "Found correct button"
+ );
+ is(
+ input.getAttributeValue("AXRole"),
+ buttons[1].getAttributeValue("AXRole"),
+ "Found correct input button"
+ );
+ }
+);
+
+/**
+ * Test rotor with heading
+ */
+addAccessibleTask(
+ `<h1 id="hello">hello</h1><br><h2 id="world">world</h2><br>goodbye`,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXHeadingSearchKey",
+ AXImmediateDescendants: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const headingCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(2, headingCount, "Found two headings");
+
+ const headings = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ const hello = getNativeInterface(accDoc, "hello");
+ const world = getNativeInterface(accDoc, "world");
+ is(
+ hello.getAttributeValue("AXTitle"),
+ headings[0].getAttributeValue("AXTitle"),
+ "Found correct first heading"
+ );
+ is(
+ world.getAttributeValue("AXTitle"),
+ headings[1].getAttributeValue("AXTitle"),
+ "Found correct second heading"
+ );
+ }
+);
+
+/**
+ * Test rotor with buttons
+ */
+addAccessibleTask(
+ `
+ <form>
+ <h2>input[type=button]</h2>
+ <input type="button" value="apply" id="button1">
+
+ <h2>input[type=submit]</h2>
+ <input type="submit" value="submit now" id="submit">
+
+ <h2>input[type=image]</h2>
+ <input type="image" src="sample.jpg" alt="submit image" id="image">
+
+ <h2>input[type=reset]</h2>
+ <input type="reset" value="reset now" id="reset">
+
+ <h2>button element</h2>
+ <button id="button2">Submit button</button>
+ </form>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXControlSearchKey",
+ AXImmediateDescendants: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const controlsCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(5, controlsCount, "Found 5 controls");
+
+ const controls = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ const button1 = getNativeInterface(accDoc, "button1");
+ const submit = getNativeInterface(accDoc, "submit");
+ const image = getNativeInterface(accDoc, "image");
+ const reset = getNativeInterface(accDoc, "reset");
+ const button2 = getNativeInterface(accDoc, "button2");
+
+ is(
+ button1.getAttributeValue("AXTitle"),
+ controls[0].getAttributeValue("AXTitle"),
+ "Found correct first control"
+ );
+ is(
+ submit.getAttributeValue("AXTitle"),
+ controls[1].getAttributeValue("AXTitle"),
+ "Found correct second control"
+ );
+ is(
+ image.getAttributeValue("AXTitle"),
+ controls[2].getAttributeValue("AXTitle"),
+ "Found correct third control"
+ );
+ is(
+ reset.getAttributeValue("AXTitle"),
+ controls[3].getAttributeValue("AXTitle"),
+ "Found correct third control"
+ );
+ is(
+ button2.getAttributeValue("AXTitle"),
+ controls[4].getAttributeValue("AXTitle"),
+ "Found correct third control"
+ );
+ }
+);
+
+/**
+ * Test rotor with inputs
+ */
+addAccessibleTask(
+ `
+ <input type="text" value="I'm a text field." id="text"><br>
+ <input type="text" value="me too" id="implText"><br>
+ <textarea id="textarea">this is some text in a text area</textarea><br>
+ <input type="tel" value="0000000000" id="tel"><br>
+ <input type="url" value="https://example.com" id="url"><br>
+ <input type="email" value="hi@example.com" id="email"><br>
+ <input type="password" value="blah" id="password"><br>
+ <input type="month" value="2020-01" id="month"><br>
+ <input type="week" value="2020-W01" id="week"><br>
+ <input type="number" value="12" id="number"><br>
+ <input type="range" value="12" min="0" max="20" id="range"><br>
+ <input type="date" value="2020-01-01" id="date"><br>
+ <input type="time" value="10:10:10" id="time"><br>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXControlSearchKey",
+ AXImmediateDescendants: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const controlsCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(13, controlsCount, "Found 13 controls");
+ // the extra controls here come from our time control
+ // we can't filter out its internal buttons/incrementors
+ // like we do with the date entry because the time entry
+ // doesn't have its own specific role -- its just a grouping.
+
+ const controls = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const text = getNativeInterface(accDoc, "text");
+ const implText = getNativeInterface(accDoc, "implText");
+ const textarea = getNativeInterface(accDoc, "textarea");
+ const tel = getNativeInterface(accDoc, "tel");
+ const url = getNativeInterface(accDoc, "url");
+ const email = getNativeInterface(accDoc, "email");
+ const password = getNativeInterface(accDoc, "password");
+ const month = getNativeInterface(accDoc, "month");
+ const week = getNativeInterface(accDoc, "week");
+ const number = getNativeInterface(accDoc, "number");
+ const range = getNativeInterface(accDoc, "range");
+
+ const toCheck = [
+ text,
+ implText,
+ textarea,
+ tel,
+ url,
+ email,
+ password,
+ month,
+ week,
+ number,
+ range,
+ ];
+
+ for (let i = 0; i < toCheck.length; i++) {
+ is(
+ toCheck[i].getAttributeValue("AXValue"),
+ controls[i].getAttributeValue("AXValue"),
+ "Found correct input control"
+ );
+ }
+
+ const date = getNativeInterface(accDoc, "date");
+ const time = getNativeInterface(accDoc, "time");
+
+ is(
+ date.getAttributeValue("AXRole"),
+ controls[11].getAttributeValue("AXRole"),
+ "Found corrent date editor"
+ );
+
+ is(
+ time.getAttributeValue("AXRole"),
+ controls[12].getAttributeValue("AXRole"),
+ "Found corrent time editor"
+ );
+ }
+);
+
+/**
+ * Test rotor with groupings
+ */
+addAccessibleTask(
+ `
+ <fieldset>
+ <legend>Radios</legend>
+ <div role="radiogroup" id="radios">
+ <input id="radio1" type="radio" name="g1" checked="checked"> Radio 1
+ <input id="radio2" type="radio" name="g1"> Radio 2
+ </div>
+ </fieldset>
+
+ <fieldset id="checkboxes">
+ <legend>Checkboxes</legend>
+ <input id="checkbox1" type="checkbox" name="g2"> Checkbox 1
+ <input id="checkbox2" type="checkbox" name="g2" checked="checked">Checkbox 2
+ </fieldset>
+
+ <fieldset id="switches">
+ <legend>Switches</legend>
+ <input id="switch1" name="g3" role="switch" type="checkbox">Switch 1
+ <input checked="checked" id="switch2" name="g3" role="switch" type="checkbox">Switch 2
+ </fieldset>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXControlSearchKey",
+ AXImmediateDescendants: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const controlsCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(9, controlsCount, "Found 9 controls");
+
+ const controls = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const radios = getNativeInterface(accDoc, "radios");
+ const radio1 = getNativeInterface(accDoc, "radio1");
+ const radio2 = getNativeInterface(accDoc, "radio2");
+
+ is(
+ radios.getAttributeValue("AXRole"),
+ controls[0].getAttributeValue("AXRole"),
+ "Found correct group of radios"
+ );
+ is(
+ radio1.getAttributeValue("AXRole"),
+ controls[1].getAttributeValue("AXRole"),
+ "Found correct radio 1"
+ );
+ is(
+ radio2.getAttributeValue("AXRole"),
+ controls[2].getAttributeValue("AXRole"),
+ "Found correct radio 2"
+ );
+
+ const checkboxes = getNativeInterface(accDoc, "checkboxes");
+ const checkbox1 = getNativeInterface(accDoc, "checkbox1");
+ const checkbox2 = getNativeInterface(accDoc, "checkbox2");
+
+ is(
+ checkboxes.getAttributeValue("AXRole"),
+ controls[3].getAttributeValue("AXRole"),
+ "Found correct group of checkboxes"
+ );
+ is(
+ checkbox1.getAttributeValue("AXRole"),
+ controls[4].getAttributeValue("AXRole"),
+ "Found correct checkbox 1"
+ );
+ is(
+ checkbox2.getAttributeValue("AXRole"),
+ controls[5].getAttributeValue("AXRole"),
+ "Found correct checkbox 2"
+ );
+
+ const switches = getNativeInterface(accDoc, "switches");
+ const switch1 = getNativeInterface(accDoc, "switch1");
+ const switch2 = getNativeInterface(accDoc, "switch2");
+
+ is(
+ switches.getAttributeValue("AXRole"),
+ controls[6].getAttributeValue("AXRole"),
+ "Found correct group of switches"
+ );
+ is(
+ switch1.getAttributeValue("AXRole"),
+ controls[7].getAttributeValue("AXRole"),
+ "Found correct switch 1"
+ );
+ is(
+ switch2.getAttributeValue("AXRole"),
+ controls[8].getAttributeValue("AXRole"),
+ "Found correct switch 2"
+ );
+ }
+);
+
+/**
+ * Test rotor with misc controls
+ */
+addAccessibleTask(
+ `
+ <input role="spinbutton" id="spinbutton" type="number" value="25">
+
+ <details id="details">
+ <summary>Hello</summary>
+ world
+ </details>
+
+ <ul role="tree" id="tree">
+ <li role="treeitem">item1</li>
+ <li role="treeitem">item1</li>
+ </ul>
+
+ <a id="buttonMenu" role="button">Click Me</a>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXControlSearchKey",
+ AXImmediateDescendants: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const controlsCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(4, controlsCount, "Found 4 controls");
+
+ const controls = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const spin = getNativeInterface(accDoc, "spinbutton");
+ const details = getNativeInterface(accDoc, "details");
+ const tree = getNativeInterface(accDoc, "tree");
+ const buttonMenu = getNativeInterface(accDoc, "buttonMenu");
+
+ is(
+ spin.getAttributeValue("AXRole"),
+ controls[0].getAttributeValue("AXRole"),
+ "Found correct spinbutton"
+ );
+ is(
+ details.getAttributeValue("AXRole"),
+ controls[1].getAttributeValue("AXRole"),
+ "Found correct details element"
+ );
+ is(
+ tree.getAttributeValue("AXRole"),
+ controls[2].getAttributeValue("AXRole"),
+ "Found correct tree"
+ );
+ is(
+ buttonMenu.getAttributeValue("AXRole"),
+ controls[3].getAttributeValue("AXRole"),
+ "Found correct button menu"
+ );
+ }
+);
+
+/**
+ * Test rotor with links
+ */
+addAccessibleTask(
+ `
+ <a href="" id="empty">empty link</a>
+ <a href="http://www.example.com/" id="href">Example link</a>
+ <a id="noHref">link without href</a>
+ `,
+ async (browser, accDoc) => {
+ let searchPred = {
+ AXSearchKey: "AXLinkSearchKey",
+ AXImmediateDescendants: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ let linkCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(2, linkCount, "Found two links");
+
+ let links = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ const empty = getNativeInterface(accDoc, "empty");
+ const href = getNativeInterface(accDoc, "href");
+
+ is(
+ empty.getAttributeValue("AXTitle"),
+ links[0].getAttributeValue("AXTitle"),
+ "Found correct first link"
+ );
+ is(
+ href.getAttributeValue("AXTitle"),
+ links[1].getAttributeValue("AXTitle"),
+ "Found correct second link"
+ );
+
+ // unvisited links
+
+ searchPred = {
+ AXSearchKey: "AXUnvisitedLinkSearchKey",
+ AXImmediateDescendants: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ linkCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(2, linkCount, "Found two links");
+
+ links = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(
+ empty.getAttributeValue("AXTitle"),
+ links[0].getAttributeValue("AXTitle"),
+ "Found correct first link"
+ );
+ is(
+ href.getAttributeValue("AXTitle"),
+ links[1].getAttributeValue("AXTitle"),
+ "Found correct second link"
+ );
+
+ // visited links
+
+ let stateChanged = waitForEvent(EVENT_STATE_CHANGE, "href");
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await PlacesTestUtils.addVisits(["http://www.example.com/"]);
+
+ await stateChanged;
+
+ searchPred = {
+ AXSearchKey: "AXVisitedLinkSearchKey",
+ AXImmediateDescendants: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ linkCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(1, linkCount, "Found one link");
+
+ links = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(
+ href.getAttributeValue("AXTitle"),
+ links[0].getAttributeValue("AXTitle"),
+ "Found correct visited link"
+ );
+
+ // Ensure history is cleared before running again
+ await PlacesUtils.history.clear();
+ }
+);
+
+/*
+ * Test AXAnyTypeSearchKey with root group
+ */
+addAccessibleTask(
+ `<h1 id="hello">hello</h1><br><h2 id="world">world</h2><br>goodbye`,
+ (browser, accDoc) => {
+ let searchPred = {
+ AXSearchKey: "AXAnyTypeSearchKey",
+ AXImmediateDescendantsOnly: 1,
+ AXResultsLimit: 1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ let results = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(results.length, 1, "One result for root group");
+ is(
+ results[0].getAttributeValue("AXIdentifier"),
+ "root-group",
+ "Is generated root group"
+ );
+
+ searchPred.AXStartElement = results[0];
+ results = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(results.length, 0, "No more results past root group");
+
+ searchPred.AXDirection = "AXDirectionPrevious";
+ results = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(
+ results.length,
+ 0,
+ "Searching backwards from root group should yield no results"
+ );
+
+ const rootGroup = webArea.getAttributeValue("AXChildren")[0];
+ is(
+ rootGroup.getAttributeValue("AXIdentifier"),
+ "root-group",
+ "Is generated root group"
+ );
+
+ searchPred = {
+ AXSearchKey: "AXAnyTypeSearchKey",
+ AXImmediateDescendantsOnly: 1,
+ AXResultsLimit: 1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ results = rootGroup.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(
+ results[0].getAttributeValue("AXRole"),
+ "AXHeading",
+ "Is first heading child"
+ );
+ }
+);
+
+/**
+ * Test rotor with checkboxes
+ */
+addAccessibleTask(
+ `
+ <fieldset id="checkboxes">
+ <legend>Checkboxes</legend>
+ <input id="checkbox1" type="checkbox" name="g2"> Checkbox 1
+ <input id="checkbox2" type="checkbox" name="g2" checked="checked">Checkbox 2
+ <div id="checkbox3" role="checkbox">Checkbox 3</div>
+ <div id="checkbox4" role="checkbox" aria-checked="true">Checkbox 4</div>
+ </fieldset>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXCheckBoxSearchKey",
+ AXImmediateDescendantsOnly: 0,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const checkboxCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(4, checkboxCount, "Found 4 checkboxes");
+
+ const checkboxes = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const checkbox1 = getNativeInterface(accDoc, "checkbox1");
+ const checkbox2 = getNativeInterface(accDoc, "checkbox2");
+ const checkbox3 = getNativeInterface(accDoc, "checkbox3");
+ const checkbox4 = getNativeInterface(accDoc, "checkbox4");
+
+ is(
+ checkbox1.getAttributeValue("AXValue"),
+ checkboxes[0].getAttributeValue("AXValue"),
+ "Found correct checkbox 1"
+ );
+ is(
+ checkbox2.getAttributeValue("AXValue"),
+ checkboxes[1].getAttributeValue("AXValue"),
+ "Found correct checkbox 2"
+ );
+ is(
+ checkbox3.getAttributeValue("AXValue"),
+ checkboxes[2].getAttributeValue("AXValue"),
+ "Found correct checkbox 3"
+ );
+ is(
+ checkbox4.getAttributeValue("AXValue"),
+ checkboxes[3].getAttributeValue("AXValue"),
+ "Found correct checkbox 4"
+ );
+ }
+);
+
+/**
+ * Test rotor with radiogroups
+ */
+addAccessibleTask(
+ `
+ <div role="radiogroup" id="radios" aria-labelledby="desc">
+ <h1 id="desc">some radio buttons</h1>
+ <div id="radio1" role="radio"> Radio 1</div>
+ <div id="radio2" role="radio"> Radio 2</div>
+ </div>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXRadioGroupSearchKey",
+ AXImmediateDescendants: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const radiogroupCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(1, radiogroupCount, "Found 1 radio group");
+
+ const controls = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const radios = getNativeInterface(accDoc, "radios");
+
+ is(
+ radios.getAttributeValue("AXDescription"),
+ controls[0].getAttributeValue("AXDescription"),
+ "Found correct group of radios"
+ );
+ }
+);
+
+/*
+ * Test rotor with inputs
+ */
+addAccessibleTask(
+ `
+ <input type="text" value="I'm a text field." id="text"><br>
+ <input type="text" value="me too" id="implText"><br>
+ <textarea id="textarea">this is some text in a text area</textarea><br>
+ <input type="tel" value="0000000000" id="tel"><br>
+ <input type="url" value="https://example.com" id="url"><br>
+ <input type="email" value="hi@example.com" id="email"><br>
+ <input type="password" value="blah" id="password"><br>
+ <input type="month" value="2020-01" id="month"><br>
+ <input type="week" value="2020-W01" id="week"><br>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXTextFieldSearchKey",
+ AXImmediateDescendants: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const textfieldCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(9, textfieldCount, "Found 9 fields");
+
+ const fields = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const text = getNativeInterface(accDoc, "text");
+ const implText = getNativeInterface(accDoc, "implText");
+ const textarea = getNativeInterface(accDoc, "textarea");
+ const tel = getNativeInterface(accDoc, "tel");
+ const url = getNativeInterface(accDoc, "url");
+ const email = getNativeInterface(accDoc, "email");
+ const password = getNativeInterface(accDoc, "password");
+ const month = getNativeInterface(accDoc, "month");
+ const week = getNativeInterface(accDoc, "week");
+
+ const toCheck = [
+ text,
+ implText,
+ textarea,
+ tel,
+ url,
+ email,
+ password,
+ month,
+ week,
+ ];
+
+ for (let i = 0; i < toCheck.length; i++) {
+ is(
+ toCheck[i].getAttributeValue("AXValue"),
+ fields[i].getAttributeValue("AXValue"),
+ "Found correct input control"
+ );
+ }
+ }
+);
+
+/**
+ * Test rotor with static text
+ */
+addAccessibleTask(
+ `
+ <h1>Hello I am a heading</h1>
+ This is some regular text.<p>this is some paragraph text</p><br>
+ This is a list:<ul>
+ <li>List item one</li>
+ <li>List item two</li>
+ </ul>
+
+ <a href="http://example.com">This is a link</a>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXStaticTextSearchKey",
+ AXImmediateDescendants: 0,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const textCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(7, textCount, "Found 7 pieces of text");
+
+ const text = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(
+ "Hello I am a heading",
+ text[0].getAttributeValue("AXValue"),
+ "Found correct text node for heading"
+ );
+ is(
+ "This is some regular text.",
+ text[1].getAttributeValue("AXValue"),
+ "Found correct text node"
+ );
+ is(
+ "this is some paragraph text",
+ text[2].getAttributeValue("AXValue"),
+ "Found correct text node for paragraph"
+ );
+ is(
+ "This is a list:",
+ text[3].getAttributeValue("AXValue"),
+ "Found correct text node for pre-list text node"
+ );
+ is(
+ "List item one",
+ text[4].getAttributeValue("AXValue"),
+ "Found correct text node for list item one"
+ );
+ is(
+ "List item two",
+ text[5].getAttributeValue("AXValue"),
+ "Found correct text node for list item two"
+ );
+ is(
+ "This is a link",
+ text[6].getAttributeValue("AXValue"),
+ "Found correct text node for link"
+ );
+ }
+);
+
+/**
+ * Test rotor with lists
+ */
+addAccessibleTask(
+ `
+ <ul id="unordered">
+ <li>hello</li>
+ <li>world</li>
+ </ul>
+
+ <ol id="ordered">
+ <li>item one</li>
+ <li>item two</li>
+ </ol>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXListSearchKey",
+ AXImmediateDescendants: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const listCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(2, listCount, "Found 2 lists");
+
+ const lists = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const ordered = getNativeInterface(accDoc, "ordered");
+ const unordered = getNativeInterface(accDoc, "unordered");
+
+ is(
+ unordered.getAttributeValue("AXChildren")[0].getAttributeValue("AXTitle"),
+ lists[0].getAttributeValue("AXChildren")[0].getAttributeValue("AXTitle"),
+ "Found correct unordered list"
+ );
+ is(
+ ordered.getAttributeValue("AXChildren")[0].getAttributeValue("AXTitle"),
+ lists[1].getAttributeValue("AXChildren")[0].getAttributeValue("AXTitle"),
+ "Found correct ordered list"
+ );
+ }
+);
+
+/*
+ * Test rotor with images
+ */
+addAccessibleTask(
+ `
+ <img id="img1" alt="image one" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"><br>
+ <a href="http://example.com">
+ <img id="img2" alt="image two" src="http://example.com/a11y/accessible/tests/mochitest/moz.png">
+ </a>
+ <img src="" id="img3">
+ `,
+ (browser, accDoc) => {
+ let searchPred = {
+ AXSearchKey: "AXImageSearchKey",
+ AXImmediateDescendantsOnly: 0,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ let images = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(images.length, 3, "Found three images");
+
+ const img1 = getNativeInterface(accDoc, "img1");
+ const img2 = getNativeInterface(accDoc, "img2");
+ const img3 = getNativeInterface(accDoc, "img3");
+
+ is(
+ img1.getAttributeValue("AXDescription"),
+ images[0].getAttributeValue("AXDescription"),
+ "Found correct image"
+ );
+
+ is(
+ img2.getAttributeValue("AXDescription"),
+ images[1].getAttributeValue("AXDescription"),
+ "Found correct image"
+ );
+
+ is(
+ img3.getAttributeValue("AXDescription"),
+ images[2].getAttributeValue("AXDescription"),
+ "Found correct image"
+ );
+ }
+);
+
+/**
+ * Test rotor with frames
+ */
+addAccessibleTask(
+ `
+ <iframe id="frame1" src="data:text/html,<h1>hello</h1>world"></iframe>
+ <iframe id="frame2" src="data:text/html,<iframe id='frame3' src='data:text/html,<h1>goodbye</h1>'>"></iframe>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXFrameSearchKey",
+ AXImmediateDescendantsOnly: 0,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const frameCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(3, frameCount, "Found 3 frames");
+ }
+);
+
+/**
+ * Test rotor with static text
+ */
+addAccessibleTask(
+ `
+ <h1>Hello I am a heading</h1>
+ This is some regular text.<p>this is some paragraph text</p><br>
+ This is a list:<ul>
+ <li>List item one</li>
+ <li>List item two</li>
+ </ul>
+
+ <a href="http://example.com">This is a link</a>
+ `,
+ async (browser, accDoc) => {
+ const searchPred = {
+ AXSearchKey: "AXStaticTextSearchKey",
+ AXImmediateDescendants: 0,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const textCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(7, textCount, "Found 7 pieces of text");
+
+ const text = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ is(
+ "Hello I am a heading",
+ text[0].getAttributeValue("AXValue"),
+ "Found correct text node for heading"
+ );
+ is(
+ "This is some regular text.",
+ text[1].getAttributeValue("AXValue"),
+ "Found correct text node"
+ );
+ is(
+ "this is some paragraph text",
+ text[2].getAttributeValue("AXValue"),
+ "Found correct text node for paragraph"
+ );
+ is(
+ "This is a list:",
+ text[3].getAttributeValue("AXValue"),
+ "Found correct text node for pre-list text node"
+ );
+ is(
+ "List item one",
+ text[4].getAttributeValue("AXValue"),
+ "Found correct text node for list item one"
+ );
+ is(
+ "List item two",
+ text[5].getAttributeValue("AXValue"),
+ "Found correct text node for list item two"
+ );
+ is(
+ "This is a link",
+ text[6].getAttributeValue("AXValue"),
+ "Found correct text node for link"
+ );
+ }
+);
+
+/**
+ * Test search with non-webarea root
+ */
+addAccessibleTask(
+ `
+ <div id="searchroot"><p id="p1">hello</p><p id="p2">world</p></div>
+ <div><p>goodybe</p></div>
+ `,
+ async (browser, accDoc) => {
+ let searchPred = {
+ AXSearchKey: "AXAnyTypeSearchKey",
+ AXImmediateDescendantsOnly: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ };
+
+ const searchRoot = getNativeInterface(accDoc, "searchroot");
+ const resultCount = searchRoot.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(resultCount, 2, "Found 2 items");
+
+ const p1 = getNativeInterface(accDoc, "p1");
+ searchPred = {
+ AXSearchKey: "AXAnyTypeSearchKey",
+ AXImmediateDescendantsOnly: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ AXStartElement: p1,
+ };
+
+ let results = searchRoot.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ Assert.deepEqual(
+ results.map(r => r.getAttributeValue("AXDOMIdentifier")),
+ ["p2"],
+ "Result is next group sibling"
+ );
+
+ searchPred = {
+ AXSearchKey: "AXAnyTypeSearchKey",
+ AXImmediateDescendantsOnly: 1,
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionPrevious",
+ };
+
+ results = searchRoot.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ Assert.deepEqual(
+ results.map(r => r.getAttributeValue("AXDOMIdentifier")),
+ ["p2", "p1"],
+ "A reverse search should return groups in reverse"
+ );
+ }
+);
+
+/**
+ * Test search text
+ */
+addAccessibleTask(
+ `
+ <p>It's about the future, isn't it?</p>
+ <p>Okay, alright, Saturday is good, Saturday's good, I could spend a week in 1955.</p>
+ <ul>
+ <li>I could hang out, you could show me around.</li>
+ <li>There's that word again, heavy.</li>
+ </ul>
+ `,
+ async (browser, f, accDoc) => {
+ let searchPred = {
+ AXSearchKey: "AXAnyTypeSearchKey",
+ AXResultsLimit: -1,
+ AXDirection: "AXDirectionNext",
+ AXSearchText: "could",
+ };
+
+ const webArea = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ is(
+ webArea.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "Got web area accessible"
+ );
+
+ const textSearchCount = webArea.getParameterizedAttributeValue(
+ "AXUIElementCountForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+ is(textSearchCount, 2, "Found 2 matching items in text search");
+
+ const results = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ info(results.map(r => r.getAttributeValue("AXMozDebugDescription")));
+
+ Assert.deepEqual(
+ results.map(r => r.getAttributeValue("AXValue")),
+ [
+ "Okay, alright, Saturday is good, Saturday's good, I could spend a week in 1955.",
+ "I could hang out, you could show me around.",
+ ],
+ "Correct text search results"
+ );
+ },
+ { topLevel: false, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/mac/browser_selectables.js b/accessible/tests/browser/mac/browser_selectables.js
new file mode 100644
index 0000000000..331cd7d21c
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_selectables.js
@@ -0,0 +1,342 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+function getSelectedIds(selectable) {
+ return selectable
+ .getAttributeValue("AXSelectedChildren")
+ .map(c => c.getAttributeValue("AXDOMIdentifier"));
+}
+
+/**
+ * Test aria tabs
+ */
+addAccessibleTask("mac/doc_aria_tabs.html", async (browser, accDoc) => {
+ let tablist = getNativeInterface(accDoc, "tablist");
+ is(
+ tablist.getAttributeValue("AXRole"),
+ "AXTabGroup",
+ "Correct role for tablist"
+ );
+
+ let tabMacAccs = tablist.getAttributeValue("AXTabs");
+ is(tabMacAccs.length, 3, "3 items in AXTabs");
+
+ let selectedTabs = tablist.getAttributeValue("AXSelectedChildren");
+ is(selectedTabs.length, 1, "one selected tab");
+
+ let tab = selectedTabs[0];
+ is(tab.getAttributeValue("AXRole"), "AXRadioButton", "Correct role for tab");
+ is(
+ tab.getAttributeValue("AXSubrole"),
+ "AXTabButton",
+ "Correct subrole for tab"
+ );
+ is(tab.getAttributeValue("AXTitle"), "First Tab", "Correct title for tab");
+
+ let tabToSelect = tabMacAccs[1];
+ is(
+ tabToSelect.getAttributeValue("AXTitle"),
+ "Second Tab",
+ "Correct title for tab"
+ );
+
+ let actions = tabToSelect.actionNames;
+ ok(true, actions);
+ ok(actions.includes("AXPress"), "Has switch action");
+
+ let evt = waitForMacEvent("AXSelectedChildrenChanged");
+ tabToSelect.performAction("AXPress");
+ await evt;
+
+ selectedTabs = tablist.getAttributeValue("AXSelectedChildren");
+ is(selectedTabs.length, 1, "one selected tab");
+ is(
+ selectedTabs[0].getAttributeValue("AXTitle"),
+ "Second Tab",
+ "Correct title for tab"
+ );
+});
+
+addAccessibleTask('<p id="p">hello</p>', async (browser, accDoc) => {
+ let p = getNativeInterface(accDoc, "p");
+ ok(
+ p.attributeNames.includes("AXSelected"),
+ "html element includes 'AXSelected' attribute"
+ );
+ is(p.getAttributeValue("AXSelected"), 0, "AX selected is 'false'");
+});
+
+addAccessibleTask(
+ `<select id="select" aria-label="Choose a number" multiple>
+ <option id="one" selected>One</option>
+ <option id="two">Two</option>
+ <option id="three">Three</option>
+ <option id="four" disabled>Four</option>
+ </select>`,
+ async (browser, accDoc) => {
+ let select = getNativeInterface(accDoc, "select");
+ let one = getNativeInterface(accDoc, "one");
+ let two = getNativeInterface(accDoc, "two");
+ let three = getNativeInterface(accDoc, "three");
+ let four = getNativeInterface(accDoc, "four");
+
+ is(
+ select.getAttributeValue("AXTitle"),
+ "Choose a number",
+ "Select titled correctly"
+ );
+ ok(
+ select.attributeNames.includes("AXOrientation"),
+ "Have orientation attribute"
+ );
+ ok(
+ select.isAttributeSettable("AXSelectedChildren"),
+ "Select can have AXSelectedChildren set"
+ );
+
+ is(one.getAttributeValue("AXTitle"), "", "Option should not have a title");
+ is(
+ one.getAttributeValue("AXValue"),
+ "One",
+ "Option should have correct value"
+ );
+ is(
+ one.getAttributeValue("AXRole"),
+ "AXStaticText",
+ "Options should have AXStaticText role"
+ );
+ ok(one.isAttributeSettable("AXSelected"), "Option can have AXSelected set");
+
+ is(select.getAttributeValue("AXSelectedChildren").length, 1);
+ let evt = waitForMacEvent("AXSelectedChildrenChanged");
+ one.setAttributeValue("AXSelected", false);
+ await evt;
+ is(select.getAttributeValue("AXSelectedChildren").length, 0);
+ evt = waitForMacEvent("AXSelectedChildrenChanged");
+ three.setAttributeValue("AXSelected", true);
+ await evt;
+ is(select.getAttributeValue("AXSelectedChildren").length, 1);
+ ok(getSelectedIds(select).includes("three"), "'three' is selected");
+ evt = waitForMacEvent("AXSelectedChildrenChanged");
+ select.setAttributeValue("AXSelectedChildren", [one, two]);
+ await evt;
+ await untilCacheOk(() => {
+ let ids = getSelectedIds(select);
+ return ids[0] == "one" && ids[1] == "two";
+ }, "Got correct selected children");
+
+ evt = waitForMacEvent("AXSelectedChildrenChanged");
+ select.setAttributeValue("AXSelectedChildren", [three, two, four]);
+ await evt;
+ await untilCacheOk(() => {
+ let ids = getSelectedIds(select);
+ return ids[0] == "two" && ids[1] == "three";
+ }, "Got correct selected children");
+
+ ok(!four.getAttributeValue("AXEnabled"), "Disabled option is disabled");
+ }
+);
+
+addAccessibleTask(
+ `<select id="select" aria-label="Choose a thing" multiple>
+ <optgroup label="Fruits">
+ <option id="banana" selected>Banana</option>
+ <option id="apple">Apple</option>
+ <option id="orange">Orange</option>
+ </optgroup>
+ <optgroup label="Vegetables">
+ <option id="lettuce" selected>Lettuce</option>
+ <option id="tomato">Tomato</option>
+ <option id="onion">Onion</option>
+ </optgroup>
+ <optgroup label="Spices">
+ <option id="cumin">Cumin</option>
+ <option id="coriander">Coriander</option>
+ <option id="allspice" selected>Allspice</option>
+ </optgroup>
+ <option id="everything">Everything</option>
+ </select>`,
+ async (browser, accDoc) => {
+ let select = getNativeInterface(accDoc, "select");
+
+ is(
+ select.getAttributeValue("AXTitle"),
+ "Choose a thing",
+ "Select titled correctly"
+ );
+ ok(
+ select.attributeNames.includes("AXOrientation"),
+ "Have orientation attribute"
+ );
+ ok(
+ select.isAttributeSettable("AXSelectedChildren"),
+ "Select can have AXSelectedChildren set"
+ );
+ let childValueSelectablePairs = select
+ .getAttributeValue("AXChildren")
+ .map(c => [
+ c.getAttributeValue("AXValue"),
+ c.isAttributeSettable("AXSelected"),
+ c.getAttributeValue("AXEnabled"),
+ ]);
+ Assert.deepEqual(
+ childValueSelectablePairs,
+ [
+ ["Fruits", false, false],
+ ["Banana", true, true],
+ ["Apple", true, true],
+ ["Orange", true, true],
+ ["Vegetables", false, false],
+ ["Lettuce", true, true],
+ ["Tomato", true, true],
+ ["Onion", true, true],
+ ["Spices", false, false],
+ ["Cumin", true, true],
+ ["Coriander", true, true],
+ ["Allspice", true, true],
+ ["Everything", true, true],
+ ],
+ "Options are selectable, group labels are not"
+ );
+
+ let allspice = getNativeInterface(accDoc, "allspice");
+ is(
+ allspice.getAttributeValue("AXTitle"),
+ "",
+ "Option should not have a title"
+ );
+ is(
+ allspice.getAttributeValue("AXValue"),
+ "Allspice",
+ "Option should have a value"
+ );
+ is(
+ allspice.getAttributeValue("AXRole"),
+ "AXStaticText",
+ "Options should have AXStaticText role"
+ );
+ ok(
+ allspice.isAttributeSettable("AXSelected"),
+ "Option can have AXSelected set"
+ );
+ is(
+ allspice
+ .getAttributeValue("AXParent")
+ .getAttributeValue("AXDOMIdentifier"),
+ "select",
+ "Select is direct parent of nested option"
+ );
+
+ let groupLabel = select.getAttributeValue("AXChildren")[0];
+ ok(
+ !groupLabel.isAttributeSettable("AXSelected"),
+ "Group label should not be selectable"
+ );
+ is(
+ groupLabel.getAttributeValue("AXValue"),
+ "Fruits",
+ "Group label should have a value"
+ );
+ is(
+ groupLabel.getAttributeValue("AXTitle"),
+ null,
+ "Group label should not have a title"
+ );
+ is(
+ groupLabel.getAttributeValue("AXRole"),
+ "AXStaticText",
+ "Group label should have AXStaticText role"
+ );
+ is(
+ groupLabel
+ .getAttributeValue("AXParent")
+ .getAttributeValue("AXDOMIdentifier"),
+ "select",
+ "Select is direct parent of group label"
+ );
+
+ Assert.deepEqual(getSelectedIds(select), ["banana", "lettuce", "allspice"]);
+ }
+);
+
+addAccessibleTask(
+ `<div role="listbox" id="select" aria-label="Choose a number" aria-multiselectable="true">
+ <div role="option" id="one" aria-selected="true">One</div>
+ <div role="option" id="two">Two</div>
+ <div role="option" id="three">Three</div>
+ <div role="option" id="four" aria-disabled="true">Four</div>
+</div>`,
+ async (browser, accDoc) => {
+ let select = getNativeInterface(accDoc, "select");
+ let one = getNativeInterface(accDoc, "one");
+ let two = getNativeInterface(accDoc, "two");
+ let three = getNativeInterface(accDoc, "three");
+ let four = getNativeInterface(accDoc, "four");
+
+ is(
+ select.getAttributeValue("AXTitle"),
+ "Choose a number",
+ "Select titled correctly"
+ );
+ ok(
+ select.attributeNames.includes("AXOrientation"),
+ "Have orientation attribute"
+ );
+ ok(
+ select.isAttributeSettable("AXSelectedChildren"),
+ "Select can have AXSelectedChildren set"
+ );
+
+ is(one.getAttributeValue("AXTitle"), "", "Option should not have a title");
+ is(
+ one.getAttributeValue("AXValue"),
+ "One",
+ "Option should have correct value"
+ );
+ is(
+ one.getAttributeValue("AXRole"),
+ "AXStaticText",
+ "Options should have AXStaticText role"
+ );
+ ok(one.isAttributeSettable("AXSelected"), "Option can have AXSelected set");
+
+ is(select.getAttributeValue("AXSelectedChildren").length, 1);
+ let evt = waitForMacEvent("AXSelectedChildrenChanged");
+ // Change selection from content.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("one").removeAttribute("aria-selected");
+ });
+ await evt;
+ is(select.getAttributeValue("AXSelectedChildren").length, 0);
+ evt = waitForMacEvent("AXSelectedChildrenChanged");
+ three.setAttributeValue("AXSelected", true);
+ await evt;
+ is(select.getAttributeValue("AXSelectedChildren").length, 1);
+ ok(getSelectedIds(select).includes("three"), "'three' is selected");
+ evt = waitForMacEvent("AXSelectedChildrenChanged");
+ select.setAttributeValue("AXSelectedChildren", [one, two]);
+ await evt;
+ await untilCacheOk(() => {
+ let ids = getSelectedIds(select);
+ return ids[0] == "one" && ids[1] == "two";
+ }, "Got correct selected children");
+
+ evt = waitForMacEvent("AXSelectedChildrenChanged");
+ select.setAttributeValue("AXSelectedChildren", [three, two, four]);
+ await evt;
+ await untilCacheOk(() => {
+ let ids = getSelectedIds(select);
+ return ids[0] == "two" && ids[1] == "three";
+ }, "Got correct selected children");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_table.js b/accessible/tests/browser/mac/browser_table.js
new file mode 100644
index 0000000000..50ae697deb
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_table.js
@@ -0,0 +1,629 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+/* import-globals-from ../../mochitest/attributes.js */
+loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Helper function to test table consistency.
+ */
+function testTableConsistency(table, expectedRowCount, expectedColumnCount) {
+ is(table.getAttributeValue("AXRole"), "AXTable", "Correct role for table");
+
+ let tableChildren = table.getAttributeValue("AXChildren");
+ // XXX: Should be expectedRowCount+ExpectedColumnCount+1 children, rows (incl headers) + cols + headers
+ // if we're trying to match Safari.
+ is(
+ tableChildren.length,
+ expectedRowCount + expectedColumnCount,
+ "Table has children = rows (4) + cols (3)"
+ );
+ for (let i = 0; i < tableChildren.length; i++) {
+ let currChild = tableChildren[i];
+ if (i < expectedRowCount) {
+ is(
+ currChild.getAttributeValue("AXRole"),
+ "AXRow",
+ "Correct role for row"
+ );
+ } else {
+ is(
+ currChild.getAttributeValue("AXRole"),
+ "AXColumn",
+ "Correct role for col"
+ );
+ is(
+ currChild.getAttributeValue("AXRoleDescription"),
+ "column",
+ "Correct role desc for col"
+ );
+ }
+ }
+
+ is(
+ table.getAttributeValue("AXColumnCount"),
+ expectedColumnCount,
+ "Table has correct column count."
+ );
+ is(
+ table.getAttributeValue("AXRowCount"),
+ expectedRowCount,
+ "Table has correct row count."
+ );
+
+ let cols = table.getAttributeValue("AXColumns");
+ is(cols.length, expectedColumnCount, "Table has col list of correct length");
+ for (let i = 0; i < cols.length; i++) {
+ let currCol = cols[i];
+ let currChildren = currCol.getAttributeValue("AXChildren");
+ is(
+ currChildren.length,
+ expectedRowCount,
+ "Column has correct number of cells"
+ );
+ for (let j = 0; j < currChildren.length; j++) {
+ let currChild = currChildren[j];
+ is(
+ currChild.getAttributeValue("AXRole"),
+ "AXCell",
+ "Column child is cell"
+ );
+ }
+ }
+
+ let rows = table.getAttributeValue("AXRows");
+ is(rows.length, expectedRowCount, "Table has row list of correct length");
+ for (let i = 0; i < rows.length; i++) {
+ let currRow = rows[i];
+ let currChildren = currRow.getAttributeValue("AXChildren");
+ is(
+ currChildren.length,
+ expectedColumnCount,
+ "Row has correct number of cells"
+ );
+ for (let j = 0; j < currChildren.length; j++) {
+ let currChild = currChildren[j];
+ is(currChild.getAttributeValue("AXRole"), "AXCell", "Row child is cell");
+ }
+ }
+}
+
+/**
+ * Test table, columns, rows
+ */
+addAccessibleTask(
+ `<table id="customers">
+ <tbody>
+ <tr id="firstrow"><th>Company</th><th>Contact</th><th>Country</th></tr>
+ <tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr>
+ <tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr>
+ <tr><td>Ernst Handel</td><td>Roland Mendel</td><td>Austria</td></tr>
+ </tbody>
+ </table>`,
+ async (browser, accDoc) => {
+ let table = getNativeInterface(accDoc, "customers");
+ testTableConsistency(table, 4, 3);
+
+ const rowText = [
+ "Madrigal Electromotive GmbH",
+ "Lydia Rodarte-Quayle",
+ "Germany",
+ ];
+ let reorder = waitForEvent(EVENT_REORDER, "customers");
+ await SpecialPowers.spawn(browser, [rowText], _rowText => {
+ let tr = content.document.createElement("tr");
+ for (let t of _rowText) {
+ let td = content.document.createElement("td");
+ td.textContent = t;
+ tr.appendChild(td);
+ }
+ content.document.getElementById("customers").appendChild(tr);
+ });
+ await reorder;
+
+ let cols = table.getAttributeValue("AXColumns");
+ is(cols.length, 3, "Table has col list of correct length");
+ for (let i = 0; i < cols.length; i++) {
+ let currCol = cols[i];
+ let currChildren = currCol.getAttributeValue("AXChildren");
+ is(currChildren.length, 5, "Column has correct number of cells");
+ let lastCell = currChildren[currChildren.length - 1];
+ let cellChildren = lastCell.getAttributeValue("AXChildren");
+ is(cellChildren.length, 1, "Cell has a single text child");
+ is(
+ cellChildren[0].getAttributeValue("AXRole"),
+ "AXStaticText",
+ "Correct role for cell child"
+ );
+ is(
+ cellChildren[0].getAttributeValue("AXValue"),
+ rowText[i],
+ "Correct text for cell"
+ );
+ }
+
+ reorder = waitForEvent(EVENT_REORDER, "firstrow");
+ await SpecialPowers.spawn(browser, [], () => {
+ let td = content.document.createElement("td");
+ td.textContent = "Ticker";
+ content.document.getElementById("firstrow").appendChild(td);
+ });
+ await reorder;
+
+ cols = table.getAttributeValue("AXColumns");
+ is(cols.length, 4, "Table has col list of correct length");
+ is(
+ cols[cols.length - 1].getAttributeValue("AXChildren").length,
+ 1,
+ "Last column has single child"
+ );
+
+ reorder = waitForEvent(
+ EVENT_REORDER,
+ e => e.accessible.role == ROLE_DOCUMENT
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("customers").remove();
+ });
+ await reorder;
+
+ try {
+ cols[0].getAttributeValue("AXChildren");
+ ok(false, "Getting children from column of expired table should fail");
+ } catch (e) {
+ ok(true, "Getting children from column of expired table should fail");
+ }
+ }
+);
+
+addAccessibleTask(
+ `<table id="table">
+ <tr>
+ <th colspan="2" id="header1">Header 1</th>
+ <th id="header2">Header 2</th>
+ </tr>
+ <tr>
+ <td id="cell1">one</td>
+ <td id="cell2" rowspan="2">two</td>
+ <td id="cell3">three</td>
+ </tr>
+ <tr>
+ <td id="cell4">four</td>
+ <td id="cell5">five</td>
+ </tr>
+ </table>`,
+ (browser, accDoc) => {
+ let table = getNativeInterface(accDoc, "table");
+
+ let getCellAt = (col, row) =>
+ table.getParameterizedAttributeValue("AXCellForColumnAndRow", [col, row]);
+
+ function testCell(cell, expectedId, expectedColRange, expectedRowRange) {
+ is(
+ cell.getAttributeValue("AXDOMIdentifier"),
+ expectedId,
+ "Correct DOM Identifier"
+ );
+ Assert.deepEqual(
+ cell.getAttributeValue("AXColumnIndexRange"),
+ expectedColRange,
+ "Correct column range"
+ );
+ Assert.deepEqual(
+ cell.getAttributeValue("AXRowIndexRange"),
+ expectedRowRange,
+ "Correct row range"
+ );
+ }
+
+ testCell(getCellAt(0, 0), "header1", [0, 2], [0, 1]);
+ testCell(getCellAt(1, 0), "header1", [0, 2], [0, 1]);
+ testCell(getCellAt(2, 0), "header2", [2, 1], [0, 1]);
+
+ testCell(getCellAt(0, 1), "cell1", [0, 1], [1, 1]);
+ testCell(getCellAt(1, 1), "cell2", [1, 1], [1, 2]);
+ testCell(getCellAt(2, 1), "cell3", [2, 1], [1, 1]);
+
+ testCell(getCellAt(0, 2), "cell4", [0, 1], [2, 1]);
+ testCell(getCellAt(1, 2), "cell2", [1, 1], [1, 2]);
+ testCell(getCellAt(2, 2), "cell5", [2, 1], [2, 1]);
+
+ let colHeaders = table.getAttributeValue("AXColumnHeaderUIElements");
+ Assert.deepEqual(
+ colHeaders.map(c => c.getAttributeValue("AXDOMIdentifier")),
+ ["header1", "header1", "header2"],
+ "Correct column headers"
+ );
+ }
+);
+
+addAccessibleTask(
+ `<table id="table">
+ <tr>
+ <td>Foo</td>
+ </tr>
+ </table>`,
+ (browser, accDoc) => {
+ // Make sure we guess this table to be a layout table.
+ testAttrs(
+ findAccessibleChildByID(accDoc, "table"),
+ { "layout-guess": "true" },
+ true
+ );
+
+ let table = getNativeInterface(accDoc, "table");
+ is(
+ table.getAttributeValue("AXRole"),
+ "AXGroup",
+ "Correct role (AXGroup) for layout table"
+ );
+
+ let children = table.getAttributeValue("AXChildren");
+ is(
+ children.length,
+ 1,
+ "Layout table has single child (no additional columns)"
+ );
+ }
+);
+
+addAccessibleTask(
+ `<div id="table" role="table">
+ <span style="display: block;">
+ <div role="row">
+ <div role="cell">Cell 1</div>
+ <div role="cell">Cell 2</div>
+ </div>
+ </span>
+ <span style="display: block;">
+ <div role="row">
+ <span style="display: block;">
+ <div role="cell">Cell 3</div>
+ <div role="cell">Cell 4</div>
+ </span>
+ </div>
+ </span>
+ </div>`,
+ async (browser, accDoc) => {
+ let table = getNativeInterface(accDoc, "table");
+ testTableConsistency(table, 2, 2);
+ }
+);
+
+/*
+ * After executing function 'change' which operates on 'elem', verify the specified
+ * 'event' (if not null) is fired on elem. After the event, check if the given
+ * native accessible 'table' is a layout or data table by role using 'isLayout'.
+ */
+async function testIsLayout(table, elem, event, change, isLayout) {
+ info(
+ "Changing " +
+ elem +
+ ", expecting table change to " +
+ (isLayout ? "AXGroup" : "AXTable")
+ );
+ const toWait = event ? waitForEvent(event, elem) : null;
+ await change();
+ if (toWait) {
+ await toWait;
+ }
+ let intendedRole = isLayout ? "AXGroup" : "AXTable";
+ await untilCacheIs(
+ () => table.getAttributeValue("AXRole"),
+ intendedRole,
+ "Table role correct after change"
+ );
+}
+
+/*
+ * The following attributes should fire an attribute changed
+ * event, which in turn invalidates the layout-table cache
+ * associated with the given table. After adding and removing
+ * each attr, verify the table is a data or layout table,
+ * appropriately. Attrs: summary, abbr, scope, headers
+ */
+addAccessibleTask(
+ `<table id="table" summary="example summary">
+ <tr role="presentation">
+ <td id="cellOne">cell1</td>
+ <td>cell2</td>
+ </tr>
+ <tr>
+ <td id="cellThree">cell3</td>
+ <td>cell4</td>
+ </tr>
+ </table>`,
+ async (browser, accDoc) => {
+ let table = getNativeInterface(accDoc, "table");
+ // summary attr should take precedence over role="presentation" to make this
+ // a data table
+ is(table.getAttributeValue("AXRole"), "AXTable", "Table is data table");
+
+ info("Removing summary attr");
+ // after summary is removed, we should have a layout table
+ await testIsLayout(
+ table,
+ "table",
+ EVENT_OBJECT_ATTRIBUTE_CHANGED,
+ async () => {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("table").removeAttribute("summary");
+ });
+ },
+ true
+ );
+
+ info("Setting abbr attr");
+ // after abbr is set we should have a data table again
+ await testIsLayout(
+ table,
+ "cellThree",
+ EVENT_OBJECT_ATTRIBUTE_CHANGED,
+ async () => {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("cellThree")
+ .setAttribute("abbr", "hello world");
+ });
+ },
+ false
+ );
+
+ info("Removing abbr attr");
+ // after abbr is removed we should have a layout table again
+ await testIsLayout(
+ table,
+ "cellThree",
+ EVENT_OBJECT_ATTRIBUTE_CHANGED,
+ async () => {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("cellThree").removeAttribute("abbr");
+ });
+ },
+ true
+ );
+
+ info("Setting scope attr");
+ // after scope is set we should have a data table again
+ await testIsLayout(
+ table,
+ "cellThree",
+ EVENT_OBJECT_ATTRIBUTE_CHANGED,
+ async () => {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("cellThree")
+ .setAttribute("scope", "col");
+ });
+ },
+ false
+ );
+
+ info("Removing scope attr");
+ // remove scope should give layout
+ await testIsLayout(
+ table,
+ "cellThree",
+ EVENT_OBJECT_ATTRIBUTE_CHANGED,
+ async () => {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("cellThree").removeAttribute("scope");
+ });
+ },
+ true
+ );
+
+ info("Setting headers attr");
+ // add headers attr should give data
+ await testIsLayout(
+ table,
+ "cellThree",
+ EVENT_OBJECT_ATTRIBUTE_CHANGED,
+ async () => {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("cellThree")
+ .setAttribute("headers", "cellOne");
+ });
+ },
+ false
+ );
+
+ info("Removing headers attr");
+ // remove headers attr should give layout
+ await testIsLayout(
+ table,
+ "cellThree",
+ EVENT_OBJECT_ATTRIBUTE_CHANGED,
+ async () => {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("cellThree")
+ .removeAttribute("headers");
+ });
+ },
+ true
+ );
+ }
+);
+
+/*
+ * The following style changes should fire a table style changed
+ * event, which in turn invalidates the layout-table cache
+ * associated with the given table.
+ */
+addAccessibleTask(
+ `<table id="table">
+ <tr id="rowOne">
+ <td id="cellOne">cell1</td>
+ <td>cell2</td>
+ </tr>
+ <tr>
+ <td>cell3</td>
+ <td>cell4</td>
+ </tr>
+ </table>`,
+ async (browser, accDoc) => {
+ let table = getNativeInterface(accDoc, "table");
+ // we should start as a layout table
+ is(table.getAttributeValue("AXRole"), "AXGroup", "Table is layout table");
+
+ info("Adding cell border");
+ // after cell border added, we should have a data table
+ await testIsLayout(
+ table,
+ "cellOne",
+ null,
+ async () => {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("cellOne")
+ .style.setProperty("border", "5px solid green");
+ });
+ },
+ false
+ );
+
+ info("Removing cell border");
+ // after cell border removed, we should have a layout table
+ await testIsLayout(
+ table,
+ "cellOne",
+ null,
+ async () => {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("cellOne")
+ .style.removeProperty("border");
+ });
+ },
+ true
+ );
+
+ info("Adding row background");
+ // after row background added, we should have a data table
+ await testIsLayout(
+ table,
+ "rowOne",
+ null,
+ async () => {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("rowOne")
+ .style.setProperty("background-color", "green");
+ });
+ },
+ false
+ );
+
+ info("Removing row background");
+ // after row background removed, we should have a layout table
+ await testIsLayout(
+ table,
+ "rowOne",
+ null,
+ async () => {
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("rowOne")
+ .style.removeProperty("background-color");
+ });
+ },
+ true
+ );
+ }
+);
+
+/*
+ * thead/tbody elements with click handlers should:
+ * (a) render as AXGroup elements
+ * (b) expose their rows as part of their parent table's AXRows array
+ */
+addAccessibleTask(
+ `<table id="table">
+ <thead id="thead">
+ <tr><td>head row</td></tr>
+ </thead>
+ <tbody id="tbody">
+ <tr><td>body row</td></tr>
+ <tr><td>another body row</td></tr>
+ </tbody>
+ </table>`,
+ async (browser, accDoc) => {
+ let table = getNativeInterface(accDoc, "table");
+
+ // No click handlers present on thead/tbody
+ let tableChildren = table.getAttributeValue("AXChildren");
+ let tableRows = table.getAttributeValue("AXRows");
+
+ is(tableChildren.length, 4, "Table has four children (3 row + 1 col)");
+ is(tableRows.length, 3, "Table has three rows");
+
+ for (let i = 0; i < tableChildren.length; i++) {
+ const child = tableChildren[i];
+ if (i < 3) {
+ is(
+ child.getAttributeValue("AXRole"),
+ "AXRow",
+ "Table's first 3 children are rows"
+ );
+ } else {
+ is(
+ child.getAttributeValue("AXRole"),
+ "AXColumn",
+ "Table's last child is a column"
+ );
+ }
+ }
+ const reorder = waitForEvent(EVENT_REORDER);
+ await invokeContentTask(browser, [], () => {
+ const head = content.document.getElementById("thead");
+ const body = content.document.getElementById("tbody");
+
+ head.addEventListener("click", function () {});
+ body.addEventListener("click", function () {});
+ });
+ await reorder;
+
+ // Click handlers present
+ tableChildren = table.getAttributeValue("AXChildren");
+
+ is(tableChildren.length, 3, "Table has three children (2 groups + 1 col)");
+ is(
+ tableChildren[0].getAttributeValue("AXRole"),
+ "AXGroup",
+ "Child one is a group"
+ );
+ is(
+ tableChildren[0].getAttributeValue("AXChildren").length,
+ 1,
+ "Child one has one child"
+ );
+
+ is(
+ tableChildren[1].getAttributeValue("AXRole"),
+ "AXGroup",
+ "Child two is a group"
+ );
+ is(
+ tableChildren[1].getAttributeValue("AXChildren").length,
+ 2,
+ "Child two has two children"
+ );
+
+ is(
+ tableChildren[2].getAttributeValue("AXRole"),
+ "AXColumn",
+ "Child three is a col"
+ );
+
+ tableRows = table.getAttributeValue("AXRows");
+ is(tableRows.length, 3, "Table has three rows");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_text_basics.js b/accessible/tests/browser/mac/browser_text_basics.js
new file mode 100644
index 0000000000..e4f0bbfa18
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_text_basics.js
@@ -0,0 +1,380 @@
+/* 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 testRangeAtMarker(macDoc, marker, attribute, expected, msg) {
+ let range = macDoc.getParameterizedAttributeValue(attribute, marker);
+ is(stringForRange(macDoc, range), expected, msg);
+}
+
+function testUIElement(
+ macDoc,
+ marker,
+ msg,
+ expectedRole,
+ expectedValue,
+ expectedRange
+) {
+ let elem = macDoc.getParameterizedAttributeValue(
+ "AXUIElementForTextMarker",
+ marker
+ );
+ is(
+ elem.getAttributeValue("AXRole"),
+ expectedRole,
+ `${msg}: element role matches`
+ );
+ is(elem.getAttributeValue("AXValue"), expectedValue, `${msg}: element value`);
+ let elemRange = macDoc.getParameterizedAttributeValue(
+ "AXTextMarkerRangeForUIElement",
+ elem
+ );
+ is(
+ stringForRange(macDoc, elemRange),
+ expectedRange,
+ `${msg}: element range matches element value`
+ );
+}
+
+function testStyleRun(macDoc, marker, msg, expectedStyleRun) {
+ testRangeAtMarker(
+ macDoc,
+ marker,
+ "AXStyleTextMarkerRangeForTextMarker",
+ expectedStyleRun,
+ `${msg}: style run matches`
+ );
+}
+
+function testParagraph(macDoc, marker, msg, expectedParagraph) {
+ testRangeAtMarker(
+ macDoc,
+ marker,
+ "AXParagraphTextMarkerRangeForTextMarker",
+ expectedParagraph,
+ `${msg}: paragraph matches`
+ );
+}
+
+function testWords(macDoc, marker, msg, expectedLeft, expectedRight) {
+ testRangeAtMarker(
+ macDoc,
+ marker,
+ "AXLeftWordTextMarkerRangeForTextMarker",
+ expectedLeft,
+ `${msg}: left word matches`
+ );
+
+ testRangeAtMarker(
+ macDoc,
+ marker,
+ "AXRightWordTextMarkerRangeForTextMarker",
+ expectedRight,
+ `${msg}: right word matches`
+ );
+}
+
+function testLines(
+ macDoc,
+ marker,
+ msg,
+ expectedLine,
+ expectedLeft,
+ expectedRight
+) {
+ testRangeAtMarker(
+ macDoc,
+ marker,
+ "AXLineTextMarkerRangeForTextMarker",
+ expectedLine,
+ `${msg}: line matches`
+ );
+
+ testRangeAtMarker(
+ macDoc,
+ marker,
+ "AXLeftLineTextMarkerRangeForTextMarker",
+ expectedLeft,
+ `${msg}: left line matches`
+ );
+
+ testRangeAtMarker(
+ macDoc,
+ marker,
+ "AXRightLineTextMarkerRangeForTextMarker",
+ expectedRight,
+ `${msg}: right line matches`
+ );
+}
+
+function* markerIterator(macDoc, reverse = false) {
+ let m = macDoc.getAttributeValue(
+ reverse ? "AXEndTextMarker" : "AXStartTextMarker"
+ );
+ let c = 0;
+ while (m) {
+ yield [m, c++];
+ m = macDoc.getParameterizedAttributeValue(
+ reverse
+ ? "AXPreviousTextMarkerForTextMarker"
+ : "AXNextTextMarkerForTextMarker",
+ m
+ );
+ }
+}
+
+// Tests consistency in text markers between:
+// 1. "Linked list" forward navagation
+// 2. Getting markers by index
+// 3. "Linked list" reverse navagation
+// For each iteration method check that the returned index is consistent
+function testMarkerIntegrity(accDoc, expectedMarkerValues) {
+ let macDoc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ // Iterate forward with "AXNextTextMarkerForTextMarker"
+ let prevMarker;
+ let count = 0;
+ for (let [marker, index] of markerIterator(macDoc)) {
+ count++;
+ let markerIndex = macDoc.getParameterizedAttributeValue(
+ "AXIndexForTextMarker",
+ marker
+ );
+ is(
+ markerIndex,
+ index,
+ `Correct index in "AXNextTextMarkerForTextMarker": ${index}`
+ );
+ if (prevMarker) {
+ let range = macDoc.getParameterizedAttributeValue(
+ "AXTextMarkerRangeForUnorderedTextMarkers",
+ [prevMarker, marker]
+ );
+ is(
+ macDoc.getParameterizedAttributeValue(
+ "AXLengthForTextMarkerRange",
+ range
+ ),
+ 1,
+ `[${index}] marker moved one character`
+ );
+ }
+ prevMarker = marker;
+
+ testWords(
+ macDoc,
+ marker,
+ `At index ${index}`,
+ ...expectedMarkerValues[index].words
+ );
+ testLines(
+ macDoc,
+ marker,
+ `At index ${index}`,
+ ...expectedMarkerValues[index].lines
+ );
+ testUIElement(
+ macDoc,
+ marker,
+ `At index ${index}`,
+ ...expectedMarkerValues[index].element
+ );
+ testParagraph(
+ macDoc,
+ marker,
+ `At index ${index}`,
+ expectedMarkerValues[index].paragraph
+ );
+ testStyleRun(
+ macDoc,
+ marker,
+ `At index ${index}`,
+ expectedMarkerValues[index].style
+ );
+ }
+
+ is(expectedMarkerValues.length, count, `Correct marker count: ${count}`);
+
+ // Use "AXTextMarkerForIndex" to retrieve all text markers
+ for (let i = 0; i < count; i++) {
+ let marker = macDoc.getParameterizedAttributeValue(
+ "AXTextMarkerForIndex",
+ i
+ );
+ let index = macDoc.getParameterizedAttributeValue(
+ "AXIndexForTextMarker",
+ marker
+ );
+ is(index, i, `Correct index in "AXTextMarkerForIndex": ${i}`);
+
+ if (i == count - 1) {
+ ok(
+ !macDoc.getParameterizedAttributeValue(
+ "AXNextTextMarkerForTextMarker",
+ marker
+ ),
+ "Iterated through all markers"
+ );
+ }
+ }
+
+ count = expectedMarkerValues.length;
+
+ // Iterate backward with "AXPreviousTextMarkerForTextMarker"
+ for (let [marker] of markerIterator(macDoc, true)) {
+ if (count <= 0) {
+ ok(false, "Exceeding marker count");
+ break;
+ }
+ count--;
+ let index = macDoc.getParameterizedAttributeValue(
+ "AXIndexForTextMarker",
+ marker
+ );
+ is(
+ index,
+ count,
+ `Correct index in "AXPreviousTextMarkerForTextMarker": ${count}`
+ );
+ }
+
+ is(count, 0, "Iterated backward through all text markers");
+}
+
+addAccessibleTask("mac/doc_textmarker_test.html", async (browser, accDoc) => {
+ const expectedValues = await SpecialPowers.spawn(browser, [], async () => {
+ return content.wrappedJSObject.EXPECTED;
+ });
+
+ testMarkerIntegrity(accDoc, expectedValues);
+});
+
+// Test text marker lesser-than operator
+addAccessibleTask(
+ `<p id="p">hello <a id="a" href="#">goodbye</a> world</p>`,
+ async (browser, accDoc) => {
+ let macDoc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ let start = macDoc.getParameterizedAttributeValue(
+ "AXTextMarkerForIndex",
+ 1
+ );
+ let end = macDoc.getParameterizedAttributeValue("AXTextMarkerForIndex", 10);
+
+ let range = macDoc.getParameterizedAttributeValue(
+ "AXTextMarkerRangeForUnorderedTextMarkers",
+ [end, start]
+ );
+ is(stringForRange(macDoc, range), "ello good");
+ }
+);
+
+addAccessibleTask(
+ `<input id="input" value=""><a href="#">goodbye</a>`,
+ async (browser, accDoc) => {
+ let macDoc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ let input = getNativeInterface(accDoc, "input");
+
+ let range = macDoc.getParameterizedAttributeValue(
+ "AXTextMarkerRangeForUIElement",
+ input
+ );
+
+ is(stringForRange(macDoc, range), "", "string value is correct");
+ }
+);
+
+addAccessibleTask(
+ `<div role="listbox" id="box">
+ <input type="radio" name="test" role="option" title="First item"/>
+ <input type="radio" name="test" role="option" title="Second item"/>
+ </div>`,
+ async (browser, accDoc) => {
+ let box = getNativeInterface(accDoc, "box");
+ const children = box.getAttributeValue("AXChildren");
+ is(children.length, 2, "Listbox contains two items");
+ is(children[0].getAttributeValue("AXValue"), "First item");
+ is(children[1].getAttributeValue("AXValue"), "Second item");
+ }
+);
+
+addAccessibleTask(
+ `<div id="t">
+ A link <b>should</b> explain <em>clearly</em> what information the <i>reader</i> will get by clicking on that link.
+ </div>`,
+ async (browser, accDoc) => {
+ let t = getNativeInterface(accDoc, "t");
+ const children = t.getAttributeValue("AXChildren");
+ const expectedTitles = [
+ "A link ",
+ "should",
+ " explain ",
+ "clearly",
+ " what information the ",
+ "reader",
+ " will get by clicking on that link. ",
+ ];
+ is(children.length, 7, "container has seven children");
+ children.forEach((child, index) => {
+ is(child.getAttributeValue("AXValue"), expectedTitles[index]);
+ });
+ }
+);
+
+addAccessibleTask(
+ `<a href="#">link</a> <input id="input" value="hello">`,
+ async (browser, accDoc) => {
+ let macDoc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ let input = getNativeInterface(accDoc, "input");
+ let range = macDoc.getParameterizedAttributeValue(
+ "AXTextMarkerRangeForUIElement",
+ input
+ );
+
+ let firstMarkerInInput = macDoc.getParameterizedAttributeValue(
+ "AXStartTextMarkerForTextMarkerRange",
+ range
+ );
+
+ let leftWordRange = macDoc.getParameterizedAttributeValue(
+ "AXLeftWordTextMarkerRangeForTextMarker",
+ firstMarkerInInput
+ );
+ let str = macDoc.getParameterizedAttributeValue(
+ "AXStringForTextMarkerRange",
+ leftWordRange
+ );
+ is(str, "hello", "Left word at start of input should be right word");
+ }
+);
+
+addAccessibleTask(`<p id="p">hello world</p>`, async (browser, accDoc) => {
+ let macDoc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ let p = getNativeInterface(accDoc, "p");
+ let range = macDoc.getParameterizedAttributeValue(
+ "AXTextMarkerRangeForUIElement",
+ p
+ );
+
+ let bounds = macDoc.getParameterizedAttributeValue(
+ "AXBoundsForTextMarkerRange",
+ range
+ );
+
+ ok(bounds.origin && bounds.size, "Returned valid bounds");
+});
diff --git a/accessible/tests/browser/mac/browser_text_input.js b/accessible/tests/browser/mac/browser_text_input.js
new file mode 100644
index 0000000000..11a9dc25f1
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_text_input.js
@@ -0,0 +1,657 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+function testValueChangedEventData(
+ macIface,
+ data,
+ expectedId,
+ expectedChangeValue,
+ expectedEditType,
+ expectedWordAtLeft
+) {
+ is(
+ data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier"),
+ expectedId,
+ "Correct AXTextChangeElement"
+ );
+ is(
+ data.AXTextStateChangeType,
+ AXTextStateChangeTypeEdit,
+ "Correct AXTextStateChangeType"
+ );
+
+ let changeValues = data.AXTextChangeValues;
+ is(changeValues.length, 1, "One element in AXTextChangeValues");
+ is(
+ changeValues[0].AXTextChangeValue,
+ expectedChangeValue,
+ "Correct AXTextChangeValue"
+ );
+ is(
+ changeValues[0].AXTextEditType,
+ expectedEditType,
+ "Correct AXTextEditType"
+ );
+
+ let textMarker = changeValues[0].AXTextChangeValueStartMarker;
+ ok(textMarker, "There is a AXTextChangeValueStartMarker");
+ let range = macIface.getParameterizedAttributeValue(
+ "AXLeftWordTextMarkerRangeForTextMarker",
+ textMarker
+ );
+ let str = macIface.getParameterizedAttributeValue(
+ "AXStringForTextMarkerRange",
+ range,
+ "correct word before caret"
+ );
+ is(str, expectedWordAtLeft);
+}
+
+// Return true if the first given object a subset of the second
+function isSubset(subset, superset) {
+ if (typeof subset != "object" || typeof superset != "object") {
+ return superset == subset;
+ }
+
+ for (let [prop, val] of Object.entries(subset)) {
+ if (!isSubset(val, superset[prop])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function matchWebArea(expectedId, expectedInfo) {
+ return (iface, data) => {
+ if (!data) {
+ return false;
+ }
+
+ let textChangeElemID =
+ data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier");
+
+ return (
+ iface.getAttributeValue("AXRole") == "AXWebArea" &&
+ textChangeElemID == expectedId &&
+ isSubset(expectedInfo, data)
+ );
+ };
+}
+
+function matchInput(expectedId, expectedInfo) {
+ return (iface, data) => {
+ if (!data) {
+ return false;
+ }
+
+ return (
+ iface.getAttributeValue("AXDOMIdentifier") == expectedId &&
+ isSubset(expectedInfo, data)
+ );
+ };
+}
+
+async function synthKeyAndTestSelectionChanged(
+ synthKey,
+ synthEvent,
+ expectedId,
+ expectedSelectionString,
+ expectedSelectionInfo
+) {
+ let selectionChangedEvents = Promise.all([
+ waitForMacEventWithInfo(
+ "AXSelectedTextChanged",
+ matchWebArea(expectedId, expectedSelectionInfo)
+ ),
+ waitForMacEventWithInfo(
+ "AXSelectedTextChanged",
+ matchInput(expectedId, expectedSelectionInfo)
+ ),
+ ]);
+
+ EventUtils.synthesizeKey(synthKey, synthEvent);
+ let [webareaEvent, inputEvent] = await selectionChangedEvents;
+ is(
+ inputEvent.data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier"),
+ expectedId,
+ "Correct AXTextChangeElement"
+ );
+
+ let rangeString = inputEvent.macIface.getParameterizedAttributeValue(
+ "AXStringForTextMarkerRange",
+ inputEvent.data.AXSelectedTextMarkerRange
+ );
+ is(
+ rangeString,
+ expectedSelectionString,
+ `selection has correct value (${expectedSelectionString})`
+ );
+
+ is(
+ webareaEvent.macIface.getAttributeValue("AXDOMIdentifier"),
+ "body",
+ "Input event target is top-level WebArea"
+ );
+ rangeString = webareaEvent.macIface.getParameterizedAttributeValue(
+ "AXStringForTextMarkerRange",
+ inputEvent.data.AXSelectedTextMarkerRange
+ );
+ is(
+ rangeString,
+ expectedSelectionString,
+ `selection has correct value (${expectedSelectionString}) via top document`
+ );
+
+ return inputEvent;
+}
+
+function testSelectionEventLeftChar(event, expectedChar) {
+ const selStart = event.macIface.getParameterizedAttributeValue(
+ "AXStartTextMarkerForTextMarkerRange",
+ event.data.AXSelectedTextMarkerRange
+ );
+ const selLeft = event.macIface.getParameterizedAttributeValue(
+ "AXPreviousTextMarkerForTextMarker",
+ selStart
+ );
+ const leftCharRange = event.macIface.getParameterizedAttributeValue(
+ "AXTextMarkerRangeForUnorderedTextMarkers",
+ [selLeft, selStart]
+ );
+ const leftCharString = event.macIface.getParameterizedAttributeValue(
+ "AXStringForTextMarkerRange",
+ leftCharRange
+ );
+ is(leftCharString, expectedChar, "Left character is correct");
+}
+
+function testSelectionEventLine(event, expectedLine) {
+ const selStart = event.macIface.getParameterizedAttributeValue(
+ "AXStartTextMarkerForTextMarkerRange",
+ event.data.AXSelectedTextMarkerRange
+ );
+ const lineRange = event.macIface.getParameterizedAttributeValue(
+ "AXLineTextMarkerRangeForTextMarker",
+ selStart
+ );
+ const lineString = event.macIface.getParameterizedAttributeValue(
+ "AXStringForTextMarkerRange",
+ lineRange
+ );
+ is(lineString, expectedLine, "Line is correct");
+}
+
+async function synthKeyAndTestValueChanged(
+ synthKey,
+ synthEvent,
+ expectedId,
+ expectedTextSelectionId,
+ expectedChangeValue,
+ expectedEditType,
+ expectedWordAtLeft
+) {
+ let valueChangedEvents = Promise.all([
+ waitForMacEvent(
+ "AXSelectedTextChanged",
+ matchWebArea(expectedTextSelectionId, {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ })
+ ),
+ waitForMacEvent(
+ "AXSelectedTextChanged",
+ matchInput(expectedTextSelectionId, {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ })
+ ),
+ waitForMacEventWithInfo(
+ "AXValueChanged",
+ matchWebArea(expectedId, {
+ AXTextStateChangeType: AXTextStateChangeTypeEdit,
+ AXTextChangeValues: [
+ {
+ AXTextChangeValue: expectedChangeValue,
+ AXTextEditType: expectedEditType,
+ },
+ ],
+ })
+ ),
+ waitForMacEventWithInfo(
+ "AXValueChanged",
+ matchInput(expectedId, {
+ AXTextStateChangeType: AXTextStateChangeTypeEdit,
+ AXTextChangeValues: [
+ {
+ AXTextChangeValue: expectedChangeValue,
+ AXTextEditType: expectedEditType,
+ },
+ ],
+ })
+ ),
+ ]);
+
+ EventUtils.synthesizeKey(synthKey, synthEvent);
+ let [, , webareaEvent, inputEvent] = await valueChangedEvents;
+
+ testValueChangedEventData(
+ webareaEvent.macIface,
+ webareaEvent.data,
+ expectedId,
+ expectedChangeValue,
+ expectedEditType,
+ expectedWordAtLeft
+ );
+ testValueChangedEventData(
+ inputEvent.macIface,
+ inputEvent.data,
+ expectedId,
+ expectedChangeValue,
+ expectedEditType,
+ expectedWordAtLeft
+ );
+}
+
+async function focusIntoInput(accDoc, inputId, innerContainerId) {
+ let selectionId = innerContainerId ? innerContainerId : inputId;
+ let input = getNativeInterface(accDoc, inputId);
+ ok(!input.getAttributeValue("AXFocused"), "input is not focused");
+ ok(input.isAttributeSettable("AXFocused"), "input is focusable");
+ let events = Promise.all([
+ waitForMacEvent(
+ "AXFocusedUIElementChanged",
+ iface => iface.getAttributeValue("AXDOMIdentifier") == inputId
+ ),
+ waitForMacEventWithInfo(
+ "AXSelectedTextChanged",
+ matchWebArea(selectionId, {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ })
+ ),
+ waitForMacEventWithInfo(
+ "AXSelectedTextChanged",
+ matchInput(selectionId, {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ })
+ ),
+ ]);
+ input.setAttributeValue("AXFocused", true);
+ await events;
+}
+
+async function focusIntoInputAndType(accDoc, inputId, innerContainerId) {
+ let selectionId = innerContainerId ? innerContainerId : inputId;
+ await focusIntoInput(accDoc, inputId, innerContainerId);
+
+ async function testTextInput(
+ synthKey,
+ expectedChangeValue,
+ expectedWordAtLeft
+ ) {
+ await synthKeyAndTestValueChanged(
+ synthKey,
+ null,
+ inputId,
+ selectionId,
+ expectedChangeValue,
+ AXTextEditTypeTyping,
+ expectedWordAtLeft
+ );
+ }
+
+ await testTextInput("h", "h", "h");
+ await testTextInput("e", "e", "he");
+ await testTextInput("l", "l", "hel");
+ await testTextInput("l", "l", "hell");
+ await testTextInput("o", "o", "hello");
+ await testTextInput(" ", " ", "hello");
+ // You would expect this to be useless but this is what VO
+ // consumes. I guess it concats the inserted text data to the
+ // word to the left of the marker.
+ await testTextInput("w", "w", " ");
+ await testTextInput("o", "o", "wo");
+ await testTextInput("r", "r", "wor");
+ await testTextInput("l", "l", "worl");
+ await testTextInput("d", "d", "world");
+
+ async function testTextDelete(expectedChangeValue, expectedWordAtLeft) {
+ await synthKeyAndTestValueChanged(
+ "KEY_Backspace",
+ null,
+ inputId,
+ selectionId,
+ expectedChangeValue,
+ AXTextEditTypeDelete,
+ expectedWordAtLeft
+ );
+ }
+
+ await testTextDelete("d", "worl");
+ await testTextDelete("l", "wor");
+
+ await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowLeft",
+ null,
+ selectionId,
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
+ AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
+ }
+ );
+ await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowLeft",
+ { shiftKey: true },
+ selectionId,
+ "o",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
+ AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
+ AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
+ }
+ );
+ await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowLeft",
+ { shiftKey: true },
+ selectionId,
+ "wo",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
+ AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
+ AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
+ }
+ );
+ await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowLeft",
+ null,
+ selectionId,
+ "",
+ { AXTextStateChangeType: AXTextStateChangeTypeSelectionMove }
+ );
+ await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowLeft",
+ { shiftKey: true, metaKey: true },
+ selectionId,
+ "hello ",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
+ AXTextSelectionDirection: AXTextSelectionDirectionBeginning,
+ AXTextSelectionGranularity: AXTextSelectionGranularityLine,
+ }
+ );
+ await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowLeft",
+ null,
+ selectionId,
+ "",
+ { AXTextStateChangeType: AXTextStateChangeTypeSelectionMove }
+ );
+ await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowRight",
+ { shiftKey: true, altKey: true },
+ selectionId,
+ "hello",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityWord,
+ }
+ );
+}
+
+// Test text input
+addAccessibleTask(
+ `<a href="#">link</a> <input id="input">`,
+ async (browser, accDoc) => {
+ await focusIntoInputAndType(accDoc, "input");
+ },
+ { topLevel: true, iframe: true, remoteIframe: true }
+);
+
+// Test content editable
+addAccessibleTask(
+ `<div id="input" contentEditable="true" tabindex="0" role="textbox" aria-multiline="true"><div id="inner"><br /></div></div>`,
+ async (browser, accDoc) => {
+ const inner = getNativeInterface(accDoc, "inner");
+ const editableAncestor = inner.getAttributeValue("AXEditableAncestor");
+ is(
+ editableAncestor.getAttributeValue("AXDOMIdentifier"),
+ "input",
+ "Editable ancestor is input"
+ );
+ await focusIntoInputAndType(accDoc, "input");
+ }
+);
+
+// Test input that gets role::EDITCOMBOBOX
+addAccessibleTask(`<input type="text" id="box">`, async (browser, accDoc) => {
+ const box = getNativeInterface(accDoc, "box");
+ const editableAncestor = box.getAttributeValue("AXEditableAncestor");
+ is(
+ editableAncestor.getAttributeValue("AXDOMIdentifier"),
+ "box",
+ "Editable ancestor is box itself"
+ );
+ await focusIntoInputAndType(accDoc, "box");
+});
+
+// Test multiline caret control in a text area
+addAccessibleTask(
+ `<textarea id="input" cols="15">one two three four five six seven eight</textarea>`,
+ async (browser, accDoc) => {
+ await focusIntoInput(accDoc, "input");
+
+ await synthKeyAndTestSelectionChanged("KEY_ArrowRight", null, "input", "", {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
+ });
+
+ await synthKeyAndTestSelectionChanged("KEY_ArrowDown", null, "input", "", {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityLine,
+ });
+
+ await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowLeft",
+ { metaKey: true },
+ "input",
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionBeginning,
+ AXTextSelectionGranularity: AXTextSelectionGranularityLine,
+ }
+ );
+
+ await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowRight",
+ { metaKey: true },
+ "input",
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionEnd,
+ AXTextSelectionGranularity: AXTextSelectionGranularityLine,
+ }
+ );
+ },
+ { topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test that the caret returns the correct marker when it is positioned after
+ * the last character (to facilitate appending text).
+ */
+addAccessibleTask(
+ `<input id="input" value="abc">`,
+ async function (browser, docAcc) {
+ await focusIntoInput(docAcc, "input");
+
+ let event = await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowRight",
+ null,
+ "input",
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
+ }
+ );
+ testSelectionEventLeftChar(event, "a");
+ event = await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowRight",
+ null,
+ "input",
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
+ }
+ );
+ testSelectionEventLeftChar(event, "b");
+ event = await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowRight",
+ null,
+ "input",
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
+ }
+ );
+ testSelectionEventLeftChar(event, "c");
+ },
+ { chrome: true, topLevel: true }
+);
+
+/**
+ * Test that the caret returns the correct line when the caret is at the start
+ * of the line.
+ */
+addAccessibleTask(
+ `
+<textarea id="hard">ab
+cd
+ef
+
+gh
+</textarea>
+<div role="textbox" id="wrapped" contenteditable style="width: 1ch;">a b c</div>
+ `,
+ async function (browser, docAcc) {
+ let hard = getNativeInterface(docAcc, "hard");
+ await focusIntoInput(docAcc, "hard");
+ is(hard.getAttributeValue("AXInsertionPointLineNumber"), 0);
+ let event = await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowDown",
+ null,
+ "hard",
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityLine,
+ }
+ );
+ testSelectionEventLine(event, "cd");
+ is(hard.getAttributeValue("AXInsertionPointLineNumber"), 1);
+ event = await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowDown",
+ null,
+ "hard",
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityLine,
+ }
+ );
+ testSelectionEventLine(event, "ef");
+ is(hard.getAttributeValue("AXInsertionPointLineNumber"), 2);
+ event = await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowDown",
+ null,
+ "hard",
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityLine,
+ }
+ );
+ testSelectionEventLine(event, "");
+ is(hard.getAttributeValue("AXInsertionPointLineNumber"), 3);
+ event = await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowDown",
+ null,
+ "hard",
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityLine,
+ }
+ );
+ testSelectionEventLine(event, "gh");
+ is(hard.getAttributeValue("AXInsertionPointLineNumber"), 4);
+ event = await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowDown",
+ null,
+ "hard",
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityLine,
+ }
+ );
+ testSelectionEventLine(event, "");
+ is(hard.getAttributeValue("AXInsertionPointLineNumber"), 5);
+
+ let wrapped = getNativeInterface(docAcc, "wrapped");
+ await focusIntoInput(docAcc, "wrapped");
+ is(wrapped.getAttributeValue("AXInsertionPointLineNumber"), 0);
+ event = await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowDown",
+ null,
+ "wrapped",
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityLine,
+ }
+ );
+ testSelectionEventLine(event, "b ");
+ is(wrapped.getAttributeValue("AXInsertionPointLineNumber"), 1);
+ event = await synthKeyAndTestSelectionChanged(
+ "KEY_ArrowDown",
+ null,
+ "wrapped",
+ "",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
+ AXTextSelectionDirection: AXTextSelectionDirectionNext,
+ AXTextSelectionGranularity: AXTextSelectionGranularityLine,
+ }
+ );
+ testSelectionEventLine(event, "c");
+ is(wrapped.getAttributeValue("AXInsertionPointLineNumber"), 2);
+ },
+ { chrome: true, topLevel: true }
+);
diff --git a/accessible/tests/browser/mac/browser_text_leaf.js b/accessible/tests/browser/mac/browser_text_leaf.js
new file mode 100644
index 0000000000..21deed6212
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_text_leaf.js
@@ -0,0 +1,83 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Test accessibles aren't created for linebreaks.
+ */
+addAccessibleTask(
+ `hello<br>world`,
+ async (browser, accDoc) => {
+ let doc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+ let docChildren = doc.getAttributeValue("AXChildren");
+ is(docChildren.length, 1, "The document contains a root group");
+
+ let rootGroup = docChildren[0];
+ let children = rootGroup.getAttributeValue("AXChildren");
+ is(docChildren.length, 1, "The root group contains 2 children");
+
+ // verify first child is correct
+ is(
+ children[0].getAttributeValue("AXRole"),
+ "AXStaticText",
+ "First child is a text node"
+ );
+ is(
+ children[0].getAttributeValue("AXValue"),
+ "hello",
+ "First child is hello text"
+ );
+
+ // verify second child is correct
+ is(
+ children[1].getAttributeValue("AXRole"),
+ "AXStaticText",
+ "Second child is a text node"
+ );
+
+ is(
+ children[1].getAttributeValue("AXValue"),
+ gIsIframe && !gIsRemoteIframe ? "world" : "world ",
+ "Second child is world text"
+ );
+ // we have a trailing space in here due to bug 1577028
+ // but this appears fixed in non-remote iframes
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+addAccessibleTask(
+ `<p id="p">hello, this is a test</p>`,
+ async (browser, accDoc) => {
+ let p = getNativeInterface(accDoc, "p");
+ let textLeaf = p.getAttributeValue("AXChildren")[0];
+ ok(textLeaf, "paragraph has a text leaf");
+
+ let str = textLeaf.getParameterizedAttributeValue(
+ "AXStringForRange",
+ NSRange(3, 6)
+ );
+
+ is(str, "lo, th", "AXStringForRange matches.");
+
+ let smallBounds = textLeaf.getParameterizedAttributeValue(
+ "AXBoundsForRange",
+ NSRange(3, 6)
+ );
+
+ let largeBounds = textLeaf.getParameterizedAttributeValue(
+ "AXBoundsForRange",
+ NSRange(3, 8)
+ );
+
+ ok(smallBounds.size[0] < largeBounds.size[0], "longer range is wider");
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/mac/browser_text_selection.js b/accessible/tests/browser/mac/browser_text_selection.js
new file mode 100644
index 0000000000..a914adba8e
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_text_selection.js
@@ -0,0 +1,187 @@
+/* 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";
+
+/**
+ * Test simple text selection
+ */
+addAccessibleTask(`<p id="p">Hello World</p>`, async (browser, accDoc) => {
+ let macDoc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ let startMarker = macDoc.getAttributeValue("AXStartTextMarker");
+ let endMarker = macDoc.getAttributeValue("AXEndTextMarker");
+ let range = macDoc.getParameterizedAttributeValue(
+ "AXTextMarkerRangeForUnorderedTextMarkers",
+ [startMarker, endMarker]
+ );
+ is(stringForRange(macDoc, range), "Hello World");
+
+ let evt = waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => {
+ return (
+ !info.AXTextStateSync &&
+ info.AXTextStateChangeType == AXTextStateChangeTypeSelectionExtend &&
+ elem.getAttributeValue("AXRole") == "AXWebArea"
+ );
+ });
+ await SpecialPowers.spawn(browser, [], () => {
+ let p = content.document.getElementById("p");
+ let r = new content.Range();
+ r.setStart(p.firstChild, 1);
+ r.setEnd(p.firstChild, 8);
+
+ let s = content.getSelection();
+ s.addRange(r);
+ });
+ await evt;
+
+ range = macDoc.getAttributeValue("AXSelectedTextMarkerRange");
+ is(stringForRange(macDoc, range), "ello Wo");
+
+ let firstWordRange = macDoc.getParameterizedAttributeValue(
+ "AXRightWordTextMarkerRangeForTextMarker",
+ startMarker
+ );
+ is(stringForRange(macDoc, firstWordRange), "Hello");
+
+ evt = waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => {
+ return (
+ !info.AXTextStateSync &&
+ info.AXTextStateChangeType == AXTextStateChangeTypeSelectionExtend &&
+ elem.getAttributeValue("AXRole") == "AXWebArea"
+ );
+ });
+ macDoc.setAttributeValue("AXSelectedTextMarkerRange", firstWordRange);
+ await evt;
+ range = macDoc.getAttributeValue("AXSelectedTextMarkerRange");
+ is(stringForRange(macDoc, range), "Hello");
+
+ // Collapse selection
+ evt = waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => {
+ return (
+ info.AXTextStateSync &&
+ info.AXTextStateChangeType == AXTextStateChangeTypeSelectionMove &&
+ elem.getAttributeValue("AXRole") == "AXWebArea"
+ );
+ });
+ await SpecialPowers.spawn(browser, [], () => {
+ let s = content.getSelection();
+ s.collapseToEnd();
+ });
+ await evt;
+});
+
+/**
+ * Test text selection events caused by focus change
+ */
+addAccessibleTask(
+ `<p>
+ Hello <a href="#" id="link">World</a>,
+ I <a href="#" style="user-select: none;" id="unselectable_link">love</a>
+ <button id="button">you</button></p>`,
+ async (browser, accDoc) => {
+ // Set up an AXSelectedTextChanged listener here. It will get resolved
+ // on the first non-root event it encounters, so if we test its data at the end
+ // of this test it will show us the first text-selectable object that was focused,
+ // which is "link".
+ let selTextChanged = waitForMacEvent(
+ "AXSelectedTextChanged",
+ e => e.getAttributeValue("AXDOMIdentifier") != "body"
+ );
+
+ let focusChanged = waitForMacEvent("AXFocusedUIElementChanged");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("unselectable_link").focus();
+ });
+ let focusChangedTarget = await focusChanged;
+ is(
+ focusChangedTarget.getAttributeValue("AXDOMIdentifier"),
+ "unselectable_link",
+ "Correct event target"
+ );
+
+ focusChanged = waitForMacEvent("AXFocusedUIElementChanged");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("button").focus();
+ });
+ focusChangedTarget = await focusChanged;
+ is(
+ focusChangedTarget.getAttributeValue("AXDOMIdentifier"),
+ "button",
+ "Correct event target"
+ );
+
+ focusChanged = waitForMacEvent("AXFocusedUIElementChanged");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("link").focus();
+ });
+ focusChangedTarget = await focusChanged;
+ is(
+ focusChangedTarget.getAttributeValue("AXDOMIdentifier"),
+ "link",
+ "Correct event target"
+ );
+
+ let selTextChangedTarget = await selTextChanged;
+ is(
+ selTextChangedTarget.getAttributeValue("AXDOMIdentifier"),
+ "link",
+ "Correct event target"
+ );
+ }
+);
+
+/**
+ * Test text selection with focus change
+ */
+addAccessibleTask(
+ `<p id="p">Hello <input id="input"></p>`,
+ async (browser, accDoc) => {
+ let macDoc = accDoc.nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+
+ let evt = waitForMacEventWithInfo("AXSelectedTextChanged", (elem, info) => {
+ return (
+ !info.AXTextStateSync &&
+ info.AXTextStateChangeType == AXTextStateChangeTypeSelectionExtend &&
+ elem.getAttributeValue("AXRole") == "AXWebArea"
+ );
+ });
+ await SpecialPowers.spawn(browser, [], () => {
+ let p = content.document.getElementById("p");
+ let r = new content.Range();
+ r.setStart(p.firstChild, 1);
+ r.setEnd(p.firstChild, 3);
+
+ let s = content.getSelection();
+ s.addRange(r);
+ });
+ await evt;
+
+ let range = macDoc.getAttributeValue("AXSelectedTextMarkerRange");
+ is(stringForRange(macDoc, range), "el");
+
+ let events = Promise.all([
+ waitForMacEvent("AXFocusedUIElementChanged"),
+ waitForMacEventWithInfo("AXSelectedTextChanged"),
+ ]);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("input").focus();
+ });
+ let [, { data }] = await events;
+ ok(
+ data.AXTextSelectionChangedFocus,
+ "have AXTextSelectionChangedFocus in event info"
+ );
+ ok(!data.AXTextStateSync, "no AXTextStateSync in editables");
+ is(
+ data.AXTextSelectionDirection,
+ AXTextSelectionDirectionDiscontiguous,
+ "discontigous direction"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_toggle_radio_check.js b/accessible/tests/browser/mac/browser_toggle_radio_check.js
new file mode 100644
index 0000000000..1695d73b0d
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_toggle_radio_check.js
@@ -0,0 +1,304 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test input[type=checkbox]
+ */
+addAccessibleTask(
+ `<input type="checkbox" id="vehicle"><label for="vehicle"> Bike</label>`,
+ async (browser, accDoc) => {
+ let checkbox = getNativeInterface(accDoc, "vehicle");
+ await untilCacheIs(
+ () => checkbox.getAttributeValue("AXValue"),
+ 0,
+ "Correct initial value"
+ );
+
+ let actions = checkbox.actionNames;
+ ok(actions.includes("AXPress"), "Has press action");
+
+ let evt = waitForMacEvent("AXValueChanged", "vehicle");
+ checkbox.performAction("AXPress");
+ await evt;
+ await untilCacheIs(
+ () => checkbox.getAttributeValue("AXValue"),
+ 1,
+ "Correct checked value"
+ );
+
+ evt = waitForMacEvent("AXValueChanged", "vehicle");
+ checkbox.performAction("AXPress");
+ await evt;
+ await untilCacheIs(
+ () => checkbox.getAttributeValue("AXValue"),
+ 0,
+ "Correct checked value"
+ );
+ }
+);
+
+/**
+ * Test aria-pressed toggle buttons
+ */
+addAccessibleTask(
+ `<button id="toggle" aria-pressed="false">toggle</button>`,
+ async (browser, accDoc) => {
+ // Set up a callback to change the toggle value
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("toggle").onclick = e => {
+ let curVal = e.target.getAttribute("aria-pressed");
+ let nextVal = curVal == "false" ? "true" : "false";
+ e.target.setAttribute("aria-pressed", nextVal);
+ };
+ });
+
+ let toggle = getNativeInterface(accDoc, "toggle");
+ await untilCacheIs(
+ () => toggle.getAttributeValue("AXValue"),
+ 0,
+ "Correct initial value"
+ );
+
+ let actions = toggle.actionNames;
+ ok(actions.includes("AXPress"), "Has press action");
+
+ let evt = waitForMacEvent("AXValueChanged", "toggle");
+ toggle.performAction("AXPress");
+ await evt;
+ await untilCacheIs(
+ () => toggle.getAttributeValue("AXValue"),
+ 1,
+ "Correct checked value"
+ );
+
+ evt = waitForMacEvent("AXValueChanged", "toggle");
+ toggle.performAction("AXPress");
+ await evt;
+ await untilCacheIs(
+ () => toggle.getAttributeValue("AXValue"),
+ 0,
+ "Correct checked value"
+ );
+ }
+);
+
+/**
+ * Test aria-checked with tri state
+ */
+addAccessibleTask(
+ `<button role="checkbox" id="checkbox" aria-checked="false">toggle</button>`,
+ async (browser, accDoc) => {
+ // Set up a callback to change the toggle value
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("checkbox").onclick = e => {
+ const states = ["false", "true", "mixed"];
+ let currState = e.target.getAttribute("aria-checked");
+ let nextState = states[(states.indexOf(currState) + 1) % states.length];
+ e.target.setAttribute("aria-checked", nextState);
+ };
+ });
+ let checkbox = getNativeInterface(accDoc, "checkbox");
+ await untilCacheIs(
+ () => checkbox.getAttributeValue("AXValue"),
+ 0,
+ "Correct initial value"
+ );
+
+ let actions = checkbox.actionNames;
+ ok(actions.includes("AXPress"), "Has press action");
+
+ let evt = waitForMacEvent("AXValueChanged", "checkbox");
+ checkbox.performAction("AXPress");
+ await evt;
+ await untilCacheIs(
+ () => checkbox.getAttributeValue("AXValue"),
+ 1,
+ "Correct checked value"
+ );
+
+ // Changing from checked to mixed fires two events. Make sure we wait until
+ // the second so we're asserting based on the latest state.
+ evt = waitForMacEvent("AXValueChanged", (iface, data) => {
+ return (
+ iface.getAttributeValue("AXDOMIdentifier") == "checkbox" &&
+ iface.getAttributeValue("AXValue") == 2
+ );
+ });
+ checkbox.performAction("AXPress");
+ await evt;
+ is(checkbox.getAttributeValue("AXValue"), 2, "Correct checked value");
+ }
+);
+
+/**
+ * Test input[type=radio]
+ */
+addAccessibleTask(
+ `<input type="radio" id="huey" name="drone" value="huey" checked>
+ <label for="huey">Huey</label>
+ <input type="radio" id="dewey" name="drone" value="dewey">
+ <label for="dewey">Dewey</label>`,
+ async (browser, accDoc) => {
+ let huey = getNativeInterface(accDoc, "huey");
+ await untilCacheIs(
+ () => huey.getAttributeValue("AXValue"),
+ 1,
+ "Correct initial value for huey"
+ );
+
+ let dewey = getNativeInterface(accDoc, "dewey");
+ await untilCacheIs(
+ () => dewey.getAttributeValue("AXValue"),
+ 0,
+ "Correct initial value for dewey"
+ );
+
+ let actions = dewey.actionNames;
+ ok(actions.includes("AXPress"), "Has press action");
+
+ let evt = Promise.all([
+ waitForMacEvent("AXValueChanged", "huey"),
+ waitForMacEvent("AXValueChanged", "dewey"),
+ ]);
+ dewey.performAction("AXPress");
+ await evt;
+ await untilCacheIs(
+ () => dewey.getAttributeValue("AXValue"),
+ 1,
+ "Correct checked value for dewey"
+ );
+ await untilCacheIs(
+ () => huey.getAttributeValue("AXValue"),
+ 0,
+ "Correct checked value for huey"
+ );
+ }
+);
+
+/**
+ * Test role=switch
+ */
+addAccessibleTask(
+ `<div role="switch" aria-checked="false" id="sw">hello</div>`,
+ async (browser, accDoc) => {
+ let sw = getNativeInterface(accDoc, "sw");
+ await untilCacheIs(
+ () => sw.getAttributeValue("AXValue"),
+ 0,
+ "Initially switch is off"
+ );
+ is(sw.getAttributeValue("AXRole"), "AXCheckBox", "Has correct role");
+ is(sw.getAttributeValue("AXSubrole"), "AXSwitch", "Has correct subrole");
+
+ let stateChanged = Promise.all([
+ waitForMacEvent("AXValueChanged", "sw"),
+ waitForStateChange("sw", STATE_CHECKED, true),
+ ]);
+
+ // We should get a state change event, and a value change.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("sw")
+ .setAttribute("aria-checked", "true");
+ });
+
+ await stateChanged;
+
+ await untilCacheIs(
+ () => sw.getAttributeValue("AXValue"),
+ 1,
+ "Switch is now on"
+ );
+ }
+);
+
+/**
+ * Test input[type=checkbox] with role=menuitemcheckbox
+ */
+addAccessibleTask(
+ `<input type="checkbox" role="menuitemcheckbox" id="vehicle"><label for="vehicle"> Bike</label>`,
+ async (browser, accDoc) => {
+ let checkbox = getNativeInterface(accDoc, "vehicle");
+ await untilCacheIs(
+ () => checkbox.getAttributeValue("AXValue"),
+ 0,
+ "Correct initial value"
+ );
+
+ let actions = checkbox.actionNames;
+ ok(actions.includes("AXPress"), "Has press action");
+
+ let evt = waitForMacEvent("AXValueChanged", "vehicle");
+ checkbox.performAction("AXPress");
+ await evt;
+ await untilCacheIs(
+ () => checkbox.getAttributeValue("AXValue"),
+ 1,
+ "Correct checked value"
+ );
+
+ evt = waitForMacEvent("AXValueChanged", "vehicle");
+ checkbox.performAction("AXPress");
+ await evt;
+ await untilCacheIs(
+ () => checkbox.getAttributeValue("AXValue"),
+ 0,
+ "Correct checked value"
+ );
+ }
+);
+
+/**
+ * Test input[type=radio] with role=menuitemradio
+ */
+addAccessibleTask(
+ `<input type="radio" role="menuitemradio" id="huey" name="drone" value="huey" checked>
+ <label for="huey">Huey</label>
+ <input type="radio" role="menuitemradio" id="dewey" name="drone" value="dewey">
+ <label for="dewey">Dewey</label>`,
+ async (browser, accDoc) => {
+ let huey = getNativeInterface(accDoc, "huey");
+ await untilCacheIs(
+ () => huey.getAttributeValue("AXValue"),
+ 1,
+ "Correct initial value for huey"
+ );
+
+ let dewey = getNativeInterface(accDoc, "dewey");
+ await untilCacheIs(
+ () => dewey.getAttributeValue("AXValue"),
+ 0,
+ "Correct initial value for dewey"
+ );
+
+ let actions = dewey.actionNames;
+ ok(actions.includes("AXPress"), "Has press action");
+
+ let evt = Promise.all([
+ waitForMacEvent("AXValueChanged", "huey"),
+ waitForMacEvent("AXValueChanged", "dewey"),
+ ]);
+ dewey.performAction("AXPress");
+ await evt;
+ await untilCacheIs(
+ () => dewey.getAttributeValue("AXValue"),
+ 1,
+ "Correct checked value for dewey"
+ );
+ await untilCacheIs(
+ () => huey.getAttributeValue("AXValue"),
+ 0,
+ "Correct checked value for huey"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_webarea.js b/accessible/tests/browser/mac/browser_webarea.js
new file mode 100644
index 0000000000..ac6122de14
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_webarea.js
@@ -0,0 +1,77 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+// Test web area role and AXLoadComplete event
+addAccessibleTask(``, async (browser, accDoc) => {
+ let evt = waitForMacEvent("AXLoadComplete", (iface, data) => {
+ return iface.getAttributeValue("AXDescription") == "webarea test";
+ });
+ await SpecialPowers.spawn(browser, [], () => {
+ content.location = "data:text/html,<title>webarea test</title>";
+ });
+ let doc = await evt;
+
+ is(
+ doc.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "document has AXWebArea role"
+ );
+ is(doc.getAttributeValue("AXValue"), "", "document has no AXValue");
+ is(doc.getAttributeValue("AXTitle"), null, "document has no AXTitle");
+
+ is(doc.getAttributeValue("AXLoaded"), 1, "document has finished loading");
+});
+
+// Test iframe web area role and AXLayoutComplete event
+addAccessibleTask(`<title>webarea test</title>`, async (browser, accDoc) => {
+ // If the iframe loads before the top level document finishes loading, we'll
+ // get both an AXLayoutComplete event for the iframe and an AXLoadComplete
+ // event for the document. Otherwise, if the iframe loads after the
+ // document, we'll get one AXLoadComplete event.
+ let eventPromise = Promise.race([
+ waitForMacEvent("AXLayoutComplete", (iface, data) => {
+ return iface.getAttributeValue("AXDescription") == "iframe document";
+ }),
+ waitForMacEvent("AXLoadComplete", (iface, data) => {
+ return iface.getAttributeValue("AXDescription") == "webarea test";
+ }),
+ ]);
+ await SpecialPowers.spawn(browser, [], () => {
+ const iframe = content.document.createElement("iframe");
+ iframe.src = "data:text/html,<title>iframe document</title>hello world";
+ content.document.body.appendChild(iframe);
+ });
+ let doc = await eventPromise;
+
+ if (doc.getAttributeValue("AXTitle")) {
+ // iframe should have no title, so if we get a title here
+ // we've got the main document and need to get the iframe from
+ // the main doc
+ doc = doc.getAttributeValue("AXChildren")[0];
+ }
+
+ is(
+ doc.getAttributeValue("AXRole"),
+ "AXWebArea",
+ "iframe document has AXWebArea role"
+ );
+ is(doc.getAttributeValue("AXValue"), "", "iframe document has no AXValue");
+ is(doc.getAttributeValue("AXTitle"), null, "iframe document has no AXTitle");
+ is(
+ doc.getAttributeValue("AXDescription"),
+ "iframe document",
+ "test has correct label"
+ );
+
+ is(
+ doc.getAttributeValue("AXLoaded"),
+ 1,
+ "iframe document has finished loading"
+ );
+});
diff --git a/accessible/tests/browser/mac/doc_aria_tabs.html b/accessible/tests/browser/mac/doc_aria_tabs.html
new file mode 100644
index 0000000000..0c8f2afd6f
--- /dev/null
+++ b/accessible/tests/browser/mac/doc_aria_tabs.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html><head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+
+ <style type="text/css">
+ .tabs {
+ padding: 1em;
+ }
+
+ [role="tablist"] {
+ margin-bottom: -1px;
+ }
+
+ [role="tab"] {
+ position: relative;
+ z-index: 1;
+ background: white;
+ border-radius: 5px 5px 0 0;
+ border: 1px solid grey;
+ border-bottom: 0;
+ padding: 0.2em;
+ }
+
+ [role="tab"][aria-selected="true"] {
+ z-index: 3;
+ }
+
+ [role="tabpanel"] {
+ position: relative;
+ padding: 0 0.5em 0.5em 0.7em;
+ border: 1px solid grey;
+ border-radius: 0 0 5px 5px;
+ background: white;
+ z-index: 2;
+ }
+
+ [role="tabpanel"]:focus {
+ border-color: orange;
+ outline: 1px solid orange;
+ }
+ </style>
+ <script>
+ 'use strict';
+ /* exported changeTabs */
+ function changeTabs(target) {
+ const parent = target.parentNode;
+ const grandparent = parent.parentNode;
+
+ // Remove all current selected tabs
+ parent
+ .querySelectorAll('[aria-selected="true"]')
+ .forEach(t => t.setAttribute("aria-selected", false));
+
+ // Set this tab as selected
+ target.setAttribute("aria-selected", true);
+
+ // Hide all tab panels
+ grandparent
+ .querySelectorAll('[role="tabpanel"]')
+ .forEach(p => (p.hidden = true));
+
+ // Show the selected panel
+ grandparent.parentNode
+ .querySelector(`#${target.getAttribute("aria-controls")}`)
+ .removeAttribute("hidden");
+ }
+ </script>
+ <title>ARIA: tab role - Example - code sample</title>
+</head>
+<body id="body">
+
+ <div class="tabs">
+ <div id="tablist" role="tablist" aria-label="Sample Tabs">
+ <button onclick="changeTabs(this)" role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
+ First Tab
+ </button>
+ <button onclick="changeTabs(this)" role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
+ Second Tab
+ </button>
+ <button onclick="changeTabs(this)" role="tab" aria-selected="false" aria-controls="panel-3" id="tab-3">
+ Third Tab
+ </button>
+ </div>
+ <div id="panel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1">
+ <p>Content for the first panel</p>
+ </div>
+ <div id="panel-2" role="tabpanel" tabindex="0" aria-labelledby="tab-2" hidden="">
+ <p>Content for the second panel</p>
+ </div>
+ <div id="panel-3" role="tabpanel" tabindex="0" aria-labelledby="tab-3" hidden="">
+ <p>Content for the third panel</p>
+ </div>
+ </div>
+</body></html>
diff --git a/accessible/tests/browser/mac/doc_menulist.xhtml b/accessible/tests/browser/mac/doc_menulist.xhtml
new file mode 100644
index 0000000000..d6751bc8f4
--- /dev/null
+++ b/accessible/tests/browser/mac/doc_menulist.xhtml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <hbox>
+ <label control="defaultZoom" value="Zoom"/>
+ <hbox>
+ <menulist id="defaultZoom">
+ <menupopup>
+ <menuitem label="50%" value="50"/>
+ <menuitem label="100%" value="100"/>
+ <menuitem label="150%" value="150"/>
+ <menuitem label="200%" value="200"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/browser/mac/doc_rich_listbox.xhtml b/accessible/tests/browser/mac/doc_rich_listbox.xhtml
new file mode 100644
index 0000000000..3acaf3bff8
--- /dev/null
+++ b/accessible/tests/browser/mac/doc_rich_listbox.xhtml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <richlistbox id="categories">
+ <richlistitem id="general">
+ <label value="general"/>
+ </richlistitem>
+
+ <richlistitem id="home">
+ <label value="home"/>
+ </richlistitem>
+
+ <richlistitem id="search">
+ <label value="search"/>
+ </richlistitem>
+
+ <richlistitem id="privacy">
+ <label value="privacy"/>
+ </richlistitem>
+ </richlistbox>
+</window>
diff --git a/accessible/tests/browser/mac/doc_textmarker_test.html b/accessible/tests/browser/mac/doc_textmarker_test.html
new file mode 100644
index 0000000000..10b68b5114
--- /dev/null
+++ b/accessible/tests/browser/mac/doc_textmarker_test.html
@@ -0,0 +1,2424 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+ </head>
+ <body id="body">
+ <p>Bob Loblaw Lobs Law Bomb</p>
+ <p>I love all of my <a href="#">children</a> equally</p>
+ <p>This is the <b>best</b> free scr<a href="#">apbook</a>ing class I have ever taken</p>
+ <ul>
+ <li>Fried cheese with club sauce</li>
+ <li>Popcorn shrimp with club sauce</li>
+ <li>Chicken fingers with <i>spicy</i> club sauce</li>
+ </ul>
+ <ul style="list-style: none;"><li>Do not order the Skip's Scramble</li></ul>
+ <p style="width: 1rem">These are my awards, Mother. From Army.</p>
+ <p>I <input value="deceived you">, mom.</p>
+ <script>
+ "use strict";
+ window.EXPECTED = [
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Bob", "Bob"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Bob", "Bob"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Bob", "Bob"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Bob", " "],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: [" ", "Loblaw"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Loblaw", "Loblaw"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Loblaw", "Loblaw"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Loblaw", "Loblaw"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Loblaw", "Loblaw"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Loblaw", "Loblaw"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Loblaw", " "],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: [" ", "Lobs"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Lobs", "Lobs"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Lobs", "Lobs"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Lobs", "Lobs"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Lobs", " "],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: [" ", "Law"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Law", "Law"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Law", "Law"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Law", " "],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: [" ", "Bomb"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Bomb", "Bomb"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Bomb", "Bomb"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "Bob Loblaw Lobs Law Bomb",
+ lines: ["Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"],
+ words: ["Bomb", "Bomb"],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "Bob Loblaw Lobs Law Bomb",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "Bob Loblaw Lobs Law Bomb",
+ "I love all of my children equally"],
+ words: ["Bomb", ""],
+ element: ["AXStaticText",
+ "Bob Loblaw Lobs Law Bomb",
+ "Bob Loblaw Lobs Law Bomb"] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["I", " "],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: [" ", "love"],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["love", "love"],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["love", "love"],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["love", "love"],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["love", " "],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: [" ", "all"],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["all", "all"],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["all", "all"],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["all", " "],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: [" ", "of"],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["of", "of"],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["of", " "],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: [" ", "my"],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["my", "my"],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["my", " "],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "I love all of my ",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: [" ", "children"],
+ element: ["AXStaticText", "I love all of my ", "I love all of my "] },
+ { style: "children",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["children", "children"],
+ element: ["AXStaticText", "children", "children"] },
+ { style: "children",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["children", "children"],
+ element: ["AXStaticText", "children", "children"] },
+ { style: "children",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["children", "children"],
+ element: ["AXStaticText", "children", "children"] },
+ { style: "children",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["children", "children"],
+ element: ["AXStaticText", "children", "children"] },
+ { style: "children",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["children", "children"],
+ element: ["AXStaticText", "children", "children"] },
+ { style: "children",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["children", "children"],
+ element: ["AXStaticText", "children", "children"] },
+ { style: "children",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["children", "children"],
+ element: ["AXStaticText", "children", "children"] },
+ { style: "children",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["children", " "],
+ element: ["AXStaticText", "children", "children"] },
+ { style: " equally",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: [" ", "equally"],
+ element: ["AXStaticText", " equally", " equally"] },
+ { style: " equally",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["equally", "equally"],
+ element: ["AXStaticText", " equally", " equally"] },
+ { style: " equally",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["equally", "equally"],
+ element: ["AXStaticText", " equally", " equally"] },
+ { style: " equally",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["equally", "equally"],
+ element: ["AXStaticText", " equally", " equally"] },
+ { style: " equally",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["equally", "equally"],
+ element: ["AXStaticText", " equally", " equally"] },
+ { style: " equally",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["equally", "equally"],
+ element: ["AXStaticText", " equally", " equally"] },
+ { style: " equally",
+ paragraph: "I love all of my children equally",
+ lines: ["I love all of my children equally",
+ "I love all of my children equally",
+ "I love all of my children equally"],
+ words: ["equally", "equally"],
+ element: ["AXStaticText", " equally", " equally"] },
+ { style: " equally",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "I love all of my children equally",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["equally", ""],
+ element: ["AXStaticText", " equally", " equally"] },
+ { style: "This is the ",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["This", "This"],
+ element: ["AXStaticText", "This is the ", "This is the "] },
+ { style: "This is the ",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["This", "This"],
+ element: ["AXStaticText", "This is the ", "This is the "] },
+ { style: "This is the ",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["This", "This"],
+ element: ["AXStaticText", "This is the ", "This is the "] },
+ { style: "This is the ",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["This", " "],
+ element: ["AXStaticText", "This is the ", "This is the "] },
+ { style: "This is the ",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: [" ", "is"],
+ element: ["AXStaticText", "This is the ", "This is the "] },
+ { style: "This is the ",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["is", "is"],
+ element: ["AXStaticText", "This is the ", "This is the "] },
+ { style: "This is the ",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["is", " "],
+ element: ["AXStaticText", "This is the ", "This is the "] },
+ { style: "This is the ",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: [" ", "the"],
+ element: ["AXStaticText", "This is the ", "This is the "] },
+ { style: "This is the ",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["the", "the"],
+ element: ["AXStaticText", "This is the ", "This is the "] },
+ { style: "This is the ",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["the", "the"],
+ element: ["AXStaticText", "This is the ", "This is the "] },
+ { style: "This is the ",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["the", " "],
+ element: ["AXStaticText", "This is the ", "This is the "] },
+ { style: "This is the ",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: [" ", "best"],
+ element: ["AXStaticText", "This is the ", "This is the "] },
+ { style: "best",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["best", "best"],
+ element: ["AXStaticText", "best", "best"] },
+ { style: "best",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["best", "best"],
+ element: ["AXStaticText", "best", "best"] },
+ { style: "best",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["best", "best"],
+ element: ["AXStaticText", "best", "best"] },
+ { style: "best",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["best", " "],
+ element: ["AXStaticText", "best", "best"] },
+ { style: " free scr",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: [" ", "free"],
+ element: ["AXStaticText", " free scr", " free scr"] },
+ { style: " free scr",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["free", "free"],
+ element: ["AXStaticText", " free scr", " free scr"] },
+ { style: " free scr",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["free", "free"],
+ element: ["AXStaticText", " free scr", " free scr"] },
+ { style: " free scr",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["free", "free"],
+ element: ["AXStaticText", " free scr", " free scr"] },
+ { style: " free scr",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["free", " "],
+ element: ["AXStaticText", " free scr", " free scr"] },
+ { style: " free scr",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: [" ", "scrapbooking"],
+ element: ["AXStaticText", " free scr", " free scr"] },
+ { style: " free scr",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["scrapbooking", "scrapbooking"],
+ element: ["AXStaticText", " free scr", " free scr"] },
+ { style: " free scr",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["scrapbooking", "scrapbooking"],
+ element: ["AXStaticText", " free scr", " free scr"] },
+ { style: " free scr",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["scrapbooking", "scrapbooking"],
+ element: ["AXStaticText", " free scr", " free scr"] },
+ { style: "apbook",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["scrapbooking", "scrapbooking"],
+ element: ["AXStaticText", "apbook", "apbook"] },
+ { style: "apbook",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["scrapbooking", "scrapbooking"],
+ element: ["AXStaticText", "apbook", "apbook"] },
+ { style: "apbook",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["scrapbooking", "scrapbooking"],
+ element: ["AXStaticText", "apbook", "apbook"] },
+ { style: "apbook",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["scrapbooking", "scrapbooking"],
+ element: ["AXStaticText", "apbook", "apbook"] },
+ { style: "apbook",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["scrapbooking", "scrapbooking"],
+ element: ["AXStaticText", "apbook", "apbook"] },
+ { style: "apbook",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["scrapbooking", "scrapbooking"],
+ element: ["AXStaticText", "apbook", "apbook"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["scrapbooking", "scrapbooking"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["scrapbooking", "scrapbooking"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["scrapbooking", " "],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: [" ", "class"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["class", "class"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["class", "class"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["class", "class"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["class", "class"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["class", " "],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: [" ", "I"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["I", " "],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: [" ", "have"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["have", "have"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["have", "have"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["have", "have"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["have", " "],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: [" ", "ever"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["ever", "ever"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["ever", "ever"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["ever", "ever"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["ever", " "],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: [" ", "taken"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["taken", "taken"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["taken", "taken"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["taken", "taken"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "This is the best free scrapbooking class I have ever taken",
+ lines: ["This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken",
+ "This is the best free scrapbooking class I have ever taken"],
+ words: ["taken", "taken"],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "ing class I have ever taken",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "This is the best free scrapbooking class I have ever taken",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["taken", ""],
+ element: ["AXStaticText",
+ "ing class I have ever taken",
+ "ing class I have ever taken"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["\u2022 Fried", "\u2022 Fried"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["\u2022 Fried", "\u2022 Fried"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["\u2022 Fried", "\u2022 Fried"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["\u2022 Fried", "\u2022 Fried"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["\u2022 Fried", " "],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: [" ", "cheese"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["cheese", "cheese"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["cheese", "cheese"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["cheese", "cheese"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["cheese", "cheese"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["cheese", "cheese"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["cheese", " "],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: [" ", "with"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["with", "with"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["with", "with"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["with", "with"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["with", " "],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: [" ", "club"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["club", "club"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["club", "club"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["club", "club"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["club", " "],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: [" ", "sauce"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["sauce", "sauce"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["sauce", "sauce"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["sauce", "sauce"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Fried cheese with club sauce",
+ lines: ["\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"],
+ words: ["sauce", "sauce"],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Fried cheese with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Fried cheese with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["sauce", ""],
+ element: ["AXStaticText",
+ "Fried cheese with club sauce",
+ "\u2022 Fried cheese with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["\u2022 Popcorn", "\u2022 Popcorn"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["\u2022 Popcorn", "\u2022 Popcorn"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["\u2022 Popcorn", "\u2022 Popcorn"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["\u2022 Popcorn", "\u2022 Popcorn"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["\u2022 Popcorn", "\u2022 Popcorn"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["\u2022 Popcorn", "\u2022 Popcorn"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["\u2022 Popcorn", " "],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: [" ", "shrimp"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["shrimp", "shrimp"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["shrimp", "shrimp"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["shrimp", "shrimp"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["shrimp", "shrimp"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["shrimp", "shrimp"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["shrimp", " "],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: [" ", "with"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["with", "with"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["with", "with"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["with", "with"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["with", " "],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: [" ", "club"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["club", "club"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["club", "club"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["club", "club"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["club", " "],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: [" ", "sauce"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["sauce", "sauce"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["sauce", "sauce"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["sauce", "sauce"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Popcorn shrimp with club sauce",
+ lines: ["\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"],
+ words: ["sauce", "sauce"],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Popcorn shrimp with club sauce",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Popcorn shrimp with club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["sauce", ""],
+ element: ["AXStaticText",
+ "Popcorn shrimp with club sauce",
+ "\u2022 Popcorn shrimp with club sauce"] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["\u2022 Chicken", "\u2022 Chicken"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["\u2022 Chicken", "\u2022 Chicken"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["\u2022 Chicken", "\u2022 Chicken"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["\u2022 Chicken", "\u2022 Chicken"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["\u2022 Chicken", "\u2022 Chicken"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["\u2022 Chicken", "\u2022 Chicken"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["\u2022 Chicken", " "],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: [" ", "fingers"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["fingers", "fingers"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["fingers", "fingers"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["fingers", "fingers"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["fingers", "fingers"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["fingers", "fingers"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["fingers", "fingers"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["fingers", " "],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: [" ", "with"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["with", "with"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["with", "with"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["with", "with"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["with", " "],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "\u2022 Chicken fingers with ",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: [" ", "spicy"],
+ element: ["AXStaticText",
+ "Chicken fingers with ",
+ "\u2022 Chicken fingers with "] },
+ { style: "spicy",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["spicy", "spicy"],
+ element: ["AXStaticText", "spicy", "spicy"] },
+ { style: "spicy",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["spicy", "spicy"],
+ element: ["AXStaticText", "spicy", "spicy"] },
+ { style: "spicy",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["spicy", "spicy"],
+ element: ["AXStaticText", "spicy", "spicy"] },
+ { style: "spicy",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["spicy", "spicy"],
+ element: ["AXStaticText", "spicy", "spicy"] },
+ { style: "spicy",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["spicy", " "],
+ element: ["AXStaticText", "spicy", "spicy"] },
+ { style: " club sauce",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: [" ", "club"],
+ element: ["AXStaticText", " club sauce", " club sauce"] },
+ { style: " club sauce",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["club", "club"],
+ element: ["AXStaticText", " club sauce", " club sauce"] },
+ { style: " club sauce",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["club", "club"],
+ element: ["AXStaticText", " club sauce", " club sauce"] },
+ { style: " club sauce",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["club", "club"],
+ element: ["AXStaticText", " club sauce", " club sauce"] },
+ { style: " club sauce",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["club", " "],
+ element: ["AXStaticText", " club sauce", " club sauce"] },
+ { style: " club sauce",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: [" ", "sauce"],
+ element: ["AXStaticText", " club sauce", " club sauce"] },
+ { style: " club sauce",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["sauce", "sauce"],
+ element: ["AXStaticText", " club sauce", " club sauce"] },
+ { style: " club sauce",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["sauce", "sauce"],
+ element: ["AXStaticText", " club sauce", " club sauce"] },
+ { style: " club sauce",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["sauce", "sauce"],
+ element: ["AXStaticText", " club sauce", " club sauce"] },
+ { style: " club sauce",
+ paragraph: "\u2022 Chicken fingers with spicy club sauce",
+ lines: ["\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "\u2022 Chicken fingers with spicy club sauce"],
+ words: ["sauce", "sauce"],
+ element: ["AXStaticText", " club sauce", " club sauce"] },
+ { style: " club sauce",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "\u2022 Chicken fingers with spicy club sauce",
+ "Do not order the Skip's Scramble"],
+ words: ["sauce", ""],
+ element: ["AXStaticText", " club sauce", " club sauce"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Do", "Do"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Do", " "],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: [" ", "not"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["not", "not"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["not", "not"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["not", " "],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: [" ", "order"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["order", "order"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["order", "order"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["order", "order"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["order", "order"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["order", " "],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: [" ", "the"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["the", "the"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["the", "the"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["the", " "],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: [" ", "Skip'"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Skip'", "Skip'"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Skip'", "Skip'"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Skip'", "Skip'"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Skip'", "Skip'"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Skip'", "s"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["s", " "],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: [" ", "Scramble"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Scramble", "Scramble"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Scramble", "Scramble"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Scramble", "Scramble"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Scramble", "Scramble"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Scramble", "Scramble"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Scramble", "Scramble"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "Do not order the Skip's Scramble",
+ lines: ["Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"],
+ words: ["Scramble", "Scramble"],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "Do not order the Skip's Scramble",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["These ",
+ "Do not order the Skip's Scramble",
+ "These "],
+ words: ["Scramble", ""],
+ element: ["AXStaticText",
+ "Do not order the Skip's Scramble",
+ "Do not order the Skip's Scramble"] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["These ", "These ", "These "],
+ words: ["These", "These"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["These ", "These ", "These "],
+ words: ["These", "These"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["These ", "These ", "These "],
+ words: ["These", "These"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["These ", "These ", "These "],
+ words: ["These", "These"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["These ", "These ", "These "],
+ words: ["These", " "],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["are ", "These ", "are "],
+ words: [" ", "are"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["are ", "are ", "are "],
+ words: ["are", "are"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["are ", "are ", "are "],
+ words: ["are", "are"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["are ", "are ", "are "],
+ words: ["are", " "],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["my ", "are ", "my "],
+ words: [" ", "my"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["my ", "my ", "my "],
+ words: ["my", "my"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["my ", "my ", "my "],
+ words: ["my", " "],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["awards, ", "my ", "awards, "],
+ words: [" ", "awards,"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["awards, ", "awards, ", "awards, "],
+ words: ["awards,", "awards,"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["awards, ", "awards, ", "awards, "],
+ words: ["awards,", "awards,"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["awards, ", "awards, ", "awards, "],
+ words: ["awards,", "awards,"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["awards, ", "awards, ", "awards, "],
+ words: ["awards,", "awards,"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["awards, ", "awards, ", "awards, "],
+ words: ["awards,", "awards,"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["awards, ", "awards, ", "awards, "],
+ words: ["awards,", "awards,"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["awards, ", "awards, ", "awards, "],
+ words: ["awards,", " "],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Mother. ", "awards, ", "Mother. "],
+ words: [" ", "Mother."],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Mother. ", "Mother. ", "Mother. "],
+ words: ["Mother.", "Mother."],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Mother. ", "Mother. ", "Mother. "],
+ words: ["Mother.", "Mother."],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Mother. ", "Mother. ", "Mother. "],
+ words: ["Mother.", "Mother."],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Mother. ", "Mother. ", "Mother. "],
+ words: ["Mother.", "Mother."],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Mother. ", "Mother. ", "Mother. "],
+ words: ["Mother.", "Mother."],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Mother. ", "Mother. ", "Mother. "],
+ words: ["Mother.", "Mother."],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Mother. ", "Mother. ", "Mother. "],
+ words: ["Mother.", " "],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["From ", "Mother. ", "From "],
+ words: [" ", "From"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["From ", "From ", "From "],
+ words: ["From", "From"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["From ", "From ", "From "],
+ words: ["From", "From"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["From ", "From ", "From "],
+ words: ["From", "From"],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["From ", "From ", "From "],
+ words: ["From", " "],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Army.", "From ", "Army."],
+ words: [" ", "Army."],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Army.", "Army.", "Army."],
+ words: ["Army.", "Army."],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Army.", "Army.", "Army."],
+ words: ["Army.", "Army."],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Army.", "Army.", "Army."],
+ words: ["Army.", "Army."],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "These are my awards, Mother. From Army.",
+ lines: ["Army.", "Army.", "Army."],
+ words: ["Army.", "Army."],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "These are my awards, Mother. From Army.",
+ paragraph: "I deceived you, mom.",
+ lines: ["I deceived you, mom.", "Army.", "I deceived you, mom."],
+ words: ["Army.", ""],
+ element: ["AXStaticText",
+ "These are my awards, Mother. From Army.",
+ "These are my awards, Mother. From Army."] },
+ { style: "I ",
+ paragraph: "I deceived you, mom.",
+ lines: ["I deceived you, mom.", "I deceived you, mom.", "I deceived you, mom."],
+ words: ["I", " "],
+ element: ["AXStaticText", "I ", "I "] },
+ { style: "I ",
+ paragraph: "deceived you",
+ lines: ["deceived you", "deceived you", "deceived you"],
+ words: ["deceived", "deceived"],
+ element: ["AXStaticText", "I ", "I "] },
+ { style: "deceived you",
+ paragraph: "deceived you",
+ lines: ["deceived you", "deceived you", "deceived you"],
+ words: ["deceived", "deceived"],
+ element: ["AXTextField", "deceived you", "deceived you"] },
+ { style: "deceived you",
+ paragraph: "deceived you",
+ lines: ["deceived you", "deceived you", "deceived you"],
+ words: ["deceived", "deceived"],
+ element: ["AXTextField", "deceived you", "deceived you"] },
+ { style: "deceived you",
+ paragraph: "deceived you",
+ lines: ["deceived you", "deceived you", "deceived you"],
+ words: ["deceived", "deceived"],
+ element: ["AXTextField", "deceived you", "deceived you"] },
+ { style: "deceived you",
+ paragraph: "deceived you",
+ lines: ["deceived you", "deceived you", "deceived you"],
+ words: ["deceived", "deceived"],
+ element: ["AXTextField", "deceived you", "deceived you"] },
+ { style: "deceived you",
+ paragraph: "deceived you",
+ lines: ["deceived you", "deceived you", "deceived you"],
+ words: ["deceived", "deceived"],
+ element: ["AXTextField", "deceived you", "deceived you"] },
+ { style: "deceived you",
+ paragraph: "deceived you",
+ lines: ["deceived you", "deceived you", "deceived you"],
+ words: ["deceived", "deceived"],
+ element: ["AXTextField", "deceived you", "deceived you"] },
+ { style: "deceived you",
+ paragraph: "deceived you",
+ lines: ["deceived you", "deceived you", "deceived you"],
+ words: ["deceived", "deceived"],
+ element: ["AXTextField", "deceived you", "deceived you"] },
+ { style: "deceived you",
+ paragraph: "deceived you",
+ lines: ["deceived you", "deceived you", "deceived you"],
+ words: ["deceived", " "],
+ element: ["AXTextField", "deceived you", "deceived you"] },
+ { style: "deceived you",
+ paragraph: "deceived you",
+ lines: ["deceived you", "deceived you", "deceived you"],
+ words: [" ", "you"],
+ element: ["AXTextField", "deceived you", "deceived you"] },
+ { style: "deceived you",
+ paragraph: "deceived you",
+ lines: ["deceived you", "deceived you", "deceived you"],
+ words: ["you", "you"],
+ element: ["AXTextField", "deceived you", "deceived you"] },
+ { style: "deceived you",
+ paragraph: "deceived you",
+ lines: ["deceived you", "deceived you", "deceived you"],
+ words: ["you", "you"],
+ element: ["AXTextField", "deceived you", "deceived you"] },
+ { style: "deceived you",
+ paragraph: "I deceived you, mom.",
+ lines: ["I deceived you, mom.", "I deceived you, mom.", "I deceived you, mom."],
+ words: ["", ""],
+ element: ["AXTextField", "deceived you", "deceived you"] },
+ { style: ", mom.",
+ paragraph: "I deceived you, mom.",
+ lines: ["I deceived you, mom.", "I deceived you, mom.", "I deceived you, mom."],
+ words: [",", " "],
+ element: ["AXStaticText", ", mom.", ", mom."] },
+ { style: ", mom.",
+ paragraph: "I deceived you, mom.",
+ lines: ["I deceived you, mom.", "I deceived you, mom.", "I deceived you, mom."],
+ words: [" ", "mom."],
+ element: ["AXStaticText", ", mom.", ", mom."] },
+ { style: ", mom.",
+ paragraph: "I deceived you, mom.",
+ lines: ["I deceived you, mom.", "I deceived you, mom.", "I deceived you, mom."],
+ words: ["mom.", "mom."],
+ element: ["AXStaticText", ", mom.", ", mom."] },
+ { style: ", mom.",
+ paragraph: "I deceived you, mom.",
+ lines: ["I deceived you, mom.", "I deceived you, mom.", "I deceived you, mom."],
+ words: ["mom.", "mom."],
+ element: ["AXStaticText", ", mom.", ", mom."] },
+ { style: ", mom.",
+ paragraph: "I deceived you, mom.",
+ lines: ["I deceived you, mom.", "I deceived you, mom.", "I deceived you, mom."],
+ words: ["mom.", "mom."],
+ element: ["AXStaticText", ", mom.", ", mom."] },
+ { style: ", mom.",
+ paragraph: "I deceived you, mom.",
+ lines: ["I deceived you, mom.", "I deceived you, mom.", "I deceived you, mom."],
+ words: ["mom.", ""],
+ element: ["AXStaticText", ", mom.", ", mom."] }];
+ </script>
+ </body>
+</html>
diff --git a/accessible/tests/browser/mac/doc_tree.xhtml b/accessible/tests/browser/mac/doc_tree.xhtml
new file mode 100644
index 0000000000..d043fa8923
--- /dev/null
+++ b/accessible/tests/browser/mac/doc_tree.xhtml
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <tree id="tree" hidecolumnpicker="true">
+ <treecols>
+ <treecol primary="true" label="Groceries"/>
+ </treecols>
+ <treechildren id="internalTree">
+ <treeitem id="fruits" container="true" open="true">
+ <treerow>
+ <treecell label="Fruits"/>
+ </treerow>
+ <treechildren>
+ <treeitem id="apple">
+ <treerow>
+ <treecell label="Apple"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="orange">
+ <treerow>
+ <treecell label="Orange"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem id="veggies" container="true" open="true">
+ <treerow>
+ <treecell label="Veggies"/>
+ </treerow>
+ <treechildren>
+ <treeitem id="greenVeggies" container="true" open="true">
+ <treerow>
+ <treecell label="Green Veggies"/>
+ </treerow>
+ <treechildren>
+ <treeitem id="spinach">
+ <treerow>
+ <treecell label="Spinach"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="peas">
+ <treerow>
+ <treecell label="Peas"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem id="squash">
+ <treerow>
+ <treecell label="Squash"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ </treechildren>
+ </tree>
+</window>
diff --git a/accessible/tests/browser/mac/head.js b/accessible/tests/browser/mac/head.js
new file mode 100644
index 0000000000..f33f86288b
--- /dev/null
+++ b/accessible/tests/browser/mac/head.js
@@ -0,0 +1,133 @@
+/* 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";
+
+/* exported getNativeInterface, waitForMacEventWithInfo, waitForMacEvent, waitForStateChange,
+ NSRange, NSDictionary, stringForRange, AXTextStateChangeTypeEdit,
+ AXTextEditTypeDelete, AXTextEditTypeTyping, AXTextStateChangeTypeSelectionMove,
+ AXTextStateChangeTypeSelectionExtend, AXTextSelectionDirectionUnknown,
+ AXTextSelectionDirectionPrevious, AXTextSelectionDirectionNext,
+ AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown,
+ AXTextSelectionDirectionBeginning, AXTextSelectionDirectionEnd,
+ AXTextSelectionGranularityCharacter, AXTextSelectionGranularityWord,
+ AXTextSelectionGranularityLine */
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js.
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR }
+);
+
+// AXTextStateChangeType enum values
+const AXTextStateChangeTypeEdit = 1;
+const AXTextStateChangeTypeSelectionMove = 2;
+const AXTextStateChangeTypeSelectionExtend = 3;
+
+// AXTextEditType enum values
+const AXTextEditTypeDelete = 1;
+const AXTextEditTypeTyping = 3;
+
+// AXTextSelectionDirection enum values
+const AXTextSelectionDirectionUnknown = 0;
+const AXTextSelectionDirectionBeginning = 1;
+const AXTextSelectionDirectionEnd = 2;
+const AXTextSelectionDirectionPrevious = 3;
+const AXTextSelectionDirectionNext = 4;
+const AXTextSelectionDirectionDiscontiguous = 5;
+
+// AXTextSelectionGranularity enum values
+const AXTextSelectionGranularityUnknown = 0;
+const AXTextSelectionGranularityCharacter = 1;
+const AXTextSelectionGranularityWord = 2;
+const AXTextSelectionGranularityLine = 3;
+
+function getNativeInterface(accDoc, id) {
+ return findAccessibleChildByID(accDoc, id).nativeInterface.QueryInterface(
+ Ci.nsIAccessibleMacInterface
+ );
+}
+
+function waitForMacEventWithInfo(notificationType, filter) {
+ let filterFunc = (macIface, data) => {
+ if (!filter) {
+ return true;
+ }
+
+ if (typeof filter == "function") {
+ return filter(macIface, data);
+ }
+
+ return macIface.getAttributeValue("AXDOMIdentifier") == filter;
+ };
+
+ return new Promise(resolve => {
+ let eventObserver = {
+ observe(subject, topic, data) {
+ let macEvent = subject.QueryInterface(Ci.nsIAccessibleMacEvent);
+ if (
+ data === notificationType &&
+ filterFunc(macEvent.macIface, macEvent.data)
+ ) {
+ Services.obs.removeObserver(this, "accessible-mac-event");
+ resolve(macEvent);
+ }
+ },
+ };
+ Services.obs.addObserver(eventObserver, "accessible-mac-event");
+ });
+}
+
+function waitForMacEvent(notificationType, filter) {
+ return waitForMacEventWithInfo(notificationType, filter).then(
+ e => e.macIface
+ );
+}
+
+function NSRange(location, length) {
+ return {
+ valueType: "NSRange",
+ value: [location, length],
+ };
+}
+
+function NSDictionary(dict) {
+ return {
+ objectType: "NSDictionary",
+ object: dict,
+ };
+}
+
+function stringForRange(macDoc, range) {
+ if (!range) {
+ return "";
+ }
+
+ let str = macDoc.getParameterizedAttributeValue(
+ "AXStringForTextMarkerRange",
+ range
+ );
+
+ let attrStr = macDoc.getParameterizedAttributeValue(
+ "AXAttributedStringForTextMarkerRange",
+ range
+ );
+
+ // This is a fly-by test to make sure our attributed strings
+ // always match our flat strings.
+ is(
+ attrStr.map(({ string }) => string).join(""),
+ str,
+ "attributed text matches non-attributed text"
+ );
+
+ return str;
+}
diff --git a/accessible/tests/browser/role/browser.ini b/accessible/tests/browser/role/browser.ini
new file mode 100644
index 0000000000..751f24ecc4
--- /dev/null
+++ b/accessible/tests/browser/role/browser.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/browser/*.mjs
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_computedARIARole.js]
diff --git a/accessible/tests/browser/role/browser_computedARIARole.js b/accessible/tests/browser/role/browser_computedARIARole.js
new file mode 100644
index 0000000000..50cfe43c98
--- /dev/null
+++ b/accessible/tests/browser/role/browser_computedARIARole.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";
+
+addAccessibleTask(
+ `
+<div id="ariaButton" role="button">ARIA button</div>
+<div id="ariaLog" role="log">ARIA log</div>
+<div id="ariaMain" role="main">ARIA main</div>
+<div id="ariaRegion" role="region" aria-label="ARIA region">ARIA region</div>
+<nav id="ariaUnnamedRegion" role="region">ARIA unnamed region</nav>
+<div id="ariaDirectory" role="directory">ARIA directory</div>
+<div id="ariaAlertdialog" role="alertdialog">ARIA alertdialog</div>
+<div id="ariaFeed" role="feed">ARIA feed</div>
+<div id="ariaRowgroup" role="rowgroup">ARIA rowgroup</div>
+<div id="ariaSearchbox" role="searchbox">ARIA searchbox</div>
+<div id="ariaUnknown" role="unknown">unknown ARIA role</div>
+<button id="htmlButton">HTML button</button>
+<button id="toggleButton" aria-pressed="true">toggle button</button>
+<main id="htmlMain">HTML main</main>
+<header id="htmlHeader">HTML header</header>
+<section id="htmlSection">
+ <header id="htmlSectionHeader">HTML header inside section</header>
+</section>
+<section id="htmlRegion" aria-label="HTML region">HTML region</section>
+<fieldset id="htmlFieldset">HTML fieldset</fieldset>
+<table>
+ <tbody id="htmlTbody" tabindex="-1"><tr><th>HTML tbody</th></tr></tbody>
+</table>
+<table role="grid">
+ <tr>
+ <td id="htmlGridcell">HTML implicit gridcell</td>
+ </tr>
+</table>
+<div id="htmlDiv">HTML div</div>
+<span id="htmlSpan" aria-label="HTML span">HTML span</span>
+<iframe id="iframe"></iframe>
+ `,
+ async function (browser, docAcc) {
+ function testComputedARIARole(id, role) {
+ const acc = findAccessibleChildByID(docAcc, id);
+ is(acc.computedARIARole, role, `computedARIARole for ${id} is correct`);
+ }
+
+ testComputedARIARole("ariaButton", "button");
+ testComputedARIARole("ariaLog", "log");
+ // Landmarks map to a single Gecko role.
+ testComputedARIARole("ariaMain", "main");
+ testComputedARIARole("ariaRegion", "region");
+ // Unnamed ARIA regions should ignore the ARIA role.
+ testComputedARIARole("ariaUnnamedRegion", "navigation");
+ // The directory ARIA role is an alias of list.
+ testComputedARIARole("ariaDirectory", "list");
+ // alertdialog, feed, rowgroup and searchbox map to a Gecko role, but it
+ // isn't unique.
+ testComputedARIARole("ariaAlertdialog", "alertdialog");
+ testComputedARIARole("ariaFeed", "feed");
+ testComputedARIARole("ariaRowgroup", "rowgroup");
+ testComputedARIARole("ariaSearchbox", "searchbox");
+ testComputedARIARole("ariaUnknown", "generic");
+ testComputedARIARole("htmlButton", "button");
+ // There is only a single ARIA role for buttons, but Gecko uses different
+ // roles depending on states.
+ testComputedARIARole("toggleButton", "button");
+ testComputedARIARole("htmlMain", "main");
+ testComputedARIARole("htmlHeader", "banner");
+ // <section> only maps to the region ARIA role if it has a label.
+ testComputedARIARole("htmlSection", "generic");
+ // <header> only maps to the banner role if it is not a child of a
+ // sectioning element.
+ testComputedARIARole("htmlSectionHeader", "generic");
+ testComputedARIARole("htmlRegion", "region");
+ // Gecko doesn't have a rowgroup role. Ensure we differentiate for
+ // computedARIARole.
+ testComputedARIARole("htmlFieldset", "group");
+ testComputedARIARole("htmlTbody", "rowgroup");
+ // <td> inside <table role="grid"> implicitly maps to ARIA gridcell.
+ testComputedARIARole("htmlGridcell", "gridcell");
+ // Test generics.
+ testComputedARIARole("htmlDiv", "generic");
+ testComputedARIARole("htmlSpan", "generic");
+ // Some roles can't be mapped to ARIA role tokens.
+ testComputedARIARole("iframe", "");
+ },
+ { chrome: true, topLevel: true }
+);
diff --git a/accessible/tests/browser/role/head.js b/accessible/tests/browser/role/head.js
new file mode 100644
index 0000000000..afc50984bd
--- /dev/null
+++ b/accessible/tests/browser/role/head.js
@@ -0,0 +1,18 @@
+/* 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";
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js.
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR }
+);
diff --git a/accessible/tests/browser/scroll/browser.ini b/accessible/tests/browser/scroll/browser.ini
new file mode 100644
index 0000000000..0cae6a7c0e
--- /dev/null
+++ b/accessible/tests/browser/scroll/browser.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_test_zoom_text.js]
+skip-if = os == 'win' # bug 1372296
+[browser_test_scroll_bounds.js]
+[browser_test_scrollTo.js]
+[browser_test_scroll_substring.js]
diff --git a/accessible/tests/browser/scroll/browser_test_scrollTo.js b/accessible/tests/browser/scroll/browser_test_scrollTo.js
new file mode 100644
index 0000000000..43a230b7b8
--- /dev/null
+++ b/accessible/tests/browser/scroll/browser_test_scrollTo.js
@@ -0,0 +1,36 @@
+/* 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";
+
+/**
+ * Test nsIAccessible::scrollTo.
+ */
+addAccessibleTask(
+ `
+<div id="scroller" style="height: 1px; overflow: scroll;">
+ <p id="p1">a</p>
+ <p id="p2">b</p>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const scroller = findAccessibleChildByID(docAcc, "scroller");
+ // scroller can only fit one of p1 or p2, not both.
+ // p1 is on screen already.
+ const p2 = findAccessibleChildByID(docAcc, "p2");
+ info("scrollTo p2");
+ let scrolled = waitForEvent(
+ nsIAccessibleEvent.EVENT_SCROLLING_END,
+ scroller
+ );
+ p2.scrollTo(SCROLL_TYPE_ANYWHERE);
+ await scrolled;
+ const p1 = findAccessibleChildByID(docAcc, "p1");
+ info("scrollTo p1");
+ scrolled = waitForEvent(nsIAccessibleEvent.EVENT_SCROLLING_END, scroller);
+ p1.scrollTo(SCROLL_TYPE_ANYWHERE);
+ await scrolled;
+ },
+ { topLevel: true, iframe: true, remoteIframe: true, chrome: true }
+);
diff --git a/accessible/tests/browser/scroll/browser_test_scroll_bounds.js b/accessible/tests/browser/scroll/browser_test_scroll_bounds.js
new file mode 100644
index 0000000000..bd61340aa6
--- /dev/null
+++ b/accessible/tests/browser/scroll/browser_test_scroll_bounds.js
@@ -0,0 +1,606 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts(
+ { name: "layout.js", dir: MOCHITESTS_DIR },
+ { name: "role.js", dir: MOCHITESTS_DIR }
+);
+requestLongerTimeout(2);
+
+const appUnitsPerDevPixel = 60;
+
+function testCachedScrollPosition(acc, expectedX, expectedY) {
+ let cachedPosition = "";
+ try {
+ cachedPosition = acc.cache.getStringProperty("scroll-position");
+ } catch (e) {
+ // If the key doesn't exist, this means 0, 0.
+ cachedPosition = "0, 0";
+ }
+
+ // The value we retrieve from the cache is in app units, but the values
+ // passed in are in pixels. Since the retrieved value is a string,
+ // and harder to modify, adjust our expected x and y values to match its units.
+ return (
+ cachedPosition ==
+ `${expectedX * appUnitsPerDevPixel}, ${expectedY * appUnitsPerDevPixel}`
+ );
+}
+
+function getCachedBounds(acc) {
+ let cachedBounds = "";
+ try {
+ cachedBounds = acc.cache.getStringProperty("relative-bounds");
+ } catch (e) {
+ ok(false, "Unable to fetch cached bounds from cache!");
+ }
+ return cachedBounds;
+}
+
+/**
+ * Test bounds of accessibles after scrolling
+ */
+addAccessibleTask(
+ `
+ <div id='square' style='height:100px; width:100px; background:green; margin-top:3000px; margin-bottom:4000px;'>
+ </div>
+
+ <div id='rect' style='height:40px; width:200px; background:blue; margin-bottom:3400px'>
+ </div>
+ `,
+ async function (browser, docAcc) {
+ ok(docAcc, "iframe document acc is present");
+ await testBoundsWithContent(docAcc, "square", browser);
+ await testBoundsWithContent(docAcc, "rect", browser);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("square").scrollIntoView();
+ });
+
+ await waitForContentPaint(browser);
+
+ await testBoundsWithContent(docAcc, "square", browser);
+ await testBoundsWithContent(docAcc, "rect", browser);
+
+ // Scroll rect into view, but also make it reflow so we can be sure the
+ // bounds are correct for reflowed frames.
+ await invokeContentTask(browser, [], () => {
+ const rect = content.document.getElementById("rect");
+ rect.scrollIntoView();
+ rect.style.width = "300px";
+ rect.offsetTop; // Flush layout.
+ rect.style.width = "200px";
+ rect.offsetTop; // Flush layout.
+ });
+
+ await waitForContentPaint(browser);
+ await testBoundsWithContent(docAcc, "square", browser);
+ await testBoundsWithContent(docAcc, "rect", browser);
+ },
+ { iframe: true, remoteIframe: true, chrome: true }
+);
+
+/**
+ * Test scroll offset on cached accessibles
+ */
+addAccessibleTask(
+ `
+ <div id='square' style='height:100px; width:100px; background:green; margin-top:3000px; margin-bottom:4000px;'>
+ </div>
+
+ <div id='rect' style='height:40px; width:200px; background:blue; margin-bottom:3400px'>
+ </div>
+ `,
+ async function (browser, docAcc) {
+ ok(docAcc, "iframe document acc is present");
+ await untilCacheOk(
+ () => testCachedScrollPosition(docAcc, 0, 0),
+ "Correct initial scroll position."
+ );
+ const rectAcc = findAccessibleChildByID(docAcc, "rect");
+ const rectInitialBounds = getCachedBounds(rectAcc);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("square").scrollIntoView();
+ });
+
+ await waitForContentPaint(browser);
+
+ // The only content to scroll over is `square`'s top margin
+ // so our scroll offset here should be 3000px
+ await untilCacheOk(
+ () => testCachedScrollPosition(docAcc, 0, 3000),
+ "Correct scroll position after first scroll."
+ );
+
+ // Scroll rect into view, but also make it reflow so we can be sure the
+ // bounds are correct for reflowed frames.
+ await invokeContentTask(browser, [], () => {
+ const rect = content.document.getElementById("rect");
+ rect.scrollIntoView();
+ rect.style.width = "300px";
+ rect.offsetTop;
+ rect.style.width = "200px";
+ });
+
+ await waitForContentPaint(browser);
+ // We have to scroll over `square`'s top margin (3000px),
+ // `square` itself (100px), and `square`'s bottom margin (4000px).
+ // This should give us a 7100px offset.
+ await untilCacheOk(
+ () => testCachedScrollPosition(docAcc, 0, 7100),
+ "Correct final scroll position."
+ );
+ await untilCacheIs(
+ () => getCachedBounds(rectAcc),
+ rectInitialBounds,
+ "Cached relative bounds don't change when scrolling"
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test scroll offset fixed-pos acc accs
+ */
+addAccessibleTask(
+ `
+ <div style="margin-top: 100px; margin-left: 75px; border: 1px solid;">
+ <div id="d" style="position:fixed;">
+ <button id="top">top</button>
+ </div>
+ </div>
+ `,
+ async function (browser, docAcc) {
+ const origTopBounds = await testBoundsWithContent(docAcc, "top", browser);
+ const origDBounds = await testBoundsWithContent(docAcc, "d", browser);
+ const e = waitForEvent(EVENT_REORDER, docAcc);
+ await invokeContentTask(browser, [], () => {
+ for (let i = 0; i < 1000; ++i) {
+ const div = content.document.createElement("div");
+ div.innerHTML = "<button>${i}</button>";
+ content.document.body.append(div);
+ }
+ });
+ await e;
+
+ await invokeContentTask(browser, [], () => {
+ // scroll to the bottom of the page
+ content.window.scrollTo(0, content.document.body.scrollHeight);
+ });
+
+ await waitForContentPaint(browser);
+
+ let newTopBounds = await testBoundsWithContent(docAcc, "top", browser);
+ let newDBounds = await testBoundsWithContent(docAcc, "d", browser);
+ is(
+ origTopBounds[0],
+ newTopBounds[0],
+ "x of fixed elem is unaffected by scrolling"
+ );
+ is(
+ origTopBounds[1],
+ newTopBounds[1],
+ "y of fixed elem is unaffected by scrolling"
+ );
+ is(
+ origTopBounds[2],
+ newTopBounds[2],
+ "width of fixed elem is unaffected by scrolling"
+ );
+ is(
+ origTopBounds[3],
+ newTopBounds[3],
+ "height of fixed elem is unaffected by scrolling"
+ );
+ is(
+ origDBounds[0],
+ newTopBounds[0],
+ "x of fixed elem container is unaffected by scrolling"
+ );
+ is(
+ origDBounds[1],
+ newDBounds[1],
+ "y of fixed elem container is unaffected by scrolling"
+ );
+ is(
+ origDBounds[2],
+ newDBounds[2],
+ "width of fixed container elem is unaffected by scrolling"
+ );
+ is(
+ origDBounds[3],
+ newDBounds[3],
+ "height of fixed container elem is unaffected by scrolling"
+ );
+
+ await invokeContentTask(browser, [], () => {
+ // remove position styling
+ content.document.getElementById("d").style = "";
+ });
+
+ await waitForContentPaint(browser);
+
+ newTopBounds = await testBoundsWithContent(docAcc, "top", browser);
+ newDBounds = await testBoundsWithContent(docAcc, "d", browser);
+ is(
+ origTopBounds[0],
+ newTopBounds[0],
+ "x of non-fixed element remains accurate."
+ );
+ ok(newTopBounds[1] < 0, "y coordinate shows item scrolled off page");
+ is(
+ origTopBounds[2],
+ newTopBounds[2],
+ "width of non-fixed element remains accurate."
+ );
+ is(
+ origTopBounds[3],
+ newTopBounds[3],
+ "height of non-fixed element remains accurate."
+ );
+ is(
+ origDBounds[0],
+ newDBounds[0],
+ "x of non-fixed container element remains accurate."
+ );
+ ok(newDBounds[1] < 0, "y coordinate shows container scrolled off page");
+ // Removing the position styling on this acc causes it to be bound by
+ // its parent's bounding box, which alters its width as a block element.
+ // We don't particularly care about width in this test, so skip it.
+ is(
+ origDBounds[3],
+ newDBounds[3],
+ "height of non-fixed container element remains accurate."
+ );
+
+ await invokeContentTask(browser, [], () => {
+ // re-add position styling
+ content.document.getElementById("d").style = "position:fixed;";
+ });
+
+ await waitForContentPaint(browser);
+
+ newTopBounds = await testBoundsWithContent(docAcc, "top", browser);
+ newDBounds = await testBoundsWithContent(docAcc, "d", browser);
+ is(
+ origTopBounds[0],
+ newTopBounds[0],
+ "x correct when position:fixed is added."
+ );
+ is(
+ origTopBounds[1],
+ newTopBounds[1],
+ "y correct when position:fixed is added."
+ );
+ is(
+ origTopBounds[2],
+ newTopBounds[2],
+ "width correct when position:fixed is added."
+ );
+ is(
+ origTopBounds[3],
+ newTopBounds[3],
+ "height correct when position:fixed is added."
+ );
+ is(
+ origDBounds[0],
+ newDBounds[0],
+ "x of container correct when position:fixed is added."
+ );
+ is(
+ origDBounds[1],
+ newDBounds[1],
+ "y of container correct when position:fixed is added."
+ );
+ is(
+ origDBounds[2],
+ newDBounds[2],
+ "width of container correct when position:fixed is added."
+ );
+ is(
+ origDBounds[3],
+ newDBounds[3],
+ "height of container correct when position:fixed is added."
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test position: fixed for containers that would otherwise be pruned from the
+ * a11y tree.
+ */
+addAccessibleTask(
+ `
+<table id="fixed" role="presentation" style="position: fixed;">
+ <tr><th>fixed</th></tr>
+</table>
+<div id="mutate" role="presentation">mutate</div>
+<hr style="height: 200vh;">
+<p>bottom</p>
+ `,
+ async function (browser, docAcc) {
+ const fixed = findAccessibleChildByID(docAcc, "fixed");
+ ok(fixed, "fixed is accessible");
+ isnot(fixed.role, ROLE_TABLE, "fixed doesn't have ROLE_TABLE");
+ ok(!findAccessibleChildByID(docAcc, "mutate"), "mutate inaccessible");
+ info("Setting position: fixed on mutate");
+ let shown = waitForEvent(EVENT_SHOW, "mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").style.position = "fixed";
+ });
+ await shown;
+ const origFixedBounds = await testBoundsWithContent(
+ docAcc,
+ "fixed",
+ browser
+ );
+ const origMutateBounds = await testBoundsWithContent(
+ docAcc,
+ "mutate",
+ browser
+ );
+ info("Scrolling to bottom of page");
+ await invokeContentTask(browser, [], () => {
+ content.window.scrollTo(0, content.document.body.scrollHeight);
+ });
+ await waitForContentPaint(browser);
+ const newFixedBounds = await testBoundsWithContent(
+ docAcc,
+ "fixed",
+ browser
+ );
+ Assert.deepEqual(
+ newFixedBounds,
+ origFixedBounds,
+ "fixed bounds are unchanged"
+ );
+ const newMutateBounds = await testBoundsWithContent(
+ docAcc,
+ "mutate",
+ browser
+ );
+ Assert.deepEqual(
+ newMutateBounds,
+ origMutateBounds,
+ "mutate bounds are unchanged"
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test scroll offset on sticky-pos acc
+ */
+addAccessibleTask(
+ `
+ <div id="d" style="margin-top: 100px; margin-left: 75px; position:sticky; top:0px;">
+ <button id="top">top</button>
+ </div>
+ `,
+ async function (browser, docAcc) {
+ const containerBounds = await testBoundsWithContent(docAcc, "d", browser);
+ const e = waitForEvent(EVENT_REORDER, docAcc);
+ await invokeContentTask(browser, [], () => {
+ for (let i = 0; i < 1000; ++i) {
+ const div = content.document.createElement("div");
+ div.innerHTML = "<button>${i}</button>";
+ content.document.body.append(div);
+ }
+ });
+ await e;
+ for (let id of ["d", "top"]) {
+ info(`Verifying bounds for acc with ID ${id}`);
+ const origBounds = await testBoundsWithContent(docAcc, id, browser);
+
+ info("Scrolling partially");
+ await invokeContentTask(browser, [], () => {
+ // scroll some of the window
+ content.window.scrollTo(0, 50);
+ });
+
+ await waitForContentPaint(browser);
+
+ let newBounds = await testBoundsWithContent(docAcc, id, browser);
+ is(
+ origBounds[0],
+ newBounds[0],
+ `x coord of sticky element is unaffected by scrolling`
+ );
+ ok(
+ origBounds[1] > newBounds[1] && newBounds[1] >= 0,
+ "sticky element scrolled, but not off the page"
+ );
+ is(
+ origBounds[2],
+ newBounds[2],
+ `width of sticky element is unaffected by scrolling`
+ );
+ is(
+ origBounds[3],
+ newBounds[3],
+ `height of sticky element is unaffected by scrolling`
+ );
+
+ info("Scrolling to bottom");
+ await invokeContentTask(browser, [], () => {
+ // scroll to the bottom of the page
+ content.window.scrollTo(0, content.document.body.scrollHeight);
+ });
+
+ await waitForContentPaint(browser);
+
+ newBounds = await testBoundsWithContent(docAcc, id, browser);
+ is(
+ origBounds[0],
+ newBounds[0],
+ `x coord of sticky element is unaffected by scrolling`
+ );
+ // Subtract margin from container screen coords to get chrome height
+ // which is where our y pos should be
+ is(
+ newBounds[1],
+ containerBounds[1] - 100,
+ "Sticky element is top of screen"
+ );
+ is(
+ origBounds[2],
+ newBounds[2],
+ `width of sticky element is unaffected by scrolling`
+ );
+ is(
+ origBounds[3],
+ newBounds[3],
+ `height of sticky element is unaffected by scrolling`
+ );
+
+ info("Removing position style on container");
+ await invokeContentTask(browser, [], () => {
+ // remove position styling
+ content.document.getElementById("d").style =
+ "margin-top: 100px; margin-left: 75px;";
+ });
+
+ await waitForContentPaint(browser);
+
+ newBounds = await testBoundsWithContent(docAcc, id, browser);
+
+ is(
+ origBounds[0],
+ newBounds[0],
+ `x coord of non-sticky element remains accurate.`
+ );
+ ok(newBounds[1] < 0, "y coordinate shows item scrolled off page");
+
+ // Removing the position styling on this acc causes it to be bound by
+ // its parent's bounding box, which alters its width as a block element.
+ // We don't particularly care about width in this test, so skip it.
+ is(
+ origBounds[3],
+ newBounds[3],
+ `height of non-sticky element remains accurate.`
+ );
+
+ info("Adding position style on container");
+ await invokeContentTask(browser, [], () => {
+ // re-add position styling
+ content.document.getElementById("d").style =
+ "margin-top: 100px; margin-left: 75px; position:sticky; top:0px;";
+ });
+
+ await waitForContentPaint(browser);
+
+ newBounds = await testBoundsWithContent(docAcc, id, browser);
+ is(
+ origBounds[0],
+ newBounds[0],
+ `x coord of sticky element is unaffected by scrolling`
+ );
+ is(
+ newBounds[1],
+ containerBounds[1] - 100,
+ "Sticky element is top of screen"
+ );
+ is(
+ origBounds[2],
+ newBounds[2],
+ `width of sticky element is unaffected by scrolling`
+ );
+ is(
+ origBounds[3],
+ newBounds[3],
+ `height of sticky element is unaffected by scrolling`
+ );
+
+ info("Scrolling back up to test next ID");
+ await invokeContentTask(browser, [], () => {
+ // scroll some of the window
+ content.window.scrollTo(0, 0);
+ });
+ }
+ },
+ { chrome: false, iframe: false, remoteIframe: false }
+);
+
+/**
+ * Test position: sticky for containers that would otherwise be pruned from the
+ * a11y tree.
+ */
+addAccessibleTask(
+ `
+<hr style="height: 100vh;">
+<div id="stickyContainer">
+ <div id="sticky" role="presentation" style="position: sticky; top: 0px;">sticky</div>
+ <hr style="height: 100vh;">
+ <p id="stickyEnd">stickyEnd</p>
+</div>
+<div id="mutateContainer">
+ <div id="mutate" role="presentation" style="top: 0px;">mutate</div>
+ <hr style="height: 100vh;">
+ <p id="mutateEnd">mutateEnd</p>
+</div>
+ `,
+ async function (browser, docAcc) {
+ ok(findAccessibleChildByID(docAcc, "sticky"), "sticky is accessible");
+ info("Scrolling to sticky");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("sticky").scrollIntoView();
+ });
+ await waitForContentPaint(browser);
+ const origStickyBounds = await testBoundsWithContent(
+ docAcc,
+ "sticky",
+ browser
+ );
+ info("Scrolling to stickyEnd");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("stickyEnd").scrollIntoView();
+ });
+ await waitForContentPaint(browser);
+ const newStickyBounds = await testBoundsWithContent(
+ docAcc,
+ "sticky",
+ browser
+ );
+ Assert.deepEqual(
+ newStickyBounds,
+ origStickyBounds,
+ "sticky bounds are unchanged"
+ );
+
+ ok(!findAccessibleChildByID(docAcc, "mutate"), "mutate inaccessible");
+ info("Setting position: sticky on mutate");
+ let shown = waitForEvent(EVENT_SHOW, "mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").style.position = "sticky";
+ });
+ await shown;
+ info("Scrolling to mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").scrollIntoView();
+ });
+ await waitForContentPaint(browser);
+ const origMutateBounds = await testBoundsWithContent(
+ docAcc,
+ "mutate",
+ browser
+ );
+ info("Scrolling to mutateEnd");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutateEnd").scrollIntoView();
+ });
+ await waitForContentPaint(browser);
+ const newMutateBounds = await testBoundsWithContent(
+ docAcc,
+ "mutate",
+ browser
+ );
+ assertBoundsFuzzyEqual(newMutateBounds, origMutateBounds);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/scroll/browser_test_scroll_substring.js b/accessible/tests/browser/scroll/browser_test_scroll_substring.js
new file mode 100644
index 0000000000..e8426d00ca
--- /dev/null
+++ b/accessible/tests/browser/scroll/browser_test_scroll_substring.js
@@ -0,0 +1,67 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/layout.js */
+loadScripts({ name: "layout.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Test nsIAccessibleText::scrollSubstringTo.
+ */
+addAccessibleTask(
+ `
+ <style>
+ @font-face {
+ font-family: Ahem;
+ src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
+ }
+ pre {
+ font: 20px/20px Ahem;
+ height: 40px;
+ overflow-y: scroll;
+ }
+ </style>
+ <pre id="text">
+
+
+
+
+
+It's a jetpack, Michael. What could possibly go wrong?
+
+
+
+
+
+The only thing I found in the fridge was a dead dove in a bag.
+</pre>`,
+ async function (browser, docAcc) {
+ let text = findAccessibleChildByID(docAcc, "text", [nsIAccessibleText]);
+ let [, containerY, , containerHeight] = getBounds(text);
+ let getCharY = () => {
+ let objY = {};
+ text.getCharacterExtents(7, {}, objY, {}, {}, COORDTYPE_SCREEN_RELATIVE);
+ return objY.value;
+ };
+ ok(
+ containerHeight < getCharY(),
+ "Character is outside of container bounds"
+ );
+ text.scrollSubstringTo(7, 8, SCROLL_TYPE_TOP_EDGE);
+
+ await waitForContentPaint(browser);
+ await untilCacheIs(
+ getCharY,
+ containerY,
+ "Character is scrolled to top of container"
+ );
+ },
+ {
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
diff --git a/accessible/tests/browser/scroll/browser_test_zoom_text.js b/accessible/tests/browser/scroll/browser_test_zoom_text.js
new file mode 100644
index 0000000000..4fc0a56b43
--- /dev/null
+++ b/accessible/tests/browser/scroll/browser_test_zoom_text.js
@@ -0,0 +1,145 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/layout.js */
+loadScripts({ name: "layout.js", dir: MOCHITESTS_DIR });
+
+async function runTests(browser, accDoc) {
+ await loadContentScripts(browser, {
+ script: "Layout.sys.mjs",
+ symbol: "Layout",
+ });
+
+ let paragraph = findAccessibleChildByID(accDoc, "paragraph", [
+ nsIAccessibleText,
+ ]);
+ let offset = 64; // beginning of 4th stanza
+
+ let [x /* ,y*/] = getPos(paragraph);
+ let [docX, docY] = getPos(accDoc);
+
+ paragraph.scrollSubstringToPoint(
+ offset,
+ offset,
+ COORDTYPE_SCREEN_RELATIVE,
+ docX,
+ docY
+ );
+
+ await waitForContentPaint(browser);
+ testTextPos(paragraph, offset, [x, docY], COORDTYPE_SCREEN_RELATIVE);
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.Layout.zoomDocument(content.document, 2.0);
+ });
+
+ paragraph = findAccessibleChildByID(accDoc, "paragraph2", [
+ nsIAccessibleText,
+ ]);
+ offset = 52; // // beginning of 4th stanza
+ [x /* ,y*/] = getPos(paragraph);
+ paragraph.scrollSubstringToPoint(
+ offset,
+ offset,
+ COORDTYPE_SCREEN_RELATIVE,
+ docX,
+ docY
+ );
+
+ await waitForContentPaint(browser);
+ testTextPos(paragraph, offset, [x, docY], COORDTYPE_SCREEN_RELATIVE);
+}
+
+/**
+ * Test caching of accessible object states
+ */
+addAccessibleTask(
+ `
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br><hr>
+ <p id='paragraph'>
+ Пошел котик на торжок<br>
+ Купил котик пирожок<br>
+ Пошел котик на улочку<br>
+ Купил котик булочку<br>
+ </p>
+ <hr><br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br><hr>
+ <p id='paragraph2'>
+ Самому ли съесть<br>
+ Либо Сашеньке снесть<br>
+ Я и сам укушу<br>
+ Я и Сашеньке снесу<br>
+ </p>
+ <hr><br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>`,
+ runTests
+);
diff --git a/accessible/tests/browser/scroll/head.js b/accessible/tests/browser/scroll/head.js
new file mode 100644
index 0000000000..afc50984bd
--- /dev/null
+++ b/accessible/tests/browser/scroll/head.js
@@ -0,0 +1,18 @@
+/* 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";
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js.
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR }
+);
diff --git a/accessible/tests/browser/selectable/browser.ini b/accessible/tests/browser/selectable/browser.ini
new file mode 100644
index 0000000000..45e34f82d3
--- /dev/null
+++ b/accessible/tests/browser/selectable/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_test_aria_select.js]
+[browser_test_select.js]
diff --git a/accessible/tests/browser/selectable/browser_test_aria_select.js b/accessible/tests/browser/selectable/browser_test_aria_select.js
new file mode 100644
index 0000000000..f52603d1cb
--- /dev/null
+++ b/accessible/tests/browser/selectable/browser_test_aria_select.js
@@ -0,0 +1,164 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/selectable.js */
+
+// ////////////////////////////////////////////////////////////////////////
+// role="tablist" role="listbox" role="grid" role="tree" role="treegrid"
+addAccessibleTask(
+ `<div role="tablist" id="tablist">
+ <div role="tab">tab1</div>
+ <div role="tab">tab2</div>
+ </div>
+ <div role="listbox" id="listbox">
+ <div role="option">item1</div>
+ <div role="option">item2</div>
+ </div>
+ <div role="grid" id="grid">
+ <div role="row">
+ <span role="gridcell">cell</span>
+ <span role="gridcell">cell</span>
+ </div>
+ <div role="row">
+ <span role="gridcell">cell</span>
+ <span role="gridcell">cell</span>
+ </div>
+ </div>
+ <div role="tree" id="tree">
+ <div role="treeitem">
+ item1
+ <div role="group">
+ <div role="treeitem">item1.1</div>
+ </div>
+ </div>
+ <div>item2</div>
+ </div>
+ <div role="treegrid" id="treegrid">
+ <div role="row" aria-level="1">
+ <span role="gridcell">cell</span>
+ <span role="gridcell">cell</span>
+ </div>
+ <div role="row" aria-level="2">
+ <span role="gridcell">cell</span>
+ <span role="gridcell">cell</span>
+ </div>
+ <div role="row" aria-level="1">
+ <span role="gridcell">cell</span>
+ <span role="gridcell">cell</span>
+ </div>
+ </div>`,
+ async function (browser, docAcc) {
+ info(
+ 'role="tablist" role="listbox" role="grid" role="tree" role="treegrid"'
+ );
+ testSelectableSelection(findAccessibleChildByID(docAcc, "tablist"), []);
+ testSelectableSelection(findAccessibleChildByID(docAcc, "listbox"), []);
+ testSelectableSelection(findAccessibleChildByID(docAcc, "grid"), []);
+ testSelectableSelection(findAccessibleChildByID(docAcc, "tree"), []);
+ testSelectableSelection(findAccessibleChildByID(docAcc, "treegrid"), []);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+// ////////////////////////////////////////////////////////////////////////
+// role="tablist" aria-multiselectable
+addAccessibleTask(
+ `<div role="tablist" id="tablist" aria-multiselectable="true">
+ <div role="tab" id="tab_multi1">tab1</div>
+ <div role="tab" id="tab_multi2">tab2</div>
+ </div>`,
+ async function (browser, docAcc) {
+ info('role="tablist" aria-multiselectable');
+ let tablist = findAccessibleChildByID(docAcc, "tablist", [
+ nsIAccessibleSelectable,
+ ]);
+
+ await testMultiSelectable(tablist, ["tab_multi1", "tab_multi2"]);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+// ////////////////////////////////////////////////////////////////////////
+// role="listbox" aria-multiselectable
+addAccessibleTask(
+ `<div role="listbox" id="listbox" aria-multiselectable="true">
+ <div role="option" id="listbox2_item1">item1</div>
+ <div role="option" id="listbox2_item2">item2</div>
+ </div>`,
+ async function (browser, docAcc) {
+ info('role="listbox" aria-multiselectable');
+ let listbox = findAccessibleChildByID(docAcc, "listbox", [
+ nsIAccessibleSelectable,
+ ]);
+
+ await testMultiSelectable(listbox, ["listbox2_item1", "listbox2_item2"]);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+// ////////////////////////////////////////////////////////////////////////
+// role="grid" aria-multiselectable, selectable children in subtree
+addAccessibleTask(
+ `<table tabindex="0" border="2" cellspacing="0" id="grid" role="grid"
+ aria-multiselectable="true">
+ <thead>
+ <tr>
+ <th tabindex="-1" role="columnheader" id="grid_colhead1"
+ style="width:6em">Entry #</th>
+ <th tabindex="-1" role="columnheader" id="grid_colhead2"
+ style="width:10em">Date</th>
+ <th tabindex="-1" role="columnheader" id="grid_colhead3"
+ style="width:20em">Expense</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td tabindex="-1" role="rowheader" id="grid_rowhead"
+ aria-readonly="true">1</td>
+ <td tabindex="-1" role="gridcell" id="grid_cell1"
+ aria-selected="false">03/14/05</td>
+ <td tabindex="-1" role="gridcell" id="grid_cell2"
+ aria-selected="false">Conference Fee</td>
+ </tr>
+ </tobdy>
+ </table>`,
+ async function (browser, docAcc) {
+ info('role="grid" aria-multiselectable, selectable children in subtree');
+ let grid = findAccessibleChildByID(docAcc, "grid", [
+ nsIAccessibleSelectable,
+ ]);
+
+ await testMultiSelectable(grid, [
+ "grid_colhead1",
+ "grid_colhead2",
+ "grid_colhead3",
+ "grid_rowhead",
+ "grid_cell1",
+ "grid_cell2",
+ ]);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
diff --git a/accessible/tests/browser/selectable/browser_test_select.js b/accessible/tests/browser/selectable/browser_test_select.js
new file mode 100644
index 0000000000..f86a371d81
--- /dev/null
+++ b/accessible/tests/browser/selectable/browser_test_select.js
@@ -0,0 +1,329 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/selectable.js */
+/* import-globals-from ../../mochitest/states.js */
+
+// ////////////////////////////////////////////////////////////////////////
+// select@size="1" aka combobox
+addAccessibleTask(
+ `<select id="combobox">
+ <option id="item1">option1</option>
+ <option id="item2">option2</option>
+ </select>`,
+ async function (browser, docAcc) {
+ info("select@size='1' aka combobox");
+ let combobox = findAccessibleChildByID(docAcc, "combobox");
+ let comboboxList = combobox.firstChild;
+ ok(
+ isAccessible(comboboxList, [nsIAccessibleSelectable]),
+ "No selectable accessible for combobox"
+ );
+
+ let select = getAccessible(comboboxList, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, ["item1"]);
+
+ // select 2nd item
+ let promise = Promise.all([
+ waitForStateChange("item2", STATE_SELECTED, true),
+ waitForStateChange("item1", STATE_SELECTED, false),
+ ]);
+ select.addItemToSelection(1);
+ await promise;
+ testSelectableSelection(select, ["item2"], "addItemToSelection(1): ");
+
+ // unselect 2nd item, 1st item gets selected automatically
+ promise = Promise.all([
+ waitForStateChange("item2", STATE_SELECTED, false),
+ waitForStateChange("item1", STATE_SELECTED, true),
+ ]);
+ select.removeItemFromSelection(1);
+ await promise;
+ testSelectableSelection(select, ["item1"], "removeItemFromSelection(1): ");
+
+ // doesn't change selection
+ is(select.selectAll(), false, "No way to select all items in combobox");
+ testSelectableSelection(select, ["item1"], "selectAll: ");
+
+ // doesn't change selection
+ select.unselectAll();
+ testSelectableSelection(select, ["item1"], "unselectAll: ");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+// ////////////////////////////////////////////////////////////////////////
+// select@size="1" with optgroups
+addAccessibleTask(
+ `<select id="combobox">
+ <option id="item1">option1</option>
+ <optgroup>optgroup
+ <option id="item2">option2</option>
+ </optgroup>
+ </select>`,
+ async function (browser, docAcc) {
+ info("select@size='1' with optgroups");
+ let combobox = findAccessibleChildByID(docAcc, "combobox");
+ let comboboxList = combobox.firstChild;
+ ok(
+ isAccessible(comboboxList, [nsIAccessibleSelectable]),
+ "No selectable accessible for combobox"
+ );
+
+ let select = getAccessible(comboboxList, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, ["item1"]);
+
+ let promise = Promise.all([
+ waitForStateChange("item2", STATE_SELECTED, true),
+ waitForStateChange("item1", STATE_SELECTED, false),
+ ]);
+ select.addItemToSelection(1);
+ await promise;
+ testSelectableSelection(select, ["item2"], "addItemToSelection(1): ");
+
+ promise = Promise.all([
+ waitForStateChange("item2", STATE_SELECTED, false),
+ waitForStateChange("item1", STATE_SELECTED, true),
+ ]);
+ select.removeItemFromSelection(1);
+ await promise;
+ testSelectableSelection(select, ["item1"], "removeItemFromSelection(1): ");
+
+ is(select.selectAll(), false, "No way to select all items in combobox");
+ testSelectableSelection(select, ["item1"]);
+
+ select.unselectAll();
+ testSelectableSelection(select, ["item1"]);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+// ////////////////////////////////////////////////////////////////////////
+// select@size="4" aka single selectable listbox
+addAccessibleTask(
+ `<select id="listbox" size="4">
+ <option id="item1">option1</option>
+ <option id="item2">option2</option>
+ </select>`,
+ async function (browser, docAcc) {
+ info("select@size='4' aka single selectable listbox");
+ let select = findAccessibleChildByID(docAcc, "listbox", [
+ nsIAccessibleSelectable,
+ ]);
+ testSelectableSelection(select, []);
+
+ // select 2nd item
+ let promise = waitForStateChange("item2", STATE_SELECTED, true);
+ select.addItemToSelection(1);
+ await promise;
+ testSelectableSelection(select, ["item2"], "addItemToSelection(1): ");
+
+ // unselect 2nd item, 1st item gets selected automatically
+ promise = waitForStateChange("item2", STATE_SELECTED, false);
+ select.removeItemFromSelection(1);
+ await promise;
+ testSelectableSelection(select, [], "removeItemFromSelection(1): ");
+
+ // doesn't change selection
+ is(
+ select.selectAll(),
+ false,
+ "No way to select all items in single selectable listbox"
+ );
+ testSelectableSelection(select, [], "selectAll: ");
+
+ // doesn't change selection
+ select.unselectAll();
+ testSelectableSelection(select, [], "unselectAll: ");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+// ////////////////////////////////////////////////////////////////////////
+// select@size="4" with optgroups, single selectable
+addAccessibleTask(
+ `<select id="listbox" size="4">
+ <option id="item1">option1</option>
+ <optgroup>optgroup>
+ <option id="item2">option2</option>
+ </optgroup>
+ </select>`,
+ async function (browser, docAcc) {
+ info("select@size='4' with optgroups, single selectable");
+ let select = findAccessibleChildByID(docAcc, "listbox", [
+ nsIAccessibleSelectable,
+ ]);
+ testSelectableSelection(select, []);
+
+ let promise = waitForStateChange("item2", STATE_SELECTED, true);
+ select.addItemToSelection(1);
+ await promise;
+ testSelectableSelection(select, ["item2"]);
+
+ promise = waitForStateChange("item2", STATE_SELECTED, false);
+ select.removeItemFromSelection(1);
+ await promise;
+ testSelectableSelection(select, []);
+
+ is(
+ select.selectAll(),
+ false,
+ "No way to select all items in single selectable listbox"
+ );
+ testSelectableSelection(select, []);
+
+ select.unselectAll();
+ testSelectableSelection(select, []);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+// ////////////////////////////////////////////////////////////////////////
+// select@size="4" multiselect aka listbox
+addAccessibleTask(
+ `<select id="listbox" size="4" multiple="true">
+ <option id="item1">option1</option>
+ <option id="item2">option2</option>
+ </select>`,
+ async function (browser, docAcc) {
+ info("select@size='4' multiselect aka listbox");
+ let select = findAccessibleChildByID(docAcc, "listbox", [
+ nsIAccessibleSelectable,
+ ]);
+ await testMultiSelectable(
+ select,
+ ["item1", "item2"],
+ "select@size='4' multiselect aka listbox "
+ );
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+// ////////////////////////////////////////////////////////////////////////
+// select@size="4" multiselect with optgroups
+addAccessibleTask(
+ `<select id="listbox" size="4" multiple="true">
+ <option id="item1">option1</option>
+ <optgroup>optgroup>
+ <option id="item2">option2</option>
+ </optgroup>
+ </select>`,
+ async function (browser, docAcc) {
+ info("select@size='4' multiselect with optgroups");
+ let select = findAccessibleChildByID(docAcc, "listbox", [
+ nsIAccessibleSelectable,
+ ]);
+ await testMultiSelectable(
+ select,
+ ["item1", "item2"],
+ "select@size='4' multiselect aka listbox "
+ );
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+// ////////////////////////////////////////////////////////////////////////
+// multiselect with coalesced selection event
+addAccessibleTask(
+ `<select id="listbox" size="4" multiple="true">
+ <option id="item1">option1</option>
+ <option id="item2">option2</option>
+ <option id="item3">option3</option>
+ <option id="item4">option4</option>
+ <option id="item5">option5</option>
+ <option id="item6">option6</option>
+ <option id="item7">option7</option>
+ <option id="item8">option8</option>
+ <option id="item9">option9</option>
+ </select>`,
+ async function (browser, docAcc) {
+ info("select@size='4' multiselect with coalesced selection event");
+ let select = findAccessibleChildByID(docAcc, "listbox", [
+ nsIAccessibleSelectable,
+ ]);
+ await testMultiSelectable(
+ select,
+ [
+ "item1",
+ "item2",
+ "item3",
+ "item4",
+ "item5",
+ "item6",
+ "item7",
+ "item8",
+ "item9",
+ ],
+ "select@size='4' multiselect with coalesced selection event "
+ );
+ },
+ {
+ chrome: false,
+ topLevel: true,
+ iframe: false,
+ remoteIframe: false,
+ }
+);
+
+/**
+ * Ensure that we don't assert when dealing with defunct items in selection
+ * events dropped due to coalescence (bug 1800755).
+ */
+addAccessibleTask(
+ `
+<form id="form">
+ <select id="select">
+ <option>
+ <optgroup id="optgroup">
+ <option>
+ </optgroup>
+ </select>
+</form>
+ `,
+ async function (browser, docAcc) {
+ let selected = waitForEvent(EVENT_SELECTION_WITHIN, "select");
+ await invokeContentTask(browser, [], () => {
+ const form = content.document.getElementById("form");
+ const select = content.document.getElementById("select");
+ const optgroup = content.document.getElementById("optgroup");
+ form.reset();
+ select.selectedIndex = 1;
+ select.add(optgroup);
+ select.item(0).remove();
+ });
+ await selected;
+ }
+);
diff --git a/accessible/tests/browser/selectable/head.js b/accessible/tests/browser/selectable/head.js
new file mode 100644
index 0000000000..ccf9e86f77
--- /dev/null
+++ b/accessible/tests/browser/selectable/head.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";
+
+/* exported testMultiSelectable */
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js.
+/* import-globals-from ../../mochitest/selectable.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR },
+ { name: "selectable.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR },
+ { name: "role.js", dir: MOCHITESTS_DIR }
+);
+
+// Handle case where multiple selection change events are coalesced into
+// a SELECTION_WITHIN event. Promise resolves to true in that case.
+function multipleSelectionChanged(widget, changedChildren, selected) {
+ return Promise.race([
+ Promise.all(
+ changedChildren.map(id =>
+ waitForStateChange(id, STATE_SELECTED, selected)
+ )
+ ).then(() => false),
+ waitForEvent(EVENT_SELECTION_WITHIN, widget).then(() => true),
+ ]);
+}
+
+async function testMultiSelectable(widget, selectableChildren, msg = "") {
+ let isRemote = false;
+ try {
+ widget.DOMNode;
+ } catch (e) {
+ isRemote = true;
+ }
+
+ testSelectableSelection(widget, [], `${msg}: initial`);
+
+ let promise = waitForStateChange(selectableChildren[0], STATE_SELECTED, true);
+ widget.addItemToSelection(0);
+ await promise;
+ testSelectableSelection(
+ widget,
+ [selectableChildren[0]],
+ `${msg}: addItemToSelection(0)`
+ );
+
+ promise = waitForStateChange(selectableChildren[0], STATE_SELECTED, false);
+ widget.removeItemFromSelection(0);
+ await promise;
+ testSelectableSelection(widget, [], `${msg}: removeItemFromSelection(0)`);
+
+ promise = multipleSelectionChanged(widget, selectableChildren, true);
+ let success = widget.selectAll();
+ ok(success, `${msg}: selectAll success`);
+ await promise;
+ if (isRemote) {
+ await untilCacheIs(
+ () => widget.selectedItemCount,
+ selectableChildren.length,
+ "Selection cache updated"
+ );
+ }
+ testSelectableSelection(widget, selectableChildren, `${msg}: selectAll`);
+
+ promise = multipleSelectionChanged(widget, selectableChildren, false);
+ widget.unselectAll();
+ await promise;
+ if (isRemote) {
+ await untilCacheIs(
+ () => widget.selectedItemCount,
+ 0,
+ "Selection cache updated"
+ );
+ }
+ testSelectableSelection(widget, [], `${msg}: selectAll`);
+}
diff --git a/accessible/tests/browser/shared-head.js b/accessible/tests/browser/shared-head.js
new file mode 100644
index 0000000000..9037802728
--- /dev/null
+++ b/accessible/tests/browser/shared-head.js
@@ -0,0 +1,918 @@
+/* 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";
+
+/* import-globals-from ../mochitest/common.js */
+/* import-globals-from ../mochitest/layout.js */
+/* import-globals-from ../mochitest/promisified-events.js */
+
+/* exported Logger, MOCHITESTS_DIR, invokeSetAttribute, invokeFocus,
+ invokeSetStyle, getAccessibleDOMNodeID, getAccessibleTagName,
+ addAccessibleTask, findAccessibleChildByID, isDefunct,
+ CURRENT_CONTENT_DIR, loadScripts, loadContentScripts, snippetToURL,
+ Cc, Cu, arrayFromChildren, forceGC, contentSpawnMutation,
+ DEFAULT_IFRAME_ID, DEFAULT_IFRAME_DOC_BODY_ID, invokeContentTask,
+ matchContentDoc, currentContentDoc, getContentDPR,
+ waitForImageMap, getContentBoundsForDOMElm, untilCacheIs, untilCacheOk, testBoundsWithContent, waitForContentPaint */
+
+const CURRENT_FILE_DIR = "/browser/accessible/tests/browser/";
+
+/**
+ * Current browser test directory path used to load subscripts.
+ */
+const CURRENT_DIR = `chrome://mochitests/content${CURRENT_FILE_DIR}`;
+/**
+ * A11y mochitest directory where we find common files used in both browser and
+ * plain tests.
+ */
+const MOCHITESTS_DIR =
+ "chrome://mochitests/content/a11y/accessible/tests/mochitest/";
+/**
+ * A base URL for test files used in content.
+ */
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const CURRENT_CONTENT_DIR = `http://example.com${CURRENT_FILE_DIR}`;
+
+const LOADED_CONTENT_SCRIPTS = new Map();
+
+const DEFAULT_CONTENT_DOC_BODY_ID = "body";
+const DEFAULT_IFRAME_ID = "default-iframe-id";
+const DEFAULT_IFRAME_DOC_BODY_ID = "default-iframe-body-id";
+
+const HTML_MIME_TYPE = "text/html";
+const XHTML_MIME_TYPE = "application/xhtml+xml";
+
+function loadHTMLFromFile(path) {
+ // Load the HTML to return in the response from file.
+ // Since it's relative to the cwd of the test runner, we start there and
+ // append to get to the actual path of the file.
+ const testHTMLFile = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+ const dirs = path.split("/");
+ for (let i = 0; i < dirs.length; i++) {
+ testHTMLFile.append(dirs[i]);
+ }
+
+ const testHTMLFileStream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ testHTMLFileStream.init(testHTMLFile, -1, 0, 0);
+ const testHTML = NetUtil.readInputStreamToString(
+ testHTMLFileStream,
+ testHTMLFileStream.available()
+ );
+
+ return testHTML;
+}
+
+let gIsIframe = false;
+let gIsRemoteIframe = false;
+
+function currentContentDoc() {
+ return gIsIframe ? DEFAULT_IFRAME_DOC_BODY_ID : DEFAULT_CONTENT_DOC_BODY_ID;
+}
+
+/**
+ * Accessible event match criteria based on the id of the current document
+ * accessible in test.
+ *
+ * @param {nsIAccessibleEvent} event
+ * Accessible event to be tested for a match.
+ *
+ * @return {Boolean}
+ * True if accessible event's accessible object ID matches current
+ * document accessible ID.
+ */
+function matchContentDoc(event) {
+ return getAccessibleDOMNodeID(event.accessible) === currentContentDoc();
+}
+
+/**
+ * Used to dump debug information.
+ */
+let Logger = {
+ /**
+ * Set up this variable to dump log messages into console.
+ */
+ dumpToConsole: false,
+
+ /**
+ * Set up this variable to dump log messages into error console.
+ */
+ dumpToAppConsole: false,
+
+ /**
+ * Return true if dump is enabled.
+ */
+ get enabled() {
+ return this.dumpToConsole || this.dumpToAppConsole;
+ },
+
+ /**
+ * Dump information into console if applicable.
+ */
+ log(msg) {
+ if (this.enabled) {
+ this.logToConsole(msg);
+ this.logToAppConsole(msg);
+ }
+ },
+
+ /**
+ * Log message to console.
+ */
+ logToConsole(msg) {
+ if (this.dumpToConsole) {
+ dump(`\n${msg}\n`);
+ }
+ },
+
+ /**
+ * Log message to error console.
+ */
+ logToAppConsole(msg) {
+ if (this.dumpToAppConsole) {
+ Services.console.logStringMessage(`${msg}`);
+ }
+ },
+};
+
+/**
+ * Asynchronously set or remove content element's attribute (in content process
+ * if e10s is enabled).
+ * @param {Object} browser current "tabbrowser" element
+ * @param {String} id content element id
+ * @param {String} attr attribute name
+ * @param {String?} value optional attribute value, if not present, remove
+ * attribute
+ * @return {Promise} promise indicating that attribute is set/removed
+ */
+function invokeSetAttribute(browser, id, attr, value) {
+ if (value) {
+ Logger.log(`Setting ${attr} attribute to ${value} for node with id: ${id}`);
+ } else {
+ Logger.log(`Removing ${attr} attribute from node with id: ${id}`);
+ }
+
+ return invokeContentTask(
+ browser,
+ [id, attr, value],
+ (contentId, contentAttr, contentValue) => {
+ let elm = content.document.getElementById(contentId);
+ if (contentValue) {
+ elm.setAttribute(contentAttr, contentValue);
+ } else {
+ elm.removeAttribute(contentAttr);
+ }
+ }
+ );
+}
+
+/**
+ * Asynchronously set or remove content element's style (in content process if
+ * e10s is enabled, or in fission process if fission is enabled and a fission
+ * frame is present).
+ * @param {Object} browser current "tabbrowser" element
+ * @param {String} id content element id
+ * @param {String} aStyle style property name
+ * @param {String?} aValue optional style property value, if not present,
+ * remove style
+ * @return {Promise} promise indicating that style is set/removed
+ */
+function invokeSetStyle(browser, id, style, value) {
+ if (value) {
+ Logger.log(`Setting ${style} style to ${value} for node with id: ${id}`);
+ } else {
+ Logger.log(`Removing ${style} style from node with id: ${id}`);
+ }
+
+ return invokeContentTask(
+ browser,
+ [id, style, value],
+ (contentId, contentStyle, contentValue) => {
+ const elm = content.document.getElementById(contentId);
+ if (contentValue) {
+ elm.style[contentStyle] = contentValue;
+ } else {
+ delete elm.style[contentStyle];
+ }
+ }
+ );
+}
+
+/**
+ * Asynchronously set focus on a content element (in content process if e10s is
+ * enabled, or in fission process if fission is enabled and a fission frame is
+ * present).
+ * @param {Object} browser current "tabbrowser" element
+ * @param {String} id content element id
+ * @return {Promise} promise indicating that focus is set
+ */
+function invokeFocus(browser, id) {
+ Logger.log(`Setting focus on a node with id: ${id}`);
+
+ return invokeContentTask(browser, [id], contentId => {
+ const elm = content.document.getElementById(contentId);
+ if (elm.editor) {
+ elm.selectionStart = elm.selectionEnd = elm.value.length;
+ }
+
+ elm.focus();
+ });
+}
+
+/**
+ * Get DPR for a specific content window.
+ * @param browser
+ * Browser for which we want its content window's DPR reported.
+ *
+ * @return {Promise}
+ * Promise with the value that resolves to the devicePixelRatio of the
+ * content window of a given browser.
+ *
+ */
+function getContentDPR(browser) {
+ return invokeContentTask(browser, [], () => content.window.devicePixelRatio);
+}
+
+/**
+ * Asynchronously perform a task in content (in content process if e10s is
+ * enabled, or in fission process if fission is enabled and a fission frame is
+ * present).
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Array} args arguments for the content task
+ * @param {Function} task content task function
+ *
+ * @return {Promise} promise indicating that content task is complete
+ */
+function invokeContentTask(browser, args, task) {
+ return SpecialPowers.spawn(
+ browser,
+ [DEFAULT_IFRAME_ID, task.toString(), ...args],
+ (iframeId, contentTask, ...contentArgs) => {
+ // eslint-disable-next-line no-eval
+ const runnableTask = eval(`
+ (() => {
+ return (${contentTask});
+ })();`);
+ const frame = content.document.getElementById(iframeId);
+
+ return frame
+ ? SpecialPowers.spawn(frame, contentArgs, runnableTask)
+ : runnableTask.call(this, ...contentArgs);
+ }
+ );
+}
+
+/**
+ * Compare process ID's between the top level content process and possible
+ * remote/local iframe proccess.
+ * @param {Object} browser
+ * Top level browser object for a tab.
+ * @param {Boolean} isRemote
+ * Indicates if we expect the iframe content process to be remote or not.
+ */
+async function comparePIDs(browser, isRemote) {
+ function getProcessID() {
+ return Services.appinfo.processID;
+ }
+
+ const contentPID = await SpecialPowers.spawn(browser, [], getProcessID);
+ const iframePID = await invokeContentTask(browser, [], getProcessID);
+ is(
+ isRemote,
+ contentPID !== iframePID,
+ isRemote
+ ? "Remote IFRAME is in a different process."
+ : "IFRAME is in the same process."
+ );
+}
+
+/**
+ * Load a list of scripts into the test
+ * @param {Array} scripts a list of scripts to load
+ */
+function loadScripts(...scripts) {
+ for (let script of scripts) {
+ let path =
+ typeof script === "string"
+ ? `${CURRENT_DIR}${script}`
+ : `${script.dir}${script.name}`;
+ Services.scriptloader.loadSubScript(path, this);
+ }
+}
+
+/**
+ * Load a list of scripts into target's content.
+ * @param {Object} target
+ * target for loading scripts into
+ * @param {Array} scripts
+ * a list of scripts to load into content
+ */
+async function loadContentScripts(target, ...scripts) {
+ for (let { script, symbol } of scripts) {
+ let contentScript = `${CURRENT_DIR}${script}`;
+ let loadedScriptSet = LOADED_CONTENT_SCRIPTS.get(contentScript);
+ if (!loadedScriptSet) {
+ loadedScriptSet = new WeakSet();
+ LOADED_CONTENT_SCRIPTS.set(contentScript, loadedScriptSet);
+ } else if (loadedScriptSet.has(target)) {
+ continue;
+ }
+
+ await SpecialPowers.spawn(
+ target,
+ [contentScript, symbol],
+ async (_contentScript, importSymbol) => {
+ let module = ChromeUtils.importESModule(_contentScript);
+ content.window[importSymbol] = module[importSymbol];
+ }
+ );
+ loadedScriptSet.add(target);
+ }
+}
+
+function attrsToString(attrs) {
+ return Object.entries(attrs)
+ .map(([attr, value]) => `${attr}=${JSON.stringify(value)}`)
+ .join(" ");
+}
+
+function wrapWithIFrame(doc, options = {}) {
+ let src;
+ let { iframeAttrs = {}, iframeDocBodyAttrs = {} } = options;
+ iframeDocBodyAttrs = {
+ id: DEFAULT_IFRAME_DOC_BODY_ID,
+ ...iframeDocBodyAttrs,
+ };
+ if (options.remoteIframe) {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ const srcURL = new URL(`http://example.net/document-builder.sjs`);
+ if (doc.endsWith("html")) {
+ srcURL.searchParams.append("file", `${CURRENT_FILE_DIR}${doc}`);
+ } else {
+ srcURL.searchParams.append(
+ "html",
+ `<!doctype html>
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Fission Test</title>
+ </head>
+ <body ${attrsToString(iframeDocBodyAttrs)}>${doc}</body>
+ </html>`
+ );
+ }
+ src = srcURL.href;
+ } else {
+ const mimeType = doc.endsWith("xhtml") ? XHTML_MIME_TYPE : HTML_MIME_TYPE;
+ if (doc.endsWith("html")) {
+ doc = loadHTMLFromFile(`${CURRENT_FILE_DIR}${doc}`);
+ doc = doc.replace(
+ /<body[.\s\S]*?>/,
+ `<body ${attrsToString(iframeDocBodyAttrs)}>`
+ );
+ } else {
+ doc = `<!doctype html>
+ <body ${attrsToString(iframeDocBodyAttrs)}>${doc}</body>`;
+ }
+
+ src = `data:${mimeType};charset=utf-8,${encodeURIComponent(doc)}`;
+ }
+
+ iframeAttrs = {
+ id: DEFAULT_IFRAME_ID,
+ src,
+ ...iframeAttrs,
+ };
+
+ return `<iframe ${attrsToString(iframeAttrs)}/>`;
+}
+
+/**
+ * Takes an HTML snippet or HTML doc url and returns an encoded URI for a full
+ * document with the snippet or the URL as a source for the IFRAME.
+ * @param {String} doc
+ * a markup snippet or url.
+ * @param {Object} options (see options in addAccessibleTask).
+ *
+ * @return {String}
+ * a base64 encoded data url of the document container the snippet.
+ **/
+function snippetToURL(doc, options = {}) {
+ const { contentDocBodyAttrs = {} } = options;
+ const attrs = {
+ id: DEFAULT_CONTENT_DOC_BODY_ID,
+ ...contentDocBodyAttrs,
+ };
+
+ if (gIsIframe) {
+ doc = wrapWithIFrame(doc, options);
+ }
+
+ const encodedDoc = encodeURIComponent(
+ `<!doctype html>
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body ${attrsToString(attrs)}>${doc}</body>
+ </html>`
+ );
+
+ return `data:text/html;charset=utf-8,${encodedDoc}`;
+}
+
+function accessibleTask(doc, task, options = {}) {
+ return async function () {
+ gIsRemoteIframe = options.remoteIframe;
+ gIsIframe = options.iframe || gIsRemoteIframe;
+ let url;
+ if (options.chrome && doc.endsWith("html")) {
+ // Load with a chrome:// URL so this loads as a chrome document in the
+ // parent process.
+ url = `${CURRENT_DIR}${doc}`;
+ } else if (doc.endsWith("html") && !gIsIframe) {
+ url = `${CURRENT_CONTENT_DIR}${doc}`;
+ } else {
+ url = snippetToURL(doc, options);
+ }
+
+ registerCleanupFunction(() => {
+ for (let observer of Services.obs.enumerateObservers(
+ "accessible-event"
+ )) {
+ Services.obs.removeObserver(observer, "accessible-event");
+ }
+ });
+
+ let onContentDocLoad;
+ if (!options.chrome) {
+ onContentDocLoad = waitForEvent(
+ EVENT_DOCUMENT_LOAD_COMPLETE,
+ DEFAULT_CONTENT_DOC_BODY_ID
+ );
+ }
+
+ let onIframeDocLoad;
+ if (options.remoteIframe && !options.skipFissionDocLoad) {
+ onIframeDocLoad = waitForEvent(
+ EVENT_DOCUMENT_LOAD_COMPLETE,
+ DEFAULT_IFRAME_DOC_BODY_ID
+ );
+ }
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ // For chrome, we need a non-remote browser.
+ opening: !options.chrome
+ ? url
+ : () => {
+ // Passing forceNotRemote: true still sets maychangeremoteness,
+ // which will cause data: URIs to load remotely. There's no way to
+ // avoid this with gBrowser or BrowserTestUtils. Therefore, we
+ // load a blank document initially and replace it below.
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "about:blank",
+ {
+ forceNotRemote: true,
+ }
+ );
+ },
+ },
+ async function (browser) {
+ registerCleanupFunction(() => {
+ if (browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ if (tab && !tab.closing && tab.linkedBrowser) {
+ gBrowser.removeTab(tab);
+ }
+ }
+ });
+
+ if (options.chrome) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.allow_unsafe_parent_loads", true]],
+ });
+ // Ensure this never becomes a remote browser.
+ browser.removeAttribute("maychangeremoteness");
+ // Now we can load our page without it becoming remote.
+ browser.setAttribute("src", url);
+ }
+
+ await SimpleTest.promiseFocus(browser);
+
+ if (options.chrome) {
+ ok(!browser.isRemoteBrowser, "Not remote browser");
+ } else if (Services.appinfo.browserTabsRemoteAutostart) {
+ ok(browser.isRemoteBrowser, "Actually remote browser");
+ }
+
+ let docAccessible;
+ if (options.chrome) {
+ // Chrome documents don't fire DOCUMENT_LOAD_COMPLETE. Instead, wait
+ // until we can get the DocAccessible and it doesn't have the busy
+ // state.
+ await BrowserTestUtils.waitForCondition(() => {
+ docAccessible = getAccessible(browser.contentWindow.document);
+ if (!docAccessible) {
+ return false;
+ }
+ const state = {};
+ docAccessible.getState(state, {});
+ return !(state.value & STATE_BUSY);
+ });
+ } else {
+ ({ accessible: docAccessible } = await onContentDocLoad);
+ }
+ let iframeDocAccessible;
+ if (gIsIframe) {
+ if (!options.skipFissionDocLoad) {
+ await comparePIDs(browser, options.remoteIframe);
+ iframeDocAccessible = onIframeDocLoad
+ ? (await onIframeDocLoad).accessible
+ : findAccessibleChildByID(docAccessible, DEFAULT_IFRAME_ID)
+ .firstChild;
+ }
+ }
+
+ await loadContentScripts(browser, {
+ script: "Common.sys.mjs",
+ symbol: "CommonUtils",
+ });
+
+ await task(
+ browser,
+ iframeDocAccessible || docAccessible,
+ iframeDocAccessible && docAccessible
+ );
+ }
+ );
+ };
+}
+
+/**
+ * A wrapper around browser test add_task that triggers an accessible test task
+ * as a new browser test task with given document, data URL or markup snippet.
+ * @param {String} doc
+ * URL (relative to current directory) or data URL or markup snippet
+ * that is used to test content with
+ * @param {Function|AsyncFunction} task
+ * a generator or a function with tests to run
+ * @param {null|Object} options
+ * Options for running accessibility test tasks:
+ * - {Boolean} topLevel
+ * Flag to run the test with content in the top level content process.
+ * Default is true.
+ * - {Boolean} chrome
+ * Flag to run the test with content as a chrome document in the
+ * parent process. Default is false. Although url can be a markup
+ * snippet, a snippet cannot be used for XUL content. To load XUL,
+ * specify a relative URL to a XUL document. In that case, toplevel
+ * should usually be set to false, since XUL documents don't work in
+ * content processes.
+ * - {Boolean} iframe
+ * Flag to run the test with content wrapped in an iframe. Default is
+ * false.
+ * - {Boolean} remoteIframe
+ * Flag to run the test with content wrapped in a remote iframe.
+ * Default is false.
+ * - {Object} iframeAttrs
+ * A map of attribute/value pairs to be applied to IFRAME element.
+ * - {Boolean} skipFissionDocLoad
+ * If true, the test will not wait for iframe document document
+ * loaded event (useful for when IFRAME is initially hidden).
+ * - {Object} contentDocBodyAttrs
+ * a set of attributes to be applied to a top level content document
+ * body
+ * - {Object} iframeDocBodyAttrs
+ * a set of attributes to be applied to a iframe content document body
+ */
+function addAccessibleTask(doc, task, options = {}) {
+ const {
+ topLevel = true,
+ chrome = false,
+ iframe = false,
+ remoteIframe = false,
+ } = options;
+ if (topLevel) {
+ add_task(
+ accessibleTask(doc, task, {
+ ...options,
+ chrome: false,
+ iframe: false,
+ remoteIframe: false,
+ })
+ );
+ }
+
+ if (chrome) {
+ add_task(
+ accessibleTask(doc, task, {
+ ...options,
+ topLevel: false,
+ iframe: false,
+ remoteIframe: false,
+ })
+ );
+ }
+
+ if (iframe) {
+ add_task(
+ accessibleTask(doc, task, {
+ ...options,
+ topLevel: false,
+ chrome: false,
+ remoteIframe: false,
+ })
+ );
+ }
+
+ if (gFissionBrowser && remoteIframe) {
+ add_task(
+ accessibleTask(doc, task, {
+ ...options,
+ topLevel: false,
+ chrome: false,
+ iframe: false,
+ })
+ );
+ }
+}
+
+/**
+ * Check if an accessible object has a defunct test.
+ * @param {nsIAccessible} accessible object to test defunct state for
+ * @return {Boolean} flag indicating defunct state
+ */
+function isDefunct(accessible) {
+ let defunct = false;
+ try {
+ let extState = {};
+ accessible.getState({}, extState);
+ defunct = extState.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT;
+ } catch (x) {
+ defunct = true;
+ } finally {
+ if (defunct) {
+ Logger.log(`Defunct accessible: ${prettyName(accessible)}`);
+ }
+ }
+ return defunct;
+}
+
+/**
+ * Get the DOM tag name for a given accessible.
+ * @param {nsIAccessible} accessible accessible
+ * @return {String?} tag name of associated DOM node, or null.
+ */
+function getAccessibleTagName(acc) {
+ try {
+ return acc.attributes.getStringProperty("tag");
+ } catch (e) {
+ return null;
+ }
+}
+
+/**
+ * Traverses the accessible tree starting from a given accessible as a root and
+ * looks for an accessible that matches based on its DOMNode id.
+ * @param {nsIAccessible} accessible root accessible
+ * @param {String} id id to look up accessible for
+ * @param {Array?} interfaces the interface or an array interfaces
+ * to query it/them from obtained accessible
+ * @return {nsIAccessible?} found accessible if any
+ */
+function findAccessibleChildByID(accessible, id, interfaces) {
+ if (getAccessibleDOMNodeID(accessible) === id) {
+ return queryInterfaces(accessible, interfaces);
+ }
+ for (let i = 0; i < accessible.children.length; ++i) {
+ let found = findAccessibleChildByID(accessible.getChildAt(i), id);
+ if (found) {
+ return queryInterfaces(found, interfaces);
+ }
+ }
+ return null;
+}
+
+function queryInterfaces(accessible, interfaces) {
+ if (!interfaces) {
+ return accessible;
+ }
+
+ for (let iface of interfaces.filter(i => !(accessible instanceof i))) {
+ try {
+ accessible.QueryInterface(iface);
+ } catch (e) {
+ ok(false, "Can't query " + iface);
+ }
+ }
+
+ return accessible;
+}
+
+function arrayFromChildren(accessible) {
+ return Array.from({ length: accessible.childCount }, (c, i) =>
+ accessible.getChildAt(i)
+ );
+}
+
+/**
+ * Force garbage collection.
+ */
+function forceGC() {
+ SpecialPowers.gc();
+ SpecialPowers.forceShrinkingGC();
+ SpecialPowers.forceCC();
+ SpecialPowers.gc();
+ SpecialPowers.forceShrinkingGC();
+ SpecialPowers.forceCC();
+}
+
+/*
+ * This function spawns a content task and awaits expected mutation events from
+ * various content changes. It's good at catching events we did *not* expect. We
+ * do this advancing the layout refresh to flush the relocations/insertions
+ * queue.
+ */
+async function contentSpawnMutation(browser, waitFor, func, args = []) {
+ let onReorders = waitForEvents({ expected: waitFor.expected || [] });
+ let unexpectedListener = new UnexpectedEvents(waitFor.unexpected || []);
+
+ function tick() {
+ // 100ms is an arbitrary positive number to advance the clock.
+ // We don't need to advance the clock for a11y mutations, but other
+ // tick listeners may depend on an advancing clock with each refresh.
+ content.windowUtils.advanceTimeAndRefresh(100);
+ }
+
+ // This stops the refreh driver from doing its regular ticks, and leaves
+ // us in control.
+ await invokeContentTask(browser, [], tick);
+
+ // Perform the tree mutation.
+ await invokeContentTask(browser, args, func);
+
+ // Do one tick to flush our queue (insertions, relocations, etc.)
+ await invokeContentTask(browser, [], tick);
+
+ let events = await onReorders;
+
+ unexpectedListener.stop();
+
+ // Go back to normal refresh driver ticks.
+ await invokeContentTask(browser, [], function () {
+ content.windowUtils.restoreNormalRefresh();
+ });
+
+ return events;
+}
+
+async function waitForImageMap(browser, accDoc, id = "imgmap") {
+ let acc = findAccessibleChildByID(accDoc, id);
+
+ if (!acc) {
+ const onShow = waitForEvent(EVENT_SHOW, id);
+ acc = (await onShow).accessible;
+ }
+
+ if (acc.firstChild) {
+ return;
+ }
+
+ const onReorder = waitForEvent(EVENT_REORDER, id);
+ // Wave over image map
+ await invokeContentTask(browser, [id], contentId => {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.synthesizeMouse(
+ content.document.getElementById(contentId),
+ 10,
+ 10,
+ { type: "mousemove" },
+ content
+ );
+ });
+ await onReorder;
+}
+
+async function getContentBoundsForDOMElm(browser, id) {
+ return invokeContentTask(browser, [id], contentId => {
+ const { Layout: LayoutUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs"
+ );
+
+ return LayoutUtils.getBoundsForDOMElm(contentId, content.document);
+ });
+}
+
+const CACHE_WAIT_TIMEOUT_MS = 5000;
+
+/**
+ * Wait for a predicate to be true after cache ticks.
+ * This function takes two callbacks, the condition is evaluated
+ * by calling the first callback with the arguments returned by the second.
+ * This allows us to asynchronously return the arguments as a result if the condition
+ * of the first callback is met, or if it times out. The returned arguments can then
+ * be used to record a pass or fail in the test.
+ */
+function untilCacheCondition(conditionFunc, argsFunc) {
+ return new Promise((resolve, reject) => {
+ let args = argsFunc();
+ if (conditionFunc(...args)) {
+ resolve(args);
+ return;
+ }
+
+ let cacheObserver = {
+ observe(subject) {
+ args = argsFunc();
+ if (conditionFunc(...args)) {
+ clearTimeout(this.timer);
+ Services.obs.removeObserver(this, "accessible-cache");
+ resolve(args);
+ }
+ },
+
+ timeout() {
+ ok(false, "Timeout while waiting for cache update");
+ Services.obs.removeObserver(this, "accessible-cache");
+ args = argsFunc();
+ resolve(args);
+ },
+ };
+
+ cacheObserver.timer = setTimeout(
+ cacheObserver.timeout.bind(cacheObserver),
+ CACHE_WAIT_TIMEOUT_MS
+ );
+ Services.obs.addObserver(cacheObserver, "accessible-cache");
+ });
+}
+
+function untilCacheOk(conditionFunc, message) {
+ return untilCacheCondition(
+ (v, _unusedMessage) => v,
+ () => [conditionFunc(), message]
+ ).then(([v, msg]) => ok(v, msg));
+}
+
+function untilCacheIs(retrievalFunc, expected, message) {
+ return untilCacheCondition(
+ (a, b, _unusedMessage) => Object.is(a, b),
+ () => [retrievalFunc(), expected, message]
+ ).then(([got, exp, msg]) => is(got, exp, msg));
+}
+
+async function waitForContentPaint(browser) {
+ await SpecialPowers.spawn(browser, [], () => {
+ return new Promise(function (r) {
+ content.requestAnimationFrame(() => content.setTimeout(r));
+ });
+ });
+}
+
+// Returns true if both number arrays match within `FUZZ`.
+function areBoundsFuzzyEqual(actual, expected) {
+ const FUZZ = 1;
+ return actual
+ .map((val, i) => Math.abs(val - expected[i]) <= FUZZ)
+ .reduce((a, b) => a && b, true);
+}
+
+function assertBoundsFuzzyEqual(actual, expected) {
+ ok(
+ areBoundsFuzzyEqual(actual, expected),
+ `${actual} fuzzily matches expected ${expected}`
+ );
+}
+
+async function testBoundsWithContent(iframeDocAcc, id, browser) {
+ // Retrieve layout bounds from content
+ let expectedBounds = await invokeContentTask(browser, [id], _id => {
+ const { Layout: LayoutUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs"
+ );
+ return LayoutUtils.getBoundsForDOMElm(_id, content.document);
+ });
+
+ function isWithinExpected(bounds) {
+ return areBoundsFuzzyEqual(bounds, expectedBounds);
+ }
+
+ const acc = findAccessibleChildByID(iframeDocAcc, id);
+ let [accBounds] = await untilCacheCondition(isWithinExpected, () => [
+ getBounds(acc),
+ ]);
+
+ assertBoundsFuzzyEqual(accBounds, expectedBounds);
+
+ return accBounds;
+}
diff --git a/accessible/tests/browser/states/browser.ini b/accessible/tests/browser/states/browser.ini
new file mode 100644
index 0000000000..f625d7aeb5
--- /dev/null
+++ b/accessible/tests/browser/states/browser.ini
@@ -0,0 +1,19 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/browser/*.jsm
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_test_link.js]
+https_first_disabled = true
+skip-if = verify
+[browser_test_select_visibility.js]
+https_first_disabled = true
+[browser_test_visibility.js]
+https_first_disabled = true
+[browser_test_visibility_2.js]
+https_first_disabled = true
diff --git a/accessible/tests/browser/states/browser_test_link.js b/accessible/tests/browser/states/browser_test_link.js
new file mode 100644
index 0000000000..0a3e8a9975
--- /dev/null
+++ b/accessible/tests/browser/states/browser_test_link.js
@@ -0,0 +1,44 @@
+/* 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";
+
+async function runTests(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // a: no traversed state
+ testStates(getAcc("link_traversed"), 0, 0, STATE_TRAVERSED);
+
+ let onStateChanged = waitForEvent(EVENT_STATE_CHANGE, "link_traversed");
+ let newTabOpened = BrowserTestUtils.waitForNewTab(gBrowser);
+
+ await BrowserTestUtils.synthesizeMouse(
+ "#link_traversed",
+ 1,
+ 1,
+ { ctrlKey: !MAC, metaKey: MAC },
+ browser
+ );
+
+ await onStateChanged;
+ testStates(getAcc("link_traversed"), STATE_TRAVERSED);
+
+ let newTab = await newTabOpened;
+ gBrowser.removeTab(newTab);
+}
+
+/**
+ * Test caching of accessible object states
+ */
+addAccessibleTask(
+ // The URL doesn't really matter, just the fact that it isn't in the history
+ // initially. We append ms since epoch to the URL so it will never be visited
+ // initially, regardless of other tests (even this one) that ran before.
+ `
+ <a id="link_traversed"
+ href="https://www.example.com/${Date.now()}" target="_top">
+ example.com
+ </a>`,
+ runTests
+);
diff --git a/accessible/tests/browser/states/browser_test_select_visibility.js b/accessible/tests/browser/states/browser_test_select_visibility.js
new file mode 100644
index 0000000000..89b4df67f7
--- /dev/null
+++ b/accessible/tests/browser/states/browser_test_select_visibility.js
@@ -0,0 +1,76 @@
+/* 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";
+
+// test selects and options
+addAccessibleTask(
+ `<select id="select">
+ <option id="o1">hello</option>
+ <option id="o2">world</option>
+ </select>`,
+ async function (browser, accDoc) {
+ const select = findAccessibleChildByID(accDoc, "select");
+ ok(
+ isAccessible(select.firstChild, [nsIAccessibleSelectable]),
+ "No selectable accessible for combobox"
+ );
+ await untilCacheOk(
+ () => testVisibility(select, false, false),
+ "select should be on screen and visible"
+ );
+
+ if (!browser.isRemoteBrowser) {
+ await untilCacheOk(
+ () => testVisibility(select.firstChild, false, true),
+ "combobox list should be on screen and invisible"
+ );
+ } else {
+ // XXX: When the cache is used, states::INVISIBLE is
+ // incorrect. Test OFFSCREEN anyway.
+ await untilCacheOk(() => {
+ const [states] = getStates(select.firstChild);
+ return (states & STATE_OFFSCREEN) == 0;
+ }, "combobox list should be on screen");
+ }
+
+ const o1 = findAccessibleChildByID(accDoc, "o1");
+ const o2 = findAccessibleChildByID(accDoc, "o2");
+
+ await untilCacheOk(
+ () => testVisibility(o1, false, false),
+ "option one should be on screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(o2, true, false),
+ "option two should be off screen and visible"
+ );
+
+ // Select the second option (drop-down collapsed).
+ const p = waitForEvents({
+ expected: [
+ [EVENT_SELECTION, "o2"],
+ [EVENT_TEXT_VALUE_CHANGE, "select"],
+ ],
+ unexpected: [
+ stateChangeEventArgs("o2", EXT_STATE_ACTIVE, true, true),
+ stateChangeEventArgs("o1", EXT_STATE_ACTIVE, false, true),
+ ],
+ });
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("select").selectedIndex = 1;
+ });
+ await p;
+
+ await untilCacheOk(() => {
+ const [states] = getStates(o1);
+ return (states & STATE_OFFSCREEN) != 0;
+ }, "option 1 should be off screen");
+ await untilCacheOk(() => {
+ const [states] = getStates(o2);
+ return (states & STATE_OFFSCREEN) == 0;
+ }, "option 2 should be on screen");
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/states/browser_test_visibility.js b/accessible/tests/browser/states/browser_test_visibility.js
new file mode 100644
index 0000000000..25bd903ed4
--- /dev/null
+++ b/accessible/tests/browser/states/browser_test_visibility.js
@@ -0,0 +1,181 @@
+/* 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";
+
+async function runTest(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ await untilCacheOk(
+ () => testVisibility(getAcc("div"), false, false),
+ "Div should be on screen"
+ );
+
+ let input = getAcc("input_scrolledoff");
+ await untilCacheOk(
+ () => testVisibility(input, true, false),
+ "Input should be offscreen"
+ );
+
+ // scrolled off item (twice)
+ let lastLi = getAcc("li_last");
+ await untilCacheOk(
+ () => testVisibility(lastLi, true, false),
+ "Last list item should be offscreen"
+ );
+
+ // scroll into view the item
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("li_last").scrollIntoView(true);
+ });
+ await untilCacheOk(
+ () => testVisibility(lastLi, false, false),
+ "Last list item should no longer be offscreen"
+ );
+
+ // first item is scrolled off now (testcase for bug 768786)
+ let firstLi = getAcc("li_first");
+ await untilCacheOk(
+ () => testVisibility(firstLi, true, false),
+ "First listitem should now be offscreen"
+ );
+
+ await untilCacheOk(
+ () => testVisibility(getAcc("frame"), false, false),
+ "iframe should initially be onscreen"
+ );
+
+ let loaded = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, "iframeDoc");
+ await invokeContentTask(browser, [], () => {
+ content.document.querySelector("iframe").src =
+ 'data:text/html,<body id="iframeDoc"><p id="p">hi</p></body>';
+ });
+
+ const iframeDoc = (await loaded).accessible;
+ await untilCacheOk(
+ () => testVisibility(getAcc("frame"), false, false),
+ "iframe outer doc should now be on screen"
+ );
+ await untilCacheOk(
+ () => testVisibility(iframeDoc, false, false),
+ "iframe inner doc should be on screen"
+ );
+ const iframeP = findAccessibleChildByID(iframeDoc, "p");
+ await untilCacheOk(
+ () => testVisibility(iframeP, false, false),
+ "iframe content should also be on screen"
+ );
+
+ // scroll into view the div
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("div").scrollIntoView(true);
+ });
+
+ await untilCacheOk(
+ () => testVisibility(getAcc("frame"), true, false),
+ "iframe outer doc should now be off screen"
+ );
+ await untilCacheOk(
+ () => testVisibility(iframeDoc, true, false),
+ "iframe inner doc should now be off screen"
+ );
+ await untilCacheOk(
+ () => testVisibility(iframeP, true, false),
+ "iframe content should now be off screen"
+ );
+
+ let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ // Accessibles in background tab should have offscreen state and no
+ // invisible state.
+ await untilCacheOk(
+ () => testVisibility(getAcc("div"), true, false),
+ "Accs in background tab should be offscreen but not invisible."
+ );
+
+ await untilCacheOk(
+ () => testVisibility(getAcc("frame"), true, false),
+ "iframe outer doc should still be off screen"
+ );
+ await untilCacheOk(
+ () => testVisibility(iframeDoc, true, false),
+ "iframe inner doc should still be off screen"
+ );
+ await untilCacheOk(
+ () => testVisibility(iframeP, true, false),
+ "iframe content should still be off screen"
+ );
+
+ BrowserTestUtils.removeTab(newTab);
+}
+
+addAccessibleTask(
+ `
+ <div id="div" style="border:2px solid blue; width: 500px; height: 110vh;"></div>
+ <input id="input_scrolledoff">
+ <ul style="border:2px solid red; width: 100px; height: 50px; overflow: auto;">
+ <li id="li_first">item1</li><li>item2</li><li>item3</li>
+ <li>item4</li><li>item5</li><li id="li_last">item6</li>
+ </ul>
+ <iframe id="frame"></iframe>
+ `,
+ runTest,
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test div containers are reported as onscreen, even if some of their contents are
+ * offscreen.
+ */
+addAccessibleTask(
+ `
+ <div id="outer" style="width:200vw; background: green; overflow:scroll;"><div id="inner"><div style="display:inline-block; width:100vw; background:red;" id="on">on screen</div><div style="background:blue; display:inline;" id="off">offscreen</div></div></div>
+ `,
+ async function (browser, accDoc) {
+ const outer = findAccessibleChildByID(accDoc, "outer");
+ const inner = findAccessibleChildByID(accDoc, "inner");
+ const on = findAccessibleChildByID(accDoc, "on");
+ const off = findAccessibleChildByID(accDoc, "off");
+
+ await untilCacheOk(
+ () => testVisibility(outer, false, false),
+ "outer should be on screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(inner, false, false),
+ "inner should be on screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(on, false, false),
+ "on should be on screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(off, true, false),
+ "off should be off screen and visible"
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// test dynamic translation
+addAccessibleTask(
+ `<div id="container" style="position: absolute; left: -300px; top: 100px;">Hello</div><button id="b" onclick="container.style.transform = 'translateX(400px)'">Move</button>`,
+ async function (browser, accDoc) {
+ const container = findAccessibleChildByID(accDoc, "container");
+ await untilCacheOk(
+ () => testVisibility(container, true, false),
+ "container should be off screen and visible"
+ );
+ await invokeContentTask(browser, [], () => {
+ let b = content.document.getElementById("b");
+ b.click();
+ });
+
+ await waitForContentPaint(browser);
+ await untilCacheOk(
+ () => testVisibility(container, false, false),
+ "container should be on screen and visible"
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/states/browser_test_visibility_2.js b/accessible/tests/browser/states/browser_test_visibility_2.js
new file mode 100644
index 0000000000..ead134069a
--- /dev/null
+++ b/accessible/tests/browser/states/browser_test_visibility_2.js
@@ -0,0 +1,131 @@
+/* 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";
+
+/**
+ * Test tables, table rows are reported on screen, even if some cells of a given row are
+ * offscreen.
+ */
+addAccessibleTask(
+ `
+ <table id="table" style="width:150vw;" border><tr id="row"><td id="one" style="width:50vw;">one</td><td style="width:50vw;" id="two">two</td><td id="three">three</td></tr></table>
+ `,
+ async function (browser, accDoc) {
+ const table = findAccessibleChildByID(accDoc, "table");
+ const row = findAccessibleChildByID(accDoc, "row");
+ const one = findAccessibleChildByID(accDoc, "one");
+ const two = findAccessibleChildByID(accDoc, "two");
+ const three = findAccessibleChildByID(accDoc, "three");
+
+ await untilCacheOk(
+ () => testVisibility(table, false, false),
+ "table should be on screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(row, false, false),
+ "row should be on screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(one, false, false),
+ "one should be on screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(two, false, false),
+ "two should be on screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(three, true, false),
+ "three should be off screen and visible"
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test rows and cells outside of the viewport are reported as offscreen.
+ */
+addAccessibleTask(
+ `
+ <table id="table" style="height:150vh;" border><tr style="height:100vh;" id="rowA"><td id="one">one</td></tr><tr id="rowB"><td id="two">two</td></tr></table>
+ `,
+ async function (browser, accDoc) {
+ const table = findAccessibleChildByID(accDoc, "table");
+ const rowA = findAccessibleChildByID(accDoc, "rowA");
+ const one = findAccessibleChildByID(accDoc, "one");
+ const rowB = findAccessibleChildByID(accDoc, "rowB");
+ const two = findAccessibleChildByID(accDoc, "two");
+
+ await untilCacheOk(
+ () => testVisibility(table, false, false),
+ "table should be on screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(rowA, false, false),
+ "rowA should be on screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(one, false, false),
+ "one should be on screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(rowB, true, false),
+ "rowB should be off screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(two, true, false),
+ "two should be off screen and visible"
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+addAccessibleTask(
+ `
+ <div id="div">hello</div>
+ `,
+ async function (browser, accDoc) {
+ let textLeaf = findAccessibleChildByID(accDoc, "div").firstChild;
+ await untilCacheOk(
+ () => testVisibility(textLeaf, false, false),
+ "text should be on screen and visible"
+ );
+ let p = waitForEvent(EVENT_TEXT_INSERTED, "div");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("div").textContent = "goodbye";
+ });
+ await p;
+ textLeaf = findAccessibleChildByID(accDoc, "div").firstChild;
+ await untilCacheOk(
+ () => testVisibility(textLeaf, false, false),
+ "text should be on screen and visible"
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Overlapping, opaque divs with the same bounds should not be considered
+ * offscreen.
+ */
+addAccessibleTask(
+ `
+ <style>div { height: 5px; width: 5px; background: green; }</style>
+ <div id="outer" role="group"><div style="background:blue;" id="inner" role="group">hi</div></div>
+ `,
+ async function (browser, accDoc) {
+ const outer = findAccessibleChildByID(accDoc, "outer");
+ const inner = findAccessibleChildByID(accDoc, "inner");
+
+ await untilCacheOk(
+ () => testVisibility(outer, false, false),
+ "outer should be on screen and visible"
+ );
+ await untilCacheOk(
+ () => testVisibility(inner, false, false),
+ "inner should be on screen and visible"
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/states/head.js b/accessible/tests/browser/states/head.js
new file mode 100644
index 0000000000..10c616cb80
--- /dev/null
+++ b/accessible/tests/browser/states/head.js
@@ -0,0 +1,91 @@
+/* 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";
+
+/* exported waitForIFrameA11yReady, waitForIFrameUpdates, spawnTestStates, testVisibility */
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js.
+/* import-globals-from ../../mochitest/states.js */
+/* import-globals-from ../../mochitest/role.js */
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR },
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+// This is another version of addA11yLoadEvent for fission.
+async function waitForIFrameA11yReady(iFrameBrowsingContext) {
+ await SimpleTest.promiseFocus(window);
+
+ await SpecialPowers.spawn(iFrameBrowsingContext, [], () => {
+ return new Promise(resolve => {
+ function waitForDocLoad() {
+ SpecialPowers.executeSoon(() => {
+ const acc = SpecialPowers.Cc[
+ "@mozilla.org/accessibilityService;1"
+ ].getService(SpecialPowers.Ci.nsIAccessibilityService);
+
+ const accDoc = acc.getAccessibleFor(content.document);
+ let state = {};
+ accDoc.getState(state, {});
+ if (state.value & SpecialPowers.Ci.nsIAccessibleStates.STATE_BUSY) {
+ SpecialPowers.executeSoon(waitForDocLoad);
+ return;
+ }
+ resolve();
+ }, 0);
+ }
+ waitForDocLoad();
+ });
+ });
+}
+
+// A utility function to make sure the information of scroll position or visible
+// area changes reach to out-of-process iframes.
+async function waitForIFrameUpdates() {
+ // Wait for two frames since the information is notified via asynchronous IPC
+ // calls.
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ await new Promise(resolve => requestAnimationFrame(resolve));
+}
+
+// A utility function to test the state of |elementId| element in out-of-process
+// |browsingContext|.
+async function spawnTestStates(browsingContext, elementId, expectedStates) {
+ function testStates(id, expected, unexpected) {
+ const acc = SpecialPowers.Cc[
+ "@mozilla.org/accessibilityService;1"
+ ].getService(SpecialPowers.Ci.nsIAccessibilityService);
+ const target = content.document.getElementById(id);
+ let state = {};
+ acc.getAccessibleFor(target).getState(state, {});
+ if (expected === 0) {
+ Assert.equal(state.value, expected);
+ } else {
+ Assert.ok(state.value & expected);
+ }
+ Assert.ok(!(state.value & unexpected));
+ }
+ await SpecialPowers.spawn(
+ browsingContext,
+ [elementId, expectedStates],
+ testStates
+ );
+}
+
+function testVisibility(acc, shouldBeOffscreen, shouldBeInvisible) {
+ const [states] = getStates(acc);
+ let looksGood = shouldBeOffscreen == ((states & STATE_OFFSCREEN) != 0);
+ looksGood &= shouldBeInvisible == ((states & STATE_INVISIBLE) != 0);
+ return looksGood;
+}
diff --git a/accessible/tests/browser/telemetry/browser.ini b/accessible/tests/browser/telemetry/browser.ini
new file mode 100644
index 0000000000..07e44a348d
--- /dev/null
+++ b/accessible/tests/browser/telemetry/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+subsuite = a11y
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_HCM_telemetry.js]
+support-files =
+ !/browser/components/preferences/tests/head.js
diff --git a/accessible/tests/browser/telemetry/browser_HCM_telemetry.js b/accessible/tests/browser/telemetry/browser_HCM_telemetry.js
new file mode 100644
index 0000000000..23600d51c4
--- /dev/null
+++ b/accessible/tests/browser/telemetry/browser_HCM_telemetry.js
@@ -0,0 +1,365 @@
+/* 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";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/components/preferences/tests/head.js",
+ this
+);
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+registerCleanupFunction(() => {
+ reset();
+});
+
+function reset() {
+ // This (manually) runs after every task in this test suite.
+ // We have to add this in because the initial state of
+ // `document_color_use` affects the initial state of
+ // `foreground_color`/`background_color` which can change our
+ // starting telem samples. This ensures each tasks makes no lasting
+ // state changes.
+ Services.prefs.clearUserPref("browser.display.document_color_use");
+ Services.prefs.clearUserPref("browser.display.permit_backplate");
+ Services.prefs.clearUserPref("browser.display.use_system_colors");
+ Services.telemetry.clearEvents();
+ TelemetryTestUtils.assertNumberOfEvents(0);
+ Services.prefs.clearUserPref("browser.display.foreground_color");
+ Services.prefs.clearUserPref("browser.display.background_color");
+}
+
+async function openColorsDialog() {
+ await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+ const colorsButton =
+ gBrowser.selectedBrowser.contentDocument.getElementById("colors");
+
+ const dialogOpened = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/colors.xhtml"
+ );
+ colorsButton.doCommand();
+
+ return dialogOpened;
+}
+
+async function closeColorsDialog(dialogWin) {
+ const dialogClosed = BrowserTestUtils.waitForEvent(dialogWin, "unload");
+ const button = dialogWin.document
+ .getElementById("ColorsDialog")
+ .getButton("accept");
+ button.focus();
+ button.doCommand();
+ return dialogClosed;
+}
+
+function verifyBackplate(expectedValue) {
+ TelemetryTestUtils.assertScalar(
+ TelemetryTestUtils.getProcessScalars("parent", false, true),
+ "a11y.backplate",
+ expectedValue,
+ "Backplate scalar is logged as " + expectedValue
+ );
+}
+
+function verifyUseSystemColors(expectedValue) {
+ const snapshot = TelemetryTestUtils.getProcessScalars("parent", false, false);
+ ok("a11y.use_system_colors" in snapshot, "System color usage was logged.");
+ TelemetryTestUtils.assertScalar(
+ snapshot,
+ "a11y.use_system_colors",
+ expectedValue,
+ "System colors scalar is logged as " + expectedValue
+ );
+}
+
+// The magic numbers below are the uint32_t values representing RGB white
+// and RGB black respectively. They're directly captured as nsColors and
+// follow the same bit-shift pattern.
+function testIsWhite(pref, snapshot) {
+ ok(pref in snapshot, "Scalar must be present.");
+ is(snapshot[pref], 4294967295, "Scalar is logged as white");
+}
+
+function testIsBlack(pref, snapshot) {
+ ok(pref in snapshot, "Scalar must be present.");
+ is(snapshot[pref], 4278190080, "Scalar is logged as black");
+}
+
+async function setForegroundColor(color) {
+ // Note: we set the foreground and background colors by modifying this pref
+ // instead of setting the value attribute on the color input direclty.
+ // This is because setting the value of the input with setAttribute
+ // doesn't generate the correct event to save the new value to the prefs
+ // store, so we have to do it ourselves.
+ Services.prefs.setStringPref("browser.display.foreground_color", color);
+}
+
+async function setBackgroundColor(color) {
+ Services.prefs.setStringPref("browser.display.background_color", color);
+}
+
+add_task(async function testInit() {
+ const dialogWin = await openColorsDialog();
+ const menulistHCM = dialogWin.document.getElementById("useDocumentColors");
+ if (AppConstants.platform == "win") {
+ is(
+ Services.prefs.getBoolPref("browser.display.use_system_colors"),
+ true,
+ "Use system colours pref is init'd correctly"
+ );
+ verifyUseSystemColors(true);
+
+ is(
+ menulistHCM.value,
+ "0",
+ "HCM menulist should be set to only with HCM theme on startup for windows"
+ );
+
+ // Verify correct default value
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ "a11y.theme",
+ "default",
+ false
+ );
+ } else {
+ is(
+ Services.prefs.getBoolPref("browser.display.use_system_colors"),
+ false,
+ "Use system colours pref is init'd correctly"
+ );
+ verifyUseSystemColors(false);
+
+ is(
+ menulistHCM.value,
+ "1",
+ "HCM menulist should be set to never on startup for non-windows platforms"
+ );
+
+ // Verify correct default value
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ "a11y.theme",
+ "always",
+ false
+ );
+
+ await closeColorsDialog(dialogWin);
+
+ // We should not have logged any colors
+ let snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ ok(
+ !("a11y.HCM_foreground" in snapshot),
+ "Foreground color shouldn't be present."
+ );
+ ok(
+ !("a11y.HCM_background" in snapshot),
+ "Background color shouldn't be present."
+ );
+
+ // If we change the colors, our probes should not be updated
+ await setForegroundColor("#ffffff"); // white
+ await setBackgroundColor("#000000"); // black
+
+ snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ ok(
+ !("a11y.HCM_foreground" in snapshot),
+ "Foreground color shouldn't be present."
+ );
+ ok(
+ !("a11y.HCM_background" in snapshot),
+ "Background color shouldn't be present."
+ );
+ }
+
+ reset();
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function testSetAlways() {
+ const dialogWin = await openColorsDialog();
+ const menulistHCM = dialogWin.document.getElementById("useDocumentColors");
+
+ menulistHCM.doCommand();
+ const newOption = dialogWin.document.getElementById("documentColorAlways");
+ newOption.click();
+
+ is(menulistHCM.value, "2", "HCM menulist should be set to always");
+
+ await closeColorsDialog(dialogWin);
+
+ // Verify correct initial value
+ let snapshot = TelemetryTestUtils.getProcessScalars("parent", true, true);
+ TelemetryTestUtils.assertKeyedScalar(snapshot, "a11y.theme", "never", false);
+
+ snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ // We should have logged the default foreground and background colors
+ testIsWhite("a11y.HCM_background", snapshot);
+ testIsBlack("a11y.HCM_foreground", snapshot);
+
+ // If we change the colors, our probes update on non-windows platforms.
+ // On windows, useSystemColors is on by default, and so the values we set here
+ // will not be written to our telemetry probes, because they capture
+ // used colors, not the values of browser.foreground/background_color directly.
+
+ setBackgroundColor("#000000");
+ snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ if (AppConstants.platform == "win") {
+ testIsWhite("a11y.HCM_background", snapshot);
+ } else {
+ testIsBlack("a11y.HCM_background", snapshot);
+ }
+
+ setForegroundColor("#ffffff");
+ snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ if (AppConstants.platform == "win") {
+ testIsBlack("a11y.HCM_foreground", snapshot);
+ } else {
+ testIsWhite("a11y.HCM_foreground", snapshot);
+ }
+
+ reset();
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function testSetDefault() {
+ const dialogWin = await openColorsDialog();
+ const menulistHCM = dialogWin.document.getElementById("useDocumentColors");
+
+ menulistHCM.doCommand();
+ const newOption = dialogWin.document.getElementById("documentColorAutomatic");
+ newOption.click();
+
+ is(menulistHCM.value, "0", "HCM menulist should be set to default");
+
+ await closeColorsDialog(dialogWin);
+
+ // Verify correct initial value
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ "a11y.theme",
+ "default",
+ false
+ );
+
+ // We should not have logged any colors
+ let snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ ok(
+ !("a11y.HCM_foreground" in snapshot),
+ "Foreground color shouldn't be present."
+ );
+ ok(
+ !("a11y.HCM_background" in snapshot),
+ "Background color shouldn't be present."
+ );
+
+ // If we change the colors, our probes should not be updated anywhere
+ await setForegroundColor("#ffffff"); // white
+ await setBackgroundColor("#000000"); // black
+
+ snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ ok(
+ !("a11y.HCM_foreground" in snapshot),
+ "Foreground color shouldn't be present."
+ );
+ ok(
+ !("a11y.HCM_background" in snapshot),
+ "Background color shouldn't be present."
+ );
+
+ reset();
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function testSetNever() {
+ const dialogWin = await openColorsDialog();
+ const menulistHCM = dialogWin.document.getElementById("useDocumentColors");
+
+ menulistHCM.doCommand();
+ const newOption = dialogWin.document.getElementById("documentColorNever");
+ newOption.click();
+
+ is(menulistHCM.value, "1", "HCM menulist should be set to never");
+
+ await closeColorsDialog(dialogWin);
+
+ // Verify correct initial value
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ "a11y.theme",
+ "always",
+ false
+ );
+
+ // We should not have logged any colors
+ let snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ ok(
+ !("a11y.HCM_foreground" in snapshot),
+ "Foreground color shouldn't be present."
+ );
+ ok(
+ !("a11y.HCM_background" in snapshot),
+ "Background color shouldn't be present."
+ );
+
+ // If we change the colors, our probes should not be updated anywhere
+ await setForegroundColor("#ffffff"); // white
+ await setBackgroundColor("#000000"); // black
+
+ snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ ok(
+ !("a11y.HCM_foreground" in snapshot),
+ "Foreground color shouldn't be present."
+ );
+ ok(
+ !("a11y.HCM_background" in snapshot),
+ "Background color shouldn't be present."
+ );
+
+ reset();
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function testBackplate() {
+ is(
+ Services.prefs.getBoolPref("browser.display.permit_backplate"),
+ true,
+ "Backplate is init'd to true"
+ );
+
+ Services.prefs.setBoolPref("browser.display.permit_backplate", false);
+ // Verify correct recorded value
+ verifyBackplate(false);
+
+ Services.prefs.setBoolPref("browser.display.permit_backplate", true);
+ // Verify correct recorded value
+ verifyBackplate(true);
+});
+
+add_task(async function testSystemColors() {
+ let expectedInitVal = false;
+ if (AppConstants.platform == "win") {
+ expectedInitVal = true;
+ }
+
+ const dialogWin = await openColorsDialog();
+ const checkbox = dialogWin.document.getElementById("browserUseSystemColors");
+ checkbox.click();
+
+ is(
+ checkbox.checked,
+ !expectedInitVal,
+ "System colors checkbox should be modified"
+ );
+
+ await closeColorsDialog(dialogWin);
+
+ verifyUseSystemColors(!expectedInitVal);
+
+ reset();
+ gBrowser.removeCurrentTab();
+});
diff --git a/accessible/tests/browser/text/browser.ini b/accessible/tests/browser/text/browser.ini
new file mode 100644
index 0000000000..1b0c5a3033
--- /dev/null
+++ b/accessible/tests/browser/text/browser.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_editabletext.js]
+[browser_text.js]
+[browser_text_caret.js]
+[browser_text_paragraph_boundary.js]
+[browser_text_selection.js]
+[browser_text_spelling.js]
+skip-if = true # Bug 1800400
+[browser_textleafpoint.js]
diff --git a/accessible/tests/browser/text/browser_editabletext.js b/accessible/tests/browser/text/browser_editabletext.js
new file mode 100644
index 0000000000..0310122deb
--- /dev/null
+++ b/accessible/tests/browser/text/browser_editabletext.js
@@ -0,0 +1,173 @@
+/* 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";
+
+async function testEditable(browser, acc, aBefore = "", aAfter = "") {
+ async function resetInput() {
+ if (acc.childCount <= 1) {
+ return;
+ }
+
+ let emptyInputEvent = waitForEvent(EVENT_TEXT_VALUE_CHANGE, "input");
+ await invokeContentTask(browser, [], async () => {
+ content.document.getElementById("input").innerHTML = "";
+ });
+
+ await emptyInputEvent;
+ }
+
+ // ////////////////////////////////////////////////////////////////////////
+ // insertText
+ await testInsertText(acc, "hello", 0, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "hello", aAfter]);
+ await testInsertText(acc, "ma ", 0, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "ma hello", aAfter]);
+ await testInsertText(acc, "ma", 2, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "mama hello", aAfter]);
+ await testInsertText(acc, " hello", 10, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [
+ aBefore,
+ "mama hello hello",
+ aAfter,
+ ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // deleteText
+ await testDeleteText(acc, 0, 5, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "hello hello", aAfter]);
+ await testDeleteText(acc, 5, 6, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "hellohello", aAfter]);
+ await testDeleteText(acc, 5, 10, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "hello", aAfter]);
+ await testDeleteText(acc, 0, 5, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "", aAfter]);
+
+ // XXX: clipboard operation tests don't work well with editable documents.
+ if (acc.role == ROLE_DOCUMENT) {
+ return;
+ }
+
+ await resetInput();
+
+ // copyText and pasteText
+ await testInsertText(acc, "hello", 0, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "hello", aAfter]);
+
+ await testCopyText(acc, 0, 1, aBefore.length, browser, "h");
+ await testPasteText(acc, 1, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "hhello", aAfter]);
+
+ await testCopyText(acc, 5, 6, aBefore.length, browser, "o");
+ await testPasteText(acc, 6, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "hhelloo", aAfter]);
+
+ await testCopyText(acc, 2, 3, aBefore.length, browser, "e");
+ await testPasteText(acc, 1, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "hehelloo", aAfter]);
+
+ // cut & paste
+ await testCutText(acc, 0, 1, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "ehelloo", aAfter]);
+ await testPasteText(acc, 2, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "ehhelloo", aAfter]);
+
+ await testCutText(acc, 3, 4, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "ehhlloo", aAfter]);
+ await testPasteText(acc, 6, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "ehhlloeo", aAfter]);
+
+ await testCutText(acc, 0, 8, aBefore.length);
+ await isFinalValueCorrect(browser, acc, [aBefore, "", aAfter]);
+
+ await resetInput();
+
+ // ////////////////////////////////////////////////////////////////////////
+ // setTextContents
+ await testSetTextContents(acc, "hello", aBefore.length, [
+ EVENT_TEXT_INSERTED,
+ ]);
+ await isFinalValueCorrect(browser, acc, [aBefore, "hello", aAfter]);
+ await testSetTextContents(acc, "katze", aBefore.length, [
+ EVENT_TEXT_REMOVED,
+ EVENT_TEXT_INSERTED,
+ ]);
+ await isFinalValueCorrect(browser, acc, [aBefore, "katze", aAfter]);
+}
+
+addAccessibleTask(
+ `<input id="input"/>`,
+ async function (browser, docAcc) {
+ await testEditable(browser, findAccessibleChildByID(docAcc, "input"));
+ },
+ { chrome: true, topLevel: true }
+);
+
+addAccessibleTask(
+ `<style>
+ #input::after {
+ content: "pseudo element";
+ }
+</style>
+<div id="input" contenteditable="true" role="textbox"></div>`,
+ async function (browser, docAcc) {
+ await testEditable(
+ browser,
+ findAccessibleChildByID(docAcc, "input"),
+ "",
+ "pseudo element"
+ );
+ },
+ { chrome: true, topLevel: false /* bug 1834129 */ }
+);
+
+addAccessibleTask(
+ `<style>
+ #input::before {
+ content: "pseudo element";
+ }
+</style>
+<div id="input" contenteditable="true" role="textbox"></div>`,
+ async function (browser, docAcc) {
+ await testEditable(
+ browser,
+ findAccessibleChildByID(docAcc, "input"),
+ "pseudo element"
+ );
+ },
+ { chrome: true, topLevel: false /* bug 1834129 */ }
+);
+
+addAccessibleTask(
+ `<style>
+ #input::before {
+ content: "before";
+ }
+ #input::after {
+ content: "after";
+ }
+</style>
+<div id="input" contenteditable="true" role="textbox"></div>`,
+ async function (browser, docAcc) {
+ await testEditable(
+ browser,
+ findAccessibleChildByID(docAcc, "input"),
+ "before",
+ "after"
+ );
+ },
+ { chrome: true, topLevel: false /* bug 1834129 */ }
+);
+
+addAccessibleTask(
+ ``,
+ async function (browser, docAcc) {
+ await testEditable(browser, docAcc);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ contentDocBodyAttrs: { contentEditable: "true" },
+ }
+);
diff --git a/accessible/tests/browser/text/browser_text.js b/accessible/tests/browser/text/browser_text.js
new file mode 100644
index 0000000000..79909ee412
--- /dev/null
+++ b/accessible/tests/browser/text/browser_text.js
@@ -0,0 +1,326 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/attributes.js */
+/* import-globals-from ../../mochitest/text.js */
+loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Test line and word offsets for various cases for both local and remote
+ * Accessibles. There is more extensive coverage in ../../mochitest/text. These
+ * tests don't need to duplicate all of that, since much of the underlying code
+ * is unified. They should ensure that the cache works as expected and that
+ * there is consistency between local and remote.
+ */
+addAccessibleTask(
+ `
+<p id="br">ab cd<br>ef gh</p>
+<pre id="pre">ab cd
+ef gh</pre>
+<p id="linksStartEnd"><a href="https://example.com/">a</a>b<a href="https://example.com/">c</a></p>
+<p id="linksBreaking">a<a href="https://example.com/">b<br>c</a>d</p>
+<p id="p">a<br role="presentation">b</p>
+<p id="leafThenWrap" style="font-family: monospace; width: 2ch; word-break: break-word;"><span>a</span>bc</p>
+ `,
+ async function (browser, docAcc) {
+ for (const id of ["br", "pre"]) {
+ const acc = findAccessibleChildByID(docAcc, id);
+ testCharacterCount([acc], 11);
+ testTextAtOffset(acc, BOUNDARY_LINE_START, [
+ [0, 5, "ab cd\n", 0, 6],
+ [6, 11, "ef gh", 6, 11],
+ ]);
+ testTextBeforeOffset(acc, BOUNDARY_LINE_START, [
+ [0, 5, "", 0, 0],
+ [6, 11, "ab cd\n", 0, 6],
+ ]);
+ testTextAfterOffset(acc, BOUNDARY_LINE_START, [
+ [0, 5, "ef gh", 6, 11],
+ [6, 11, "", 11, 11],
+ ]);
+ testTextAtOffset(acc, BOUNDARY_LINE_END, [
+ [0, 5, "ab cd", 0, 5],
+ [6, 11, "\nef gh", 5, 11],
+ ]);
+ testTextBeforeOffset(acc, BOUNDARY_LINE_END, [
+ [0, 5, "", 0, 0],
+ [6, 11, "ab cd", 0, 5],
+ ]);
+ testTextAfterOffset(acc, BOUNDARY_LINE_END, [
+ [0, 5, "\nef gh", 5, 11],
+ [6, 11, "", 11, 11],
+ ]);
+ testTextAtOffset(acc, BOUNDARY_WORD_START, [
+ [0, 2, "ab ", 0, 3],
+ [3, 5, "cd\n", 3, 6],
+ [6, 8, "ef ", 6, 9],
+ [9, 11, "gh", 9, 11],
+ ]);
+ testTextBeforeOffset(acc, BOUNDARY_WORD_START, [
+ [0, 2, "", 0, 0],
+ [3, 5, "ab ", 0, 3],
+ [6, 8, "cd\n", 3, 6],
+ [9, 11, "ef ", 6, 9],
+ ]);
+ testTextAfterOffset(acc, BOUNDARY_WORD_START, [
+ [0, 2, "cd\n", 3, 6],
+ [3, 5, "ef ", 6, 9],
+ [6, 8, "gh", 9, 11],
+ [9, 11, "", 11, 11],
+ ]);
+ testTextAtOffset(acc, BOUNDARY_WORD_END, [
+ [0, 1, "ab", 0, 2],
+ [2, 4, " cd", 2, 5],
+ [5, 7, "\nef", 5, 8],
+ [8, 11, " gh", 8, 11],
+ ]);
+ testTextBeforeOffset(acc, BOUNDARY_WORD_END, [
+ [0, 2, "", 0, 0],
+ [3, 5, "ab", 0, 2],
+ // See below for offset 6.
+ [7, 8, " cd", 2, 5],
+ [9, 11, "\nef", 5, 8],
+ ]);
+ testTextBeforeOffset(acc, BOUNDARY_WORD_END, [[6, 6, " cd", 2, 5]]);
+ testTextAfterOffset(acc, BOUNDARY_WORD_END, [
+ [0, 2, " cd", 2, 5],
+ [3, 5, "\nef", 5, 8],
+ [6, 8, " gh", 8, 11],
+ [9, 11, "", 11, 11],
+ ]);
+ testTextAtOffset(acc, BOUNDARY_PARAGRAPH, [
+ [0, 5, "ab cd\n", 0, 6],
+ [6, 11, "ef gh", 6, 11],
+ ]);
+ }
+ const linksStartEnd = findAccessibleChildByID(docAcc, "linksStartEnd");
+ testTextAtOffset(linksStartEnd, BOUNDARY_LINE_START, [
+ [0, 3, `${kEmbedChar}b${kEmbedChar}`, 0, 3],
+ ]);
+ testTextAtOffset(linksStartEnd, BOUNDARY_WORD_START, [
+ [0, 3, `${kEmbedChar}b${kEmbedChar}`, 0, 3],
+ ]);
+ const linksBreaking = findAccessibleChildByID(docAcc, "linksBreaking");
+ testTextAtOffset(linksBreaking, BOUNDARY_LINE_START, [
+ [0, 0, `a${kEmbedChar}`, 0, 2],
+ [1, 1, `a${kEmbedChar}d`, 0, 3],
+ [2, 3, `${kEmbedChar}d`, 1, 3],
+ ]);
+ const p = findAccessibleChildByID(docAcc, "p");
+ testTextAtOffset(p, BOUNDARY_LINE_START, [
+ [0, 0, "a", 0, 1],
+ [1, 2, "b", 1, 2],
+ ]);
+ testTextAtOffset(p, BOUNDARY_PARAGRAPH, [[0, 2, "ab", 0, 2]]);
+ const leafThenWrap = findAccessibleChildByID(docAcc, "leafThenWrap");
+ testTextAtOffset(leafThenWrap, BOUNDARY_LINE_START, [
+ [0, 1, "ab", 0, 2],
+ [2, 3, "c", 2, 3],
+ ]);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test line offsets after text mutation.
+ */
+addAccessibleTask(
+ `
+<p id="initBr"><br></p>
+<p id="rewrap" style="font-family: monospace; width: 2ch; word-break: break-word;"><span id="rewrap1">ac</span>def</p>
+ `,
+ async function (browser, docAcc) {
+ const initBr = findAccessibleChildByID(docAcc, "initBr");
+ testTextAtOffset(initBr, BOUNDARY_LINE_START, [
+ [0, 0, "\n", 0, 1],
+ [1, 1, "", 1, 1],
+ ]);
+ info("initBr: Inserting text before br");
+ let reordered = waitForEvent(EVENT_REORDER, initBr);
+ await invokeContentTask(browser, [], () => {
+ const initBrNode = content.document.getElementById("initBr");
+ initBrNode.insertBefore(
+ content.document.createTextNode("a"),
+ initBrNode.firstElementChild
+ );
+ });
+ await reordered;
+ testTextAtOffset(initBr, BOUNDARY_LINE_START, [
+ [0, 1, "a\n", 0, 2],
+ [2, 2, "", 2, 2],
+ ]);
+
+ const rewrap = findAccessibleChildByID(docAcc, "rewrap");
+ testTextAtOffset(rewrap, BOUNDARY_LINE_START, [
+ [0, 1, "ac", 0, 2],
+ [2, 3, "de", 2, 4],
+ [4, 5, "f", 4, 5],
+ ]);
+ info("rewrap: Changing ac to abc");
+ reordered = waitForEvent(EVENT_REORDER, rewrap);
+ await invokeContentTask(browser, [], () => {
+ const rewrap1 = content.document.getElementById("rewrap1");
+ rewrap1.textContent = "abc";
+ });
+ await reordered;
+ testTextAtOffset(rewrap, BOUNDARY_LINE_START, [
+ [0, 1, "ab", 0, 2],
+ [2, 3, "cd", 2, 4],
+ [4, 6, "ef", 4, 6],
+ ]);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test retrieval of text offsets when an invalid offset is given.
+ */
+addAccessibleTask(
+ `<p id="p">test</p>`,
+ async function (browser, docAcc) {
+ const p = findAccessibleChildByID(docAcc, "p");
+ testTextAtOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]);
+ testTextBeforeOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]);
+ testTextAfterOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]);
+ },
+ {
+ // The old HyperTextAccessible implementation doesn't crash, but it returns
+ // different offsets. This doesn't matter because they're invalid either
+ // way. Since the new HyperTextAccessibleBase implementation is all we will
+ // have soon, just test that.
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test HyperText embedded object methods.
+ */
+addAccessibleTask(
+ `<div id="container">a<a id="link" href="https://example.com/">b</a>c</div>`,
+ async function (browser, docAcc) {
+ const container = findAccessibleChildByID(docAcc, "container", [
+ nsIAccessibleHyperText,
+ ]);
+ is(container.linkCount, 1, "container linkCount is 1");
+ let link = container.getLinkAt(0);
+ queryInterfaces(link, [nsIAccessible, nsIAccessibleHyperText]);
+ is(getAccessibleDOMNodeID(link), "link", "LinkAt 0 is the link");
+ is(container.getLinkIndex(link), 0, "getLinkIndex for link is 0");
+ is(link.startIndex, 1, "link's startIndex is 1");
+ is(link.endIndex, 2, "link's endIndex is 2");
+ is(container.getLinkIndexAtOffset(1), 0, "getLinkIndexAtOffset(1) is 0");
+ is(container.getLinkIndexAtOffset(0), -1, "getLinkIndexAtOffset(0) is -1");
+ is(link.linkCount, 0, "link linkCount is 0");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test HyperText embedded object methods near a list bullet.
+ */
+addAccessibleTask(
+ `<ul><li id="li"><a id="link" href="https://example.com/">a</a></li></ul>`,
+ async function (browser, docAcc) {
+ const li = findAccessibleChildByID(docAcc, "li", [nsIAccessibleHyperText]);
+ let link = li.getLinkAt(0);
+ queryInterfaces(link, [nsIAccessible]);
+ is(getAccessibleDOMNodeID(link), "link", "LinkAt 0 is the link");
+ is(li.getLinkIndex(link), 0, "getLinkIndex for link is 0");
+ is(link.startIndex, 2, "link's startIndex is 2");
+ is(li.getLinkIndexAtOffset(2), 0, "getLinkIndexAtOffset(2) is 0");
+ is(li.getLinkIndexAtOffset(0), -1, "getLinkIndexAtOffset(0) is -1");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+const boldAttrs = { "font-weight": "700" };
+
+/**
+ * Test text attribute methods.
+ */
+addAccessibleTask(
+ `
+<p id="plain">ab</p>
+<p id="bold" style="font-weight: bold;">ab</p>
+<p id="partialBold">ab<b>cd</b>ef</p>
+<p id="consecutiveBold">ab<b>cd</b><b>ef</b>gh</p>
+<p id="embeddedObjs">ab<a href="https://example.com/">cd</a><a href="https://example.com/">ef</a><a href="https://example.com/">gh</a>ij</p>
+<p id="empty"></p>
+<p id="fontFamilies" style="font-family: sans-serif;">ab<span style="font-family: monospace;">cd</span><span style="font-family: monospace;">ef</span>gh</p>
+ `,
+ async function (browser, docAcc) {
+ let defAttrs = {
+ "text-position": "baseline",
+ "font-style": "normal",
+ "font-weight": "400",
+ };
+
+ const plain = findAccessibleChildByID(docAcc, "plain");
+ testDefaultTextAttrs(plain, defAttrs, true);
+ for (let offset = 0; offset <= 2; ++offset) {
+ testTextAttrs(plain, offset, {}, defAttrs, 0, 2, true);
+ }
+
+ const bold = findAccessibleChildByID(docAcc, "bold");
+ defAttrs["font-weight"] = "700";
+ testDefaultTextAttrs(bold, defAttrs, true);
+ testTextAttrs(bold, 0, {}, defAttrs, 0, 2, true);
+
+ const partialBold = findAccessibleChildByID(docAcc, "partialBold");
+ defAttrs["font-weight"] = "400";
+ testDefaultTextAttrs(partialBold, defAttrs, true);
+ testTextAttrs(partialBold, 0, {}, defAttrs, 0, 2, true);
+ testTextAttrs(partialBold, 2, boldAttrs, defAttrs, 2, 4, true);
+ testTextAttrs(partialBold, 4, {}, defAttrs, 4, 6, true);
+
+ const consecutiveBold = findAccessibleChildByID(docAcc, "consecutiveBold");
+ testDefaultTextAttrs(consecutiveBold, defAttrs, true);
+ testTextAttrs(consecutiveBold, 0, {}, defAttrs, 0, 2, true);
+ testTextAttrs(consecutiveBold, 2, boldAttrs, defAttrs, 2, 6, true);
+ testTextAttrs(consecutiveBold, 6, {}, defAttrs, 6, 8, true);
+
+ const embeddedObjs = findAccessibleChildByID(docAcc, "embeddedObjs");
+ testDefaultTextAttrs(embeddedObjs, defAttrs, true);
+ testTextAttrs(embeddedObjs, 0, {}, defAttrs, 0, 2, true);
+ for (let offset = 2; offset <= 4; ++offset) {
+ // attrs and defAttrs should be completely empty, so we pass
+ // false for aSkipUnexpectedAttrs.
+ testTextAttrs(embeddedObjs, offset, {}, {}, 2, 5, false);
+ }
+ testTextAttrs(embeddedObjs, 5, {}, defAttrs, 5, 7, true);
+
+ const empty = findAccessibleChildByID(docAcc, "empty");
+ testDefaultTextAttrs(empty, defAttrs, true);
+ testTextAttrs(empty, 0, {}, defAttrs, 0, 0, true);
+
+ const fontFamilies = findAccessibleChildByID(docAcc, "fontFamilies", [
+ nsIAccessibleHyperText,
+ ]);
+ testDefaultTextAttrs(fontFamilies, defAttrs, true);
+ testTextAttrs(fontFamilies, 0, {}, defAttrs, 0, 2, true);
+ testTextAttrs(fontFamilies, 2, {}, defAttrs, 2, 6, true);
+ testTextAttrs(fontFamilies, 6, {}, defAttrs, 6, 8, true);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
diff --git a/accessible/tests/browser/text/browser_text_caret.js b/accessible/tests/browser/text/browser_text_caret.js
new file mode 100644
index 0000000000..e0cea334d6
--- /dev/null
+++ b/accessible/tests/browser/text/browser_text_caret.js
@@ -0,0 +1,452 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/text.js */
+
+/**
+ * Test caret retrieval.
+ */
+addAccessibleTask(
+ `
+<textarea id="textarea"
+ spellcheck="false"
+ style="scrollbar-width: none; font-family: 'Liberation Mono', monospace;"
+ cols="6">ab cd e</textarea>
+<textarea id="empty"></textarea>
+ `,
+ async function (browser, docAcc) {
+ const textarea = findAccessibleChildByID(docAcc, "textarea", [
+ nsIAccessibleText,
+ ]);
+ let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ textarea.takeFocus();
+ let evt = await caretMoved;
+ is(textarea.caretOffset, 0, "Initial caret offset is 0");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "a",
+ 0,
+ 1,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "ab ",
+ 0,
+ 3,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 1, "Caret offset is 1 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "b",
+ 1,
+ 2,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "ab ",
+ 0,
+ 3,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 2, "Caret offset is 2 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ " ",
+ 2,
+ 3,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "ab ",
+ 0,
+ 3,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 3, "Caret offset is 3 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "c",
+ 3,
+ 4,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "cd ",
+ 3,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 4, "Caret offset is 4 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "d",
+ 4,
+ 5,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "cd ",
+ 3,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 5, "Caret offset is 5 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ " ",
+ 5,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "cd ",
+ 3,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 6, "Caret offset is 6 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(evt.isAtEndOfLine, "Caret is at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "",
+ 6,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "cd ",
+ 3,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 6, "Caret offset remains 6 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ // Caret is at start of second line.
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "e",
+ 6,
+ 7,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "e",
+ 6,
+ 7,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "e",
+ 6,
+ 7,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 7, "Caret offset is 7 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(evt.isAtEndOfLine, "Caret is at end of line");
+ // Caret is at end of textarea.
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "",
+ 7,
+ 7,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "e",
+ 6,
+ 7,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "e",
+ 6,
+ 7,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ const empty = findAccessibleChildByID(docAcc, "empty", [nsIAccessibleText]);
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, empty);
+ empty.takeFocus();
+ evt = await caretMoved;
+ is(empty.caretOffset, 0, "Caret offset in empty textarea is 0");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test setting the caret.
+ */
+addAccessibleTask(
+ `
+<textarea id="textarea">ab\nc</textarea>
+<div id="editable" contenteditable>
+ <p id="p">a<a id="link" href="https://example.com/">b</a></p>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const textarea = findAccessibleChildByID(docAcc, "textarea", [
+ nsIAccessibleText,
+ ]);
+ info("textarea: Set caret offset to 0");
+ let focused = waitForEvent(EVENT_FOCUS, textarea);
+ let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ textarea.caretOffset = 0;
+ await focused;
+ await caretMoved;
+ is(textarea.caretOffset, 0, "textarea caret correct");
+ // Test setting caret to another line.
+ info("textarea: Set caret offset to 3");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ textarea.caretOffset = 3;
+ await caretMoved;
+ is(textarea.caretOffset, 3, "textarea caret correct");
+ // Test setting caret to the end.
+ info("textarea: Set caret offset to 4 (end)");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ textarea.caretOffset = 4;
+ await caretMoved;
+ is(textarea.caretOffset, 4, "textarea caret correct");
+
+ const editable = findAccessibleChildByID(docAcc, "editable", [
+ nsIAccessibleText,
+ ]);
+ focused = waitForEvent(EVENT_FOCUS, editable);
+ editable.takeFocus();
+ await focused;
+ const p = findAccessibleChildByID(docAcc, "p", [nsIAccessibleText]);
+ info("p: Set caret offset to 0");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, p);
+ p.caretOffset = 0;
+ await focused;
+ await caretMoved;
+ is(p.caretOffset, 0, "p caret correct");
+ const link = findAccessibleChildByID(docAcc, "link", [nsIAccessibleText]);
+ info("link: Set caret offset to 0");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, link);
+ link.caretOffset = 0;
+ await caretMoved;
+ is(link.caretOffset, 0, "link caret correct");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/text/browser_text_paragraph_boundary.js b/accessible/tests/browser/text/browser_text_paragraph_boundary.js
new file mode 100644
index 0000000000..04e64520e8
--- /dev/null
+++ b/accessible/tests/browser/text/browser_text_paragraph_boundary.js
@@ -0,0 +1,22 @@
+/* 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";
+
+// Test that we don't crash the parent process when querying the paragraph
+// boundary on an Accessible which has remote ProxyAccessible descendants.
+addAccessibleTask(
+ `test`,
+ async function testParagraphBoundaryWithRemoteDescendants(browser, accDoc) {
+ const root = getRootAccessible(document).QueryInterface(
+ Ci.nsIAccessibleText
+ );
+ let start = {};
+ let end = {};
+ // The offsets will change as the Firefox UI changes. We don't really care
+ // what they are, just that we don't crash.
+ root.getTextAtOffset(0, nsIAccessibleText.BOUNDARY_PARAGRAPH, start, end);
+ ok(true, "Getting paragraph boundary succeeded");
+ }
+);
diff --git a/accessible/tests/browser/text/browser_text_selection.js b/accessible/tests/browser/text/browser_text_selection.js
new file mode 100644
index 0000000000..3b47d5f36e
--- /dev/null
+++ b/accessible/tests/browser/text/browser_text_selection.js
@@ -0,0 +1,344 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/text.js */
+
+function waitForSelectionChange(selectionAcc, caretAcc) {
+ if (!caretAcc) {
+ caretAcc = selectionAcc;
+ }
+ return waitForEvents(
+ [
+ [EVENT_TEXT_SELECTION_CHANGED, selectionAcc],
+ // We must swallow the caret events as well to avoid confusion with later,
+ // unrelated caret events.
+ [EVENT_TEXT_CARET_MOVED, caretAcc],
+ ],
+ true
+ );
+}
+
+function changeDomSelection(
+ browser,
+ anchorId,
+ anchorOffset,
+ focusId,
+ focusOffset
+) {
+ return invokeContentTask(
+ browser,
+ [anchorId, anchorOffset, focusId, focusOffset],
+ (
+ contentAnchorId,
+ contentAnchorOffset,
+ contentFocusId,
+ contentFocusOffset
+ ) => {
+ // We want the text node, so we use firstChild.
+ content.window
+ .getSelection()
+ .setBaseAndExtent(
+ content.document.getElementById(contentAnchorId).firstChild,
+ contentAnchorOffset,
+ content.document.getElementById(contentFocusId).firstChild,
+ contentFocusOffset
+ );
+ }
+ );
+}
+
+function testSelectionRange(
+ browser,
+ root,
+ startContainer,
+ startOffset,
+ endContainer,
+ endOffset
+) {
+ let selRange = root.selectionRanges.queryElementAt(0, nsIAccessibleTextRange);
+ testTextRange(
+ selRange,
+ getAccessibleDOMNodeID(root),
+ startContainer,
+ startOffset,
+ endContainer,
+ endOffset
+ );
+}
+
+/**
+ * Test text selection via keyboard.
+ */
+addAccessibleTask(
+ `
+<textarea id="textarea">ab</textarea>
+<div id="editable" contenteditable>
+ <p id="p1">a</p>
+ <p id="p2">bc</p>
+ <p id="pWithLink">d<a id="link" href="https://example.com/">e</a><span id="textAfterLink">f</span></p>
+</div>
+ `,
+ async function (browser, docAcc) {
+ queryInterfaces(docAcc, [nsIAccessibleText]);
+
+ const textarea = findAccessibleChildByID(docAcc, "textarea", [
+ nsIAccessibleText,
+ ]);
+ info("Focusing textarea");
+ let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ textarea.takeFocus();
+ await caretMoved;
+ testSelectionRange(browser, textarea, textarea, 0, textarea, 0);
+ is(textarea.selectionCount, 0, "textarea selectionCount is 0");
+ is(docAcc.selectionCount, 0, "document selectionCount is 0");
+
+ info("Selecting a in textarea");
+ let selChanged = waitForSelectionChange(textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ await selChanged;
+ testSelectionRange(browser, textarea, textarea, 0, textarea, 1);
+ testTextGetSelection(textarea, 0, 1, 0);
+
+ info("Selecting b in textarea");
+ selChanged = waitForSelectionChange(textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ await selChanged;
+ testSelectionRange(browser, textarea, textarea, 0, textarea, 2);
+ testTextGetSelection(textarea, 0, 2, 0);
+
+ info("Unselecting b in textarea");
+ selChanged = waitForSelectionChange(textarea);
+ EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
+ await selChanged;
+ testSelectionRange(browser, textarea, textarea, 0, textarea, 1);
+ testTextGetSelection(textarea, 0, 1, 0);
+
+ info("Unselecting a in textarea");
+ // We don't fire selection changed when the selection collapses.
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
+ await caretMoved;
+ testSelectionRange(browser, textarea, textarea, 0, textarea, 0);
+ is(textarea.selectionCount, 0, "textarea selectionCount is 0");
+
+ const editable = findAccessibleChildByID(docAcc, "editable", [
+ nsIAccessibleText,
+ ]);
+ const p1 = findAccessibleChildByID(docAcc, "p1", [nsIAccessibleText]);
+ info("Focusing editable, caret to start");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, p1);
+ await changeDomSelection(browser, "p1", 0, "p1", 0);
+ await caretMoved;
+ testSelectionRange(browser, editable, p1, 0, p1, 0);
+ is(editable.selectionCount, 0, "editable selectionCount is 0");
+ is(p1.selectionCount, 0, "p1 selectionCount is 0");
+ is(docAcc.selectionCount, 0, "document selectionCount is 0");
+
+ info("Selecting a in editable");
+ selChanged = waitForSelectionChange(p1);
+ await changeDomSelection(browser, "p1", 0, "p1", 1);
+ await selChanged;
+ testSelectionRange(browser, editable, p1, 0, p1, 1);
+ testTextGetSelection(editable, 0, 1, 0);
+ testTextGetSelection(p1, 0, 1, 0);
+ const p2 = findAccessibleChildByID(docAcc, "p2", [nsIAccessibleText]);
+ if (browser.isRemoteBrowser) {
+ is(p2.selectionCount, 0, "p2 selectionCount is 0");
+ } else {
+ todo(
+ false,
+ "Siblings report wrong selection in non-cache implementation"
+ );
+ }
+
+ // Selecting across two Accessibles with only a partial selection in the
+ // second.
+ info("Selecting ab in editable");
+ selChanged = waitForSelectionChange(editable, p2);
+ await changeDomSelection(browser, "p1", 0, "p2", 1);
+ await selChanged;
+ testSelectionRange(browser, editable, p1, 0, p2, 1);
+ testTextGetSelection(editable, 0, 2, 0);
+ testTextGetSelection(p1, 0, 1, 0);
+ testTextGetSelection(p2, 0, 1, 0);
+
+ const pWithLink = findAccessibleChildByID(docAcc, "pWithLink", [
+ nsIAccessibleText,
+ ]);
+ const link = findAccessibleChildByID(docAcc, "link", [nsIAccessibleText]);
+ // Selecting both text and a link.
+ info("Selecting de in editable");
+ selChanged = waitForSelectionChange(pWithLink, link);
+ await changeDomSelection(browser, "pWithLink", 0, "link", 1);
+ await selChanged;
+ testSelectionRange(browser, editable, pWithLink, 0, link, 1);
+ testTextGetSelection(editable, 2, 3, 0);
+ testTextGetSelection(pWithLink, 0, 2, 0);
+ testTextGetSelection(link, 0, 1, 0);
+
+ // Selecting a link and text on either side.
+ info("Selecting def in editable");
+ selChanged = waitForSelectionChange(pWithLink, pWithLink);
+ await changeDomSelection(browser, "pWithLink", 0, "textAfterLink", 1);
+ await selChanged;
+ testSelectionRange(browser, editable, pWithLink, 0, pWithLink, 3);
+ testTextGetSelection(editable, 2, 3, 0);
+ testTextGetSelection(pWithLink, 0, 3, 0);
+ testTextGetSelection(link, 0, 1, 0);
+
+ // Noncontiguous selection.
+ info("Selecting a in editable");
+ selChanged = waitForSelectionChange(p1);
+ await changeDomSelection(browser, "p1", 0, "p1", 1);
+ await selChanged;
+ info("Adding c to selection in editable");
+ selChanged = waitForSelectionChange(p2);
+ await invokeContentTask(browser, [], () => {
+ const r = content.document.createRange();
+ const p2text = content.document.getElementById("p2").firstChild;
+ r.setStart(p2text, 0);
+ r.setEnd(p2text, 1);
+ content.window.getSelection().addRange(r);
+ });
+ await selChanged;
+ let selRanges = editable.selectionRanges;
+ is(selRanges.length, 2, "2 selection ranges");
+ testTextRange(
+ selRanges.queryElementAt(0, nsIAccessibleTextRange),
+ "range 0",
+ p1,
+ 0,
+ p1,
+ 1
+ );
+ testTextRange(
+ selRanges.queryElementAt(1, nsIAccessibleTextRange),
+ "range 1",
+ p2,
+ 0,
+ p2,
+ 1
+ );
+ is(editable.selectionCount, 2, "editable selectionCount is 2");
+ testTextGetSelection(editable, 0, 1, 0);
+ testTextGetSelection(editable, 1, 2, 1);
+ if (browser.isRemoteBrowser) {
+ is(p1.selectionCount, 1, "p1 selectionCount is 1");
+ testTextGetSelection(p1, 0, 1, 0);
+ is(p2.selectionCount, 1, "p2 selectionCount is 1");
+ testTextGetSelection(p2, 0, 1, 0);
+ } else {
+ todo(
+ false,
+ "Siblings report wrong selection in non-cache implementation"
+ );
+ }
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Tabbing to an input selects all its text. Test that the cached selection
+ *reflects this. This has to be done separately from the other selection tests
+ * because prior contentEditable selection changes the events that get fired.
+ */
+addAccessibleTask(
+ `
+<button id="before">Before</button>
+<input id="input" value="test">
+ `,
+ async function (browser, docAcc) {
+ // The tab order is different when there's an iframe, so focus a control
+ // before the input to make tab consistent.
+ info("Focusing before");
+ const before = findAccessibleChildByID(docAcc, "before");
+ // Focusing a button fires a selection event. We must swallow this to
+ // avoid confusing the later test.
+ let events = waitForOrderedEvents([
+ [EVENT_FOCUS, before],
+ [EVENT_TEXT_SELECTION_CHANGED, docAcc],
+ ]);
+ before.takeFocus();
+ await events;
+
+ const input = findAccessibleChildByID(docAcc, "input", [nsIAccessibleText]);
+ info("Tabbing to input");
+ events = waitForEvents(
+ {
+ expected: [
+ [EVENT_FOCUS, input],
+ [EVENT_TEXT_SELECTION_CHANGED, input],
+ ],
+ unexpected: [[EVENT_TEXT_SELECTION_CHANGED, docAcc]],
+ },
+ "input",
+ false,
+ (args, task) => invokeContentTask(browser, args, task)
+ );
+ EventUtils.synthesizeKey("KEY_Tab");
+ await events;
+ testSelectionRange(browser, input, input, 0, input, 4);
+ testTextGetSelection(input, 0, 4, 0);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test text selection via API.
+ */
+addAccessibleTask(
+ `
+ <p id="paragraph">hello world</p>
+ <ol>
+ <li id="li">Number one</li>
+ </ol>
+ `,
+ async function (browser, docAcc) {
+ const paragraph = findAccessibleChildByID(docAcc, "paragraph", [
+ nsIAccessibleText,
+ ]);
+
+ let selChanged = waitForSelectionChange(paragraph);
+ paragraph.setSelectionBounds(0, 2, 4);
+ await selChanged;
+ testTextGetSelection(paragraph, 2, 4, 0);
+
+ selChanged = waitForSelectionChange(paragraph);
+ paragraph.addSelection(6, 10);
+ await selChanged;
+ testTextGetSelection(paragraph, 6, 10, 1);
+ is(paragraph.selectionCount, 2, "paragraph selectionCount is 2");
+
+ selChanged = waitForSelectionChange(paragraph);
+ paragraph.removeSelection(0);
+ await selChanged;
+ testTextGetSelection(paragraph, 6, 10, 0);
+ is(paragraph.selectionCount, 1, "paragraph selectionCount is 1");
+
+ const li = findAccessibleChildByID(docAcc, "li", [nsIAccessibleText]);
+
+ selChanged = waitForSelectionChange(li);
+ li.setSelectionBounds(0, 1, 8);
+ await selChanged;
+ testTextGetSelection(li, 3, 8, 0);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
diff --git a/accessible/tests/browser/text/browser_text_spelling.js b/accessible/tests/browser/text/browser_text_spelling.js
new file mode 100644
index 0000000000..14c5c16be4
--- /dev/null
+++ b/accessible/tests/browser/text/browser_text_spelling.js
@@ -0,0 +1,151 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/text.js */
+/* import-globals-from ../../mochitest/attributes.js */
+loadScripts(
+ { name: "text.js", dir: MOCHITESTS_DIR },
+ { name: "attributes.js", dir: MOCHITESTS_DIR }
+);
+
+const boldAttrs = { "font-weight": "700" };
+
+/*
+ * Given a text accessible and a list of ranges
+ * check if those ranges match the misspelled ranges in the accessible.
+ */
+function misspelledRangesMatch(acc, ranges) {
+ let offset = 0;
+ let expectedRanges = [...ranges];
+ let charCount = acc.characterCount;
+ while (offset < charCount) {
+ let start = {};
+ let end = {};
+ let attributes = acc.getTextAttributes(false, offset, start, end);
+ offset = end.value;
+ try {
+ if (attributes.getStringProperty("invalid") == "spelling") {
+ let expected = expectedRanges.shift();
+ if (
+ !expected ||
+ expected[0] != start.value ||
+ expected[1] != end.value
+ ) {
+ return false;
+ }
+ }
+ } catch (err) {}
+ }
+
+ return !expectedRanges.length;
+}
+
+/*
+ * Returns a promise that resolves after a text attribute changed event
+ * brings us to a state where the misspelled ranges match.
+ */
+async function waitForMisspelledRanges(acc, ranges) {
+ await waitForEvent(EVENT_TEXT_ATTRIBUTE_CHANGED);
+ await untilCacheOk(
+ () => misspelledRangesMatch(acc, ranges),
+ `Misspelled ranges match: ${JSON.stringify(ranges)}`
+ );
+}
+
+/**
+ * Test spelling errors.
+ */
+addAccessibleTask(
+ `
+<textarea id="textarea" spellcheck="true">test tset tset test</textarea>
+<div contenteditable id="editable" spellcheck="true">plain<span> ts</span>et <b>bold</b></div>
+ `,
+ async function (browser, docAcc) {
+ const textarea = findAccessibleChildByID(docAcc, "textarea", [
+ nsIAccessibleText,
+ ]);
+ info("Focusing textarea");
+ let spellingChanged = waitForMisspelledRanges(textarea, [
+ [5, 9],
+ [10, 14],
+ ]);
+ textarea.takeFocus();
+ await spellingChanged;
+
+ // Test removal of a spelling error.
+ info('textarea: Changing first "tset" to "test"');
+ // setTextRange fires multiple EVENT_TEXT_ATTRIBUTE_CHANGED, so replace by
+ // selecting and typing instead.
+ spellingChanged = waitForMisspelledRanges(textarea, [[10, 14]]);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("textarea").setSelectionRange(5, 9);
+ });
+ EventUtils.sendString("test");
+ // Move the cursor to trigger spell check.
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ await spellingChanged;
+
+ // Test addition of a spelling error.
+ info('textarea: Changing it back to "tset"');
+ spellingChanged = waitForMisspelledRanges(textarea, [
+ [5, 9],
+ [10, 14],
+ ]);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("textarea").setSelectionRange(5, 9);
+ });
+ EventUtils.sendString("tset");
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ await spellingChanged;
+
+ // Ensure that changing the text without changing any spelling errors
+ // correctly updates offsets.
+ info('textarea: Changing first "test" to "the"');
+ // Spelling errors don't change, so we won't get
+ // EVENT_TEXT_ATTRIBUTE_CHANGED. We change the text, wait for the insertion
+ // and then select a character so we know when the change is done.
+ let inserted = waitForEvent(EVENT_TEXT_INSERTED, textarea);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("textarea").setSelectionRange(0, 4);
+ });
+ EventUtils.sendString("the");
+ await inserted;
+ let selected = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ await selected;
+ const expectedRanges = [
+ [4, 8],
+ [9, 13],
+ ];
+ await untilCacheOk(
+ () => misspelledRangesMatch(textarea, expectedRanges),
+ `Misspelled ranges match: ${JSON.stringify(expectedRanges)}`
+ );
+
+ const editable = findAccessibleChildByID(docAcc, "editable", [
+ nsIAccessibleText,
+ ]);
+ info("Focusing editable");
+ spellingChanged = waitForMisspelledRanges(editable, [[6, 10]]);
+ editable.takeFocus();
+ await spellingChanged;
+ // Test normal text and spelling errors crossing text nodes.
+ testTextAttrs(editable, 0, {}, {}, 0, 6, true); // "plain "
+ // Ensure we detect the spelling error even though there is a style change
+ // after it.
+ testTextAttrs(editable, 6, { invalid: "spelling" }, {}, 6, 10, true); // "tset"
+ testTextAttrs(editable, 10, {}, {}, 10, 11, true); // " "
+ // Ensure a style change is still detected in the presence of a spelling
+ // error.
+ testTextAttrs(editable, 11, boldAttrs, {}, 11, 15, true); // "bold"
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
diff --git a/accessible/tests/browser/text/browser_textleafpoint.js b/accessible/tests/browser/text/browser_textleafpoint.js
new file mode 100644
index 0000000000..894e982142
--- /dev/null
+++ b/accessible/tests/browser/text/browser_textleafpoint.js
@@ -0,0 +1,485 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/text.js */
+
+addAccessibleTask(
+ `
+ <p id="p" style="white-space: pre-line;">A bug
+h<a href="#">id i</a>n
+the <strong>big</strong> rug.</p>
+`,
+ function (browser, docAcc) {
+ const container = findAccessibleChildByID(docAcc, "p");
+ const firstPoint = createTextLeafPoint(container, 0);
+ const lastPoint = createTextLeafPoint(container, kTextEndOffset);
+
+ let charSequence = [
+ ...textBoundaryGenerator(firstPoint, BOUNDARY_CHAR, DIRECTION_NEXT),
+ ];
+
+ testPointEqual(
+ firstPoint,
+ charSequence[0],
+ "Point constructed via container and offset 0 is first character point."
+ );
+ testPointEqual(
+ lastPoint,
+ charSequence[charSequence.length - 1],
+ "Point constructed via container and kTextEndOffset is last character point."
+ );
+
+ const expectedCharSequence = [
+ ["A bug\nh", 0],
+ ["A bug\nh", 1],
+ ["A bug\nh", 2],
+ ["A bug\nh", 3],
+ ["A bug\nh", 4],
+ ["A bug\nh", 5],
+ ["A bug\nh", 6],
+ ["id i", 0],
+ ["id i", 1],
+ ["id i", 2],
+ ["id i", 3],
+ ["n\nthe ", 0],
+ ["n\nthe ", 1],
+ ["n\nthe ", 2],
+ ["n\nthe ", 3],
+ ["n\nthe ", 4],
+ ["n\nthe ", 5],
+ ["big", 0],
+ ["big", 1],
+ ["big", 2],
+ [" rug.", 0],
+ [" rug.", 1],
+ [" rug.", 2],
+ [" rug.", 3],
+ [" rug.", 4],
+ [" rug.", 5],
+ ];
+
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_CHAR,
+ DIRECTION_NEXT,
+ expectedCharSequence,
+ "Forward BOUNDARY_CHAR sequence is correct"
+ );
+
+ testBoundarySequence(
+ lastPoint,
+ BOUNDARY_CHAR,
+ DIRECTION_PREVIOUS,
+ [...expectedCharSequence].reverse(),
+ "Backward BOUNDARY_CHAR sequence is correct"
+ );
+
+ const expectedWordStartSequence = [
+ ["A bug\nh", 0],
+ ["A bug\nh", 2],
+ ["A bug\nh", 6],
+ ["id i", 3],
+ ["n\nthe ", 2],
+ ["big", 0],
+ [" rug.", 1],
+ ];
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_WORD_START,
+ DIRECTION_NEXT,
+ // Add last point in doc
+ [...expectedWordStartSequence, readablePoint(lastPoint)],
+ "Forward BOUNDARY_WORD_START sequence is correct"
+ );
+
+ testBoundarySequence(
+ lastPoint,
+ BOUNDARY_WORD_START,
+ DIRECTION_PREVIOUS,
+ [...expectedWordStartSequence].reverse(),
+ "Backward BOUNDARY_WORD_START sequence is correct"
+ );
+
+ const expectedWordEndSequence = [
+ ["A bug\nh", 1],
+ ["A bug\nh", 5],
+ ["id i", 2],
+ ["n\nthe ", 1],
+ ["n\nthe ", 5],
+ [" rug.", 0],
+ [" rug.", 5],
+ ];
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_WORD_END,
+ DIRECTION_NEXT,
+ expectedWordEndSequence,
+ "Forward BOUNDARY_WORD_END sequence is correct"
+ );
+
+ testBoundarySequence(
+ lastPoint,
+ BOUNDARY_WORD_END,
+ DIRECTION_PREVIOUS,
+ [readablePoint(firstPoint), ...expectedWordEndSequence].reverse(),
+ "Backward BOUNDARY_WORD_END sequence is correct"
+ );
+
+ const expectedLineStartSequence = [
+ ["A bug\nh", 0],
+ ["A bug\nh", 6],
+ ["n\nthe ", 2],
+ ];
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_LINE_START,
+ DIRECTION_NEXT,
+ // Add last point in doc
+ [...expectedLineStartSequence, readablePoint(lastPoint)],
+ "Forward BOUNDARY_LINE_START sequence is correct"
+ );
+
+ testBoundarySequence(
+ lastPoint,
+ BOUNDARY_LINE_START,
+ DIRECTION_PREVIOUS,
+ [...expectedLineStartSequence].reverse(),
+ "Backward BOUNDARY_LINE_START sequence is correct"
+ );
+
+ const expectedLineEndSequence = [
+ ["A bug\nh", 5],
+ ["n\nthe ", 1],
+ ];
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_LINE_END,
+ DIRECTION_NEXT,
+ // Add last point in doc
+ [...expectedLineEndSequence, readablePoint(lastPoint)],
+ "Forward BOUNDARY_LINE_END sequence is correct",
+ { todo: true }
+ );
+
+ testBoundarySequence(
+ lastPoint,
+ BOUNDARY_LINE_END,
+ DIRECTION_PREVIOUS,
+ [readablePoint(firstPoint), ...expectedLineEndSequence].reverse(),
+ "Backward BOUNDARY_LINE_END sequence is correct"
+ );
+ },
+ { chrome: true, topLevel: true, iframe: false, remoteIframe: false }
+);
+
+addAccessibleTask(
+ `<p id="p">
+ Rob ca<input id="i1" value="n m">op up.
+ </p>`,
+ function (browser, docAcc) {
+ const container = findAccessibleChildByID(docAcc, "p");
+ const firstPoint = createTextLeafPoint(container, 0);
+ const lastPoint = createTextLeafPoint(container, kTextEndOffset);
+
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_CHAR,
+ DIRECTION_NEXT,
+ [
+ ["Rob ca", 0],
+ ["Rob ca", 1],
+ ["Rob ca", 2],
+ ["Rob ca", 3],
+ ["Rob ca", 4],
+ ["Rob ca", 5],
+ ["n m", 0],
+ ["n m", 1],
+ ["n m", 2],
+ ["n m", 3],
+ ],
+ "Forward BOUNDARY_CHAR sequence when stopping in editable is correct",
+ { flags: BOUNDARY_FLAG_STOP_IN_EDITABLE }
+ );
+
+ testBoundarySequence(
+ lastPoint,
+ BOUNDARY_CHAR,
+ DIRECTION_PREVIOUS,
+ [
+ ["op up. ", 7],
+ ["op up. ", 6],
+ ["op up. ", 5],
+ ["op up. ", 4],
+ ["op up. ", 3],
+ ["op up. ", 2],
+ ["op up. ", 1],
+ ["op up. ", 0],
+ ["n m", 2],
+ ["n m", 1],
+ ["n m", 0],
+ ],
+ "Backward BOUNDARY_CHAR sequence when stopping in editable is correct",
+ { flags: BOUNDARY_FLAG_STOP_IN_EDITABLE }
+ );
+
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_WORD_START,
+ DIRECTION_NEXT,
+ [
+ ["Rob ca", 0],
+ ["Rob ca", 4],
+ ["n m", 2],
+ ],
+ "Forward BOUNDARY_WORD_START sequence when stopping in editable is correct",
+ {
+ flags: BOUNDARY_FLAG_STOP_IN_EDITABLE,
+ todo: true, // Shouldn't consider end of input a word start
+ }
+ );
+ },
+ { chrome: true, topLevel: true, iframe: false, remoteIframe: false }
+);
+
+addAccessibleTask(
+ `
+ <p id="p" style="white-space: pre-line;">A bug
+on a <span style="display: block;">rug</span></p>
+ <p id="p2">
+ Numbers:
+ </p>
+ <ul>
+ <li>One</li>
+ <li>Two</li>
+ <li>Three</li>
+ </ul>`,
+ function (browser, docAcc) {
+ const firstPoint = createTextLeafPoint(docAcc, 0);
+ const lastPoint = createTextLeafPoint(docAcc, kTextEndOffset);
+
+ const expectedParagraphStart = [
+ ["A bug\non a ", 0],
+ ["A bug\non a ", 6],
+ ["rug", 0],
+ ["Numbers: ", 0],
+ ["• ", 0],
+ ["• ", 0],
+ ["• ", 0],
+ ];
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_PARAGRAPH,
+ DIRECTION_NEXT,
+ [...expectedParagraphStart, readablePoint(lastPoint)],
+ "Forward BOUNDARY_PARAGRAPH sequence is correct"
+ );
+
+ const paragraphStart = createTextLeafPoint(
+ findAccessibleChildByID(docAcc, "p2").firstChild,
+ 0
+ );
+ const wordEnd = paragraphStart.findBoundary(
+ BOUNDARY_WORD_END,
+ DIRECTION_NEXT,
+ BOUNDARY_FLAG_INCLUDE_ORIGIN
+ );
+ testPointEqual(
+ wordEnd,
+ paragraphStart,
+ "The word end from the previous block is the first point in this block"
+ );
+ },
+ { chrome: true, topLevel: true, iframe: false, remoteIframe: false }
+);
+
+// Test for skipping list item bullets.
+addAccessibleTask(
+ `<ul>
+ <li>One</li>
+ <li>Two</li>
+ <li style="white-space: pre-line;">Three
+Four</li>
+ </ul>`,
+ function (browser, docAcc) {
+ const firstPoint = createTextLeafPoint(docAcc, 0);
+ const lastPoint = createTextLeafPoint(docAcc, kTextEndOffset);
+
+ const firstNonMarkerPoint = firstPoint.findBoundary(
+ BOUNDARY_CHAR,
+ DIRECTION_NEXT,
+ BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER | BOUNDARY_FLAG_INCLUDE_ORIGIN
+ );
+ Assert.deepEqual(
+ readablePoint(firstNonMarkerPoint),
+ ["One", 0],
+ "First non-marker point is correct"
+ );
+
+ const expectedParagraphStart = [
+ ["One", 0],
+ ["Two", 0],
+ ["Three\nFour", 0],
+ ["Three\nFour", 6],
+ ];
+
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_PARAGRAPH,
+ DIRECTION_NEXT,
+ [...expectedParagraphStart, readablePoint(lastPoint)],
+ "Forward BOUNDARY_PARAGRAPH skipping list item markers sequence is correct",
+ { flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
+ );
+
+ testBoundarySequence(
+ lastPoint,
+ BOUNDARY_PARAGRAPH,
+ DIRECTION_PREVIOUS,
+ [...expectedParagraphStart].reverse(),
+ "Backward BOUNDARY_PARAGRAPH skipping list item markers sequence is correct",
+ { flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
+ );
+
+ const expectedCharSequence = [
+ ["One", 0],
+ ["One", 1],
+ ["One", 2],
+ ["Two", 0],
+ ["Two", 1],
+ ["Two", 2],
+ ["Three\nFour", 0],
+ ["Three\nFour", 1],
+ ["Three\nFour", 2],
+ ["Three\nFour", 3],
+ ["Three\nFour", 4],
+ ["Three\nFour", 5],
+ ["Three\nFour", 6],
+ ["Three\nFour", 7],
+ ["Three\nFour", 8],
+ ["Three\nFour", 9],
+ ["Three\nFour", 10],
+ ];
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_CHAR,
+ DIRECTION_NEXT,
+ expectedCharSequence,
+ "Forward BOUNDARY_CHAR skipping list item markers sequence is correct",
+ { flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
+ );
+
+ testBoundarySequence(
+ lastPoint,
+ BOUNDARY_CHAR,
+ DIRECTION_PREVIOUS,
+ [...expectedCharSequence].reverse(),
+ "Backward BOUNDARY_CHAR skipping list item markers sequence is correct",
+ { flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
+ );
+
+ const expectedWordStartSequence = [
+ ["One", 0],
+ ["Two", 0],
+ ["Three\nFour", 0],
+ ["Three\nFour", 6],
+ ];
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_WORD_START,
+ DIRECTION_NEXT,
+ [...expectedWordStartSequence, readablePoint(lastPoint)],
+ "Forward BOUNDARY_WORD_START skipping list item markers sequence is correct",
+ { flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
+ );
+
+ testBoundarySequence(
+ lastPoint,
+ BOUNDARY_WORD_START,
+ DIRECTION_PREVIOUS,
+ [...expectedWordStartSequence].reverse(),
+ "Backward BOUNDARY_WORD_START skipping list item markers sequence is correct",
+ { flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
+ );
+
+ const expectedWordEndSequence = [
+ ["Two", 0],
+ ["Three\nFour", 0],
+ ["Three\nFour", 5],
+ ["Three\nFour", 10],
+ ];
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_WORD_END,
+ DIRECTION_NEXT,
+ expectedWordEndSequence,
+ "Forward BOUNDARY_WORD_END skipping list item markers sequence is correct",
+ { flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
+ );
+
+ testBoundarySequence(
+ lastPoint,
+ BOUNDARY_WORD_END,
+ DIRECTION_PREVIOUS,
+ [
+ readablePoint(firstNonMarkerPoint),
+ ...expectedWordEndSequence,
+ ].reverse(),
+ "Backward BOUNDARY_WORD_END skipping list item markers sequence is correct",
+ { flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
+ );
+
+ const expectedLineStartSequence = [
+ ["One", 0],
+ ["Two", 0],
+ ["Three\nFour", 0],
+ ["Three\nFour", 6],
+ ];
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_LINE_START,
+ DIRECTION_NEXT,
+ // Add last point in doc
+ [...expectedLineStartSequence, readablePoint(lastPoint)],
+ "Forward BOUNDARY_LINE_START skipping list item markers sequence is correct",
+ { flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
+ );
+
+ testBoundarySequence(
+ lastPoint,
+ BOUNDARY_LINE_START,
+ DIRECTION_PREVIOUS,
+ // Add last point in doc
+ [...expectedLineStartSequence].reverse(),
+ "Backward BOUNDARY_LINE_START skipping list item markers sequence is correct",
+ { flags: BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER }
+ );
+ },
+ { chrome: true, topLevel: true, iframe: false, remoteIframe: false }
+);
+
+/**
+ * Test the paragraph boundary on tables.
+ */
+addAccessibleTask(
+ `
+<table id="table">
+ <tr><th>a</th><td>b</td></tr>
+ <tr><td>c</td><td>d</td></tr>
+</table>
+ `,
+ async function (browser, docAcc) {
+ const firstPoint = createTextLeafPoint(docAcc, 0);
+ const lastPoint = createTextLeafPoint(docAcc, kTextEndOffset);
+ testBoundarySequence(
+ firstPoint,
+ BOUNDARY_PARAGRAPH,
+ DIRECTION_NEXT,
+ [["a", 0], ["b", 0], ["c", 0], ["d", 0], readablePoint(lastPoint)],
+ "Forward BOUNDARY_PARAGRAPH sequence is correct"
+ );
+ },
+ { chrome: true, topLevel: true, iframe: false, remoteIframe: false }
+);
diff --git a/accessible/tests/browser/text/head.js b/accessible/tests/browser/text/head.js
new file mode 100644
index 0000000000..fa4b095892
--- /dev/null
+++ b/accessible/tests/browser/text/head.js
@@ -0,0 +1,276 @@
+/* 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";
+
+/* exported createTextLeafPoint, DIRECTION_NEXT, DIRECTION_PREVIOUS,
+ BOUNDARY_FLAG_DEFAULT, BOUNDARY_FLAG_INCLUDE_ORIGIN,
+ BOUNDARY_FLAG_STOP_IN_EDITABLE, BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER,
+ readablePoint, testPointEqual, textBoundaryGenerator, testBoundarySequence,
+ isFinalValueCorrect, isFinalValueCorrect, testInsertText, testDeleteText,
+ testCopyText, testPasteText, testCutText, testSetTextContents */
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js.
+
+/* import-globals-from ../../mochitest/role.js */
+
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "text.js", dir: MOCHITESTS_DIR },
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR }
+);
+
+const DIRECTION_NEXT = Ci.nsIAccessibleTextLeafPoint.DIRECTION_NEXT;
+const DIRECTION_PREVIOUS = Ci.nsIAccessibleTextLeafPoint.DIRECTION_PREVIOUS;
+
+const BOUNDARY_FLAG_DEFAULT =
+ Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_DEFAULT;
+const BOUNDARY_FLAG_INCLUDE_ORIGIN =
+ Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_INCLUDE_ORIGIN;
+const BOUNDARY_FLAG_STOP_IN_EDITABLE =
+ Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_STOP_IN_EDITABLE;
+const BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER =
+ Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER;
+
+function createTextLeafPoint(acc, offset) {
+ let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ nsIAccessibilityService
+ );
+
+ return accService.createTextLeafPoint(acc, offset);
+}
+
+// Converts an nsIAccessibleTextLeafPoint into a human/machine
+// readable tuple with a readable accessible and the offset within it.
+// For a point text leaf it would look like this: ["hello", 2],
+// For a point in an empty input it would look like this ["input#name", 0]
+function readablePoint(point) {
+ const readableLeaf = acc => {
+ let tagName = getAccessibleTagName(acc);
+ if (tagName && !tagName.startsWith("_moz_generated")) {
+ let domNodeID = getAccessibleDOMNodeID(acc);
+ if (domNodeID) {
+ return `${tagName}#${domNodeID}`;
+ }
+ return tagName;
+ }
+
+ return acc.name;
+ };
+
+ return [readableLeaf(point.accessible), point.offset];
+}
+
+function sequenceEqual(val, expected, msg) {
+ Assert.deepEqual(val, expected, msg);
+}
+
+// eslint-disable-next-line camelcase
+function sequenceEqualTodo(val, expected, msg) {
+ todo_is(JSON.stringify(val), JSON.stringify(expected), msg);
+}
+
+function pointsEqual(pointA, pointB) {
+ return (
+ pointA.offset == pointB.offset && pointA.accessible == pointB.accessible
+ );
+}
+
+function testPointEqual(pointA, pointB, msg) {
+ is(pointA.offset, pointB.offset, `Offset mismatch - ${msg}`);
+ is(pointA.accessible, pointB.accessible, `Accessible mismatch - ${msg}`);
+}
+
+function* textBoundaryGenerator(
+ firstPoint,
+ boundaryType,
+ direction,
+ flags = BOUNDARY_FLAG_DEFAULT
+) {
+ // Our start point should be inclusive of the given point.
+ let nextLeafPoint = firstPoint.findBoundary(
+ boundaryType,
+ direction,
+ flags | BOUNDARY_FLAG_INCLUDE_ORIGIN
+ );
+ let textLeafPoint = null;
+
+ do {
+ textLeafPoint = nextLeafPoint;
+ yield textLeafPoint;
+ nextLeafPoint = textLeafPoint.findBoundary(boundaryType, direction, flags);
+ } while (!pointsEqual(textLeafPoint, nextLeafPoint));
+}
+
+// This function takes FindBoundary arguments and an expected sequence
+// of boundary points formatted with readablePoint.
+// For example, word starts would look like this:
+// [["one two", 0], ["one two", 4], ["one two", 7]]
+function testBoundarySequence(
+ startPoint,
+ boundaryType,
+ direction,
+ expectedSequence,
+ msg,
+ options = {}
+) {
+ let sequence = [
+ ...textBoundaryGenerator(
+ startPoint,
+ boundaryType,
+ direction,
+ options.flags ? options.flags : BOUNDARY_FLAG_DEFAULT
+ ),
+ ];
+ (options.todo ? sequenceEqualTodo : sequenceEqual)(
+ sequence.map(readablePoint),
+ expectedSequence,
+ msg
+ );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Editable text
+
+async function waitForCopy(browser) {
+ await BrowserTestUtils.waitForContentEvent(browser, "copy", false, evt => {
+ return true;
+ });
+
+ let clipboardData = await invokeContentTask(browser, [], async () => {
+ let text = await content.navigator.clipboard.readText();
+ return text;
+ });
+
+ return clipboardData;
+}
+
+async function isFinalValueCorrect(
+ browser,
+ acc,
+ expectedTextLeafs,
+ msg = "Value is correct"
+) {
+ let value =
+ acc.role == ROLE_ENTRY
+ ? acc.value
+ : await invokeContentTask(browser, [], () => {
+ return content.document.body.textContent;
+ });
+
+ let [before, text, after] = expectedTextLeafs;
+ let finalValue =
+ before && after && !text
+ ? [before, after].join(" ")
+ : [before, text, after].join("");
+
+ is(value.replace("\xa0", " "), finalValue, msg);
+}
+
+function waitForTextChangeEvents(acc, eventSeq) {
+ let events = eventSeq.map(eventType => {
+ return [eventType, acc];
+ });
+
+ if (acc.role == ROLE_ENTRY) {
+ events.push([EVENT_TEXT_VALUE_CHANGE, acc]);
+ }
+
+ return waitForEvents(events);
+}
+
+async function testSetTextContents(acc, text, staticContentOffset, events) {
+ acc.QueryInterface(nsIAccessibleEditableText);
+ let evtPromise = waitForTextChangeEvents(acc, events);
+ acc.setTextContents(text);
+ let evt = (await evtPromise)[0];
+ evt.QueryInterface(nsIAccessibleTextChangeEvent);
+ is(evt.start, staticContentOffset);
+}
+
+async function testInsertText(
+ acc,
+ textToInsert,
+ insertOffset,
+ staticContentOffset
+) {
+ acc.QueryInterface(nsIAccessibleEditableText);
+
+ let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_INSERTED]);
+ acc.insertText(textToInsert, staticContentOffset + insertOffset);
+ let evt = (await evtPromise)[0];
+ evt.QueryInterface(nsIAccessibleTextChangeEvent);
+ is(evt.start, staticContentOffset + insertOffset);
+}
+
+async function testDeleteText(
+ acc,
+ startOffset,
+ endOffset,
+ staticContentOffset
+) {
+ acc.QueryInterface(nsIAccessibleEditableText);
+
+ let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_REMOVED]);
+ acc.deleteText(
+ staticContentOffset + startOffset,
+ staticContentOffset + endOffset
+ );
+ let evt = (await evtPromise)[0];
+ evt.QueryInterface(nsIAccessibleTextChangeEvent);
+ is(evt.start, staticContentOffset + startOffset);
+}
+
+async function testCopyText(
+ acc,
+ startOffset,
+ endOffset,
+ staticContentOffset,
+ browser,
+ aExpectedClipboard = null
+) {
+ acc.QueryInterface(nsIAccessibleEditableText);
+ let copied = waitForCopy(browser);
+ acc.copyText(
+ staticContentOffset + startOffset,
+ staticContentOffset + endOffset
+ );
+ let clipboardText = await copied;
+ if (aExpectedClipboard != null) {
+ is(clipboardText, aExpectedClipboard, "Correct text in clipboard");
+ }
+}
+
+async function testPasteText(acc, insertOffset, staticContentOffset) {
+ acc.QueryInterface(nsIAccessibleEditableText);
+ let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_INSERTED]);
+ acc.pasteText(staticContentOffset + insertOffset);
+
+ let evt = (await evtPromise)[0];
+ evt.QueryInterface(nsIAccessibleTextChangeEvent);
+ // XXX: In non-headless mode pasting text produces several text leaves
+ // and the offset is not what we expect.
+ // is(evt.start, staticContentOffset + insertOffset);
+}
+
+async function testCutText(acc, startOffset, endOffset, staticContentOffset) {
+ acc.QueryInterface(nsIAccessibleEditableText);
+ let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_REMOVED]);
+ acc.cutText(
+ staticContentOffset + startOffset,
+ staticContentOffset + endOffset
+ );
+
+ let evt = (await evtPromise)[0];
+ evt.QueryInterface(nsIAccessibleTextChangeEvent);
+ is(evt.start, staticContentOffset + startOffset);
+}
diff --git a/accessible/tests/browser/tree/browser.ini b/accessible/tests/browser/tree/browser.ini
new file mode 100644
index 0000000000..bb4aedd149
--- /dev/null
+++ b/accessible/tests/browser/tree/browser.ini
@@ -0,0 +1,19 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/browser/*.jsm
+prefs =
+ javascript.options.asyncstack_capture_debuggee_only=false
+
+[browser_aria_owns.js]
+skip-if = true || (verify && !debug && (os == 'linux')) #Bug 1445513
+[browser_browser_element.js]
+[browser_css_content_visibility.js]
+[browser_general.js]
+[browser_lazy_tabs.js]
+[browser_searchbar.js]
+[browser_shadowdom.js]
+[browser_test_nsIAccessibleDocument_URL.js]
diff --git a/accessible/tests/browser/tree/browser_aria_owns.js b/accessible/tests/browser/tree/browser_aria_owns.js
new file mode 100644
index 0000000000..0ca55ed357
--- /dev/null
+++ b/accessible/tests/browser/tree/browser_aria_owns.js
@@ -0,0 +1,278 @@
+/* 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 NO_MOVE = { unexpected: [[EVENT_REORDER, "container"]] };
+let MOVE = { expected: [[EVENT_REORDER, "container"]] };
+
+// Set last ordinal child as aria-owned, should produce no reorder.
+addAccessibleTask(
+ `<ul id="container"><li id="a">Test</li></ul>`,
+ async function (browser, accDoc) {
+ let containerAcc = findAccessibleChildByID(accDoc, "container");
+
+ testChildrenIds(containerAcc, ["a"]);
+
+ await contentSpawnMutation(browser, NO_MOVE, function () {
+ // aria-own ordinal child in place, should be a no-op.
+ content.document
+ .getElementById("container")
+ .setAttribute("aria-owns", "a");
+ });
+
+ testChildrenIds(containerAcc, ["a"]);
+ }
+);
+
+// Add a new ordinal child to a container with an aria-owned child.
+// Order should respect aria-owns.
+addAccessibleTask(
+ `<ul id="container"><li id="a">Test</li></ul>`,
+ async function (browser, accDoc) {
+ let containerAcc = findAccessibleChildByID(accDoc, "container");
+
+ testChildrenIds(containerAcc, ["a"]);
+
+ await contentSpawnMutation(browser, MOVE, function () {
+ let container = content.document.getElementById("container");
+ container.setAttribute("aria-owns", "a");
+
+ let aa = content.document.createElement("li");
+ aa.id = "aa";
+ container.appendChild(aa);
+ });
+
+ testChildrenIds(containerAcc, ["aa", "a"]);
+
+ await contentSpawnMutation(browser, MOVE, function () {
+ content.document.getElementById("container").removeAttribute("aria-owns");
+ });
+
+ testChildrenIds(containerAcc, ["a", "aa"]);
+ }
+);
+
+// Remove a no-move aria-owns attribute, should result in a no-move.
+addAccessibleTask(
+ `<ul id="container" aria-owns="a"><li id="a">Test</li></ul>`,
+ async function (browser, accDoc) {
+ let containerAcc = findAccessibleChildByID(accDoc, "container");
+
+ testChildrenIds(containerAcc, ["a"]);
+
+ await contentSpawnMutation(browser, NO_MOVE, function () {
+ // remove aria-owned child that is already ordinal, should be no-op.
+ content.document.getElementById("container").removeAttribute("aria-owns");
+ });
+
+ testChildrenIds(containerAcc, ["a"]);
+ }
+);
+
+// Attempt to steal an aria-owned child. The attempt should fail.
+addAccessibleTask(
+ `
+ <ul>
+ <li id="a">Test</li>
+ </ul>
+ <ul aria-owns="a"></ul>
+ <ul id="container"></ul>`,
+ async function (browser, accDoc) {
+ let containerAcc = findAccessibleChildByID(accDoc, "container");
+
+ testChildrenIds(containerAcc, []);
+
+ await contentSpawnMutation(browser, NO_MOVE, function () {
+ content.document
+ .getElementById("container")
+ .setAttribute("aria-owns", "a");
+ });
+
+ testChildrenIds(containerAcc, []);
+ }
+);
+
+// Don't aria-own children of <select>
+addAccessibleTask(
+ `
+ <div id="container" role="group" aria-owns="b"></div>
+ <select id="select">
+ <option id="a"></option>
+ <option id="b"></option>
+ </select>`,
+ async function (browser, accDoc) {
+ let containerAcc = findAccessibleChildByID(accDoc, "container");
+ let selectAcc = findAccessibleChildByID(accDoc, "select");
+
+ testChildrenIds(containerAcc, []);
+ testChildrenIds(selectAcc.firstChild, ["a", "b"]);
+ }
+);
+
+// Don't allow <select> to aria-own
+addAccessibleTask(
+ `
+ <div id="container" role="group">
+ <div id="a"></div>
+ <div id="b"></div>
+ </div>
+ <select id="select" aria-owns="a">
+ <option id="c"></option>
+ </select>`,
+ async function (browser, accDoc) {
+ let containerAcc = findAccessibleChildByID(accDoc, "container");
+ let selectAcc = findAccessibleChildByID(accDoc, "select");
+
+ testChildrenIds(containerAcc, ["a", "b"]);
+ testChildrenIds(selectAcc.firstChild, ["c"]);
+ }
+);
+
+// Don't allow one <select> to aria-own an <option> from another <select>.
+addAccessibleTask(
+ `
+ <select id="select1" aria-owns="c">
+ <option id="a"></option>
+ <option id="b"></option>
+ </select>
+ <select id="select2">
+ <option id="c"></option>
+ </select>`,
+ async function (browser, accDoc) {
+ let selectAcc1 = findAccessibleChildByID(accDoc, "select1");
+ let selectAcc2 = findAccessibleChildByID(accDoc, "select2");
+
+ testChildrenIds(selectAcc1.firstChild, ["a", "b"]);
+ testChildrenIds(selectAcc2.firstChild, ["c"]);
+ }
+);
+
+// Don't allow a <select> to reorder its children with aria-owns.
+addAccessibleTask(
+ `
+ <select id="container" aria-owns="c b a">
+ <option id="a"></option>
+ <option id="b"></option>
+ <option id="c"></option>
+ </select>`,
+ async function (browser, accDoc) {
+ let containerAcc = findAccessibleChildByID(accDoc, "container");
+
+ testChildrenIds(containerAcc.firstChild, ["a", "b", "c"]);
+
+ await contentSpawnMutation(browser, NO_MOVE, function () {
+ content.document
+ .getElementById("container")
+ .setAttribute("aria-owns", "a c b");
+ });
+
+ testChildrenIds(containerAcc.firstChild, ["a", "b", "c"]);
+ }
+);
+
+// Don't crash if ID in aria-owns does not exist
+addAccessibleTask(
+ `
+ <select id="container" aria-owns="boom" multiple></select>`,
+ async function (browser, accDoc) {
+ ok(true, "Did not crash");
+ }
+);
+
+addAccessibleTask(
+ `
+ <ul id="one">
+ <li id="a">Test</li>
+ <li id="b">Test 2</li>
+ <li id="c">Test 3</li>
+ </ul>
+ <ul id="two"></ul>`,
+ async function (browser, accDoc) {
+ let one = findAccessibleChildByID(accDoc, "one");
+ let two = findAccessibleChildByID(accDoc, "two");
+
+ let waitfor = {
+ expected: [
+ [EVENT_REORDER, "one"],
+ [EVENT_REORDER, "two"],
+ ],
+ };
+
+ await contentSpawnMutation(browser, waitfor, function () {
+ // Put same id twice in aria-owns
+ content.document.getElementById("two").setAttribute("aria-owns", "a a");
+ });
+
+ testChildrenIds(one, ["b", "c"]);
+ testChildrenIds(two, ["a"]);
+
+ await contentSpawnMutation(browser, waitfor, function () {
+ // If the previous double-id aria-owns worked correctly, we should
+ // be in a good state and all is fine..
+ content.document.getElementById("two").setAttribute("aria-owns", "a b");
+ });
+
+ testChildrenIds(one, ["c"]);
+ testChildrenIds(two, ["a", "b"]);
+ }
+);
+
+addAccessibleTask(
+ `<div id="a"></div><div id="b"></div>`,
+ async function (browser, accDoc) {
+ testChildrenIds(accDoc, ["a", "b"]);
+
+ let waitFor = {
+ expected: [[EVENT_REORDER, e => e.accessible == accDoc]],
+ };
+
+ await contentSpawnMutation(browser, waitFor, function () {
+ content.document.documentElement.style.display = "none";
+ content.document.documentElement.getBoundingClientRect();
+ content.document.body.setAttribute("aria-owns", "b a");
+ content.document.documentElement.remove();
+ });
+
+ testChildrenIds(accDoc, []);
+ }
+);
+
+// Don't allow ordinal child to be placed after aria-owned child (bug 1405796)
+addAccessibleTask(
+ `<div id="container"><div id="a">Hello</div></div>
+ <div><div id="c">There</div><div id="d">There</div></div>`,
+ async function (browser, accDoc) {
+ let containerAcc = findAccessibleChildByID(accDoc, "container");
+
+ testChildrenIds(containerAcc, ["a"]);
+
+ await contentSpawnMutation(browser, MOVE, function () {
+ content.document
+ .getElementById("container")
+ .setAttribute("aria-owns", "c");
+ });
+
+ testChildrenIds(containerAcc, ["a", "c"]);
+
+ await contentSpawnMutation(browser, MOVE, function () {
+ let span = content.document.createElement("span");
+ content.document.getElementById("container").appendChild(span);
+
+ let b = content.document.createElement("div");
+ b.id = "b";
+ content.document.getElementById("container").appendChild(b);
+ });
+
+ testChildrenIds(containerAcc, ["a", "b", "c"]);
+
+ await contentSpawnMutation(browser, MOVE, function () {
+ content.document
+ .getElementById("container")
+ .setAttribute("aria-owns", "c d");
+ });
+
+ testChildrenIds(containerAcc, ["a", "b", "c", "d"]);
+ }
+);
diff --git a/accessible/tests/browser/tree/browser_browser_element.js b/accessible/tests/browser/tree/browser_browser_element.js
new file mode 100644
index 0000000000..82be24d93c
--- /dev/null
+++ b/accessible/tests/browser/tree/browser_browser_element.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+// Test that the tree is correct for browser elements containing remote
+// documents.
+addAccessibleTask(`test`, async function (browser, docAcc) {
+ // testAccessibleTree also verifies childCount, indexInParent and parent.
+ testAccessibleTree(browser, {
+ INTERNAL_FRAME: [{ DOCUMENT: [{ TEXT_LEAF: [] }] }],
+ });
+});
diff --git a/accessible/tests/browser/tree/browser_css_content_visibility.js b/accessible/tests/browser/tree/browser_css_content_visibility.js
new file mode 100644
index 0000000000..798e409d86
--- /dev/null
+++ b/accessible/tests/browser/tree/browser_css_content_visibility.js
@@ -0,0 +1,121 @@
+/* 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";
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+const snippet1 = `
+ <style>
+ #container {
+ width: 150px;
+ height: 150px;
+ background: lightblue;
+ }
+ .hidden {
+ content-visibility: hidden;
+ }
+ .auto {
+ content-visibility: auto;
+ }
+ </style>
+ <div id="container">
+ <div class="hidden" id="hidden-target">
+ hidden target
+ <div id="hidden-child">
+ hidden child
+ </div>
+ </div>
+ <div class="auto" id="auto-target">
+ auto target
+ <div id="auto-child">
+ auto child
+ </div>
+ </div>
+ </div>
+ `;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["layout.css.content-visibility.enabled", true]],
+ });
+});
+
+// Check if the element specified with `content-visibility` property is accessible
+addAccessibleTask(
+ snippet1,
+ async function (browser, accDoc) {
+ const container = findAccessibleChildByID(accDoc, "container");
+ ok(
+ findAccessibleChildByID(container, "hidden-target"),
+ "hidden-target is accessible"
+ );
+ ok(
+ findAccessibleChildByID(container, "auto-target"),
+ "auto-target is accessible"
+ );
+
+ // The test checks if the child element of the element specified with
+ // `content-visibility: hidden` is ignored from a11y tree
+ let target = findAccessibleChildByID(accDoc, "hidden-target");
+ ok(
+ !findAccessibleChildByID(target, "hidden-child"),
+ "Children of hidden-target is not accessible"
+ );
+
+ // The test checks if the child element of the element specified with
+ // `content-visibility: auto` is showen in a11y tree
+ target = findAccessibleChildByID(accDoc, "auto-target");
+ ok(
+ findAccessibleChildByID(target, "auto-child"),
+ "Children of auto-target is accessible"
+ );
+ },
+ { iframe: true, remoteIframe: true, chrome: true }
+);
+
+// Check if the element having `display: contents` and a child of `content-visibility: hidden` element isn't accessible
+const snippet2 = `
+ <style>
+ #target {
+ width: 150px;
+ height: 150px;
+ background-color: lightblue;
+ }
+ #child {
+ width: 100px;
+ height: 100px;
+ background-color: lightgreen;
+ }
+ #grandchild {
+ width: 50px;
+ height: 50px;
+ background-color: red;
+ }
+ .hidden {
+ content-visibility: hidden;
+ }
+ .display-contents {
+ display: contents;
+ }
+ </style>
+ <div id="target" class="hidden">
+ <div id="child" class="display-contents">
+ <div id="grandchild"></div>
+ </div>
+ </div>
+ `;
+
+addAccessibleTask(
+ snippet2,
+ async function (browser, accDoc) {
+ const target = findAccessibleChildByID(accDoc, "target");
+ ok(
+ !findAccessibleChildByID(target, "child"),
+ "Element having `display: contents` and a child of `content-visibility: hidden` element isn't accessible"
+ );
+ testAccessibleTree(target, { SECTION: [] });
+ },
+ { iframe: true, remoteIframe: true, chrome: true }
+);
diff --git a/accessible/tests/browser/tree/browser_general.js b/accessible/tests/browser/tree/browser_general.js
new file mode 100644
index 0000000000..0d16271a36
--- /dev/null
+++ b/accessible/tests/browser/tree/browser_general.js
@@ -0,0 +1,128 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Verify adding `overflow:hidden;` styling to a div causes it to
+ * get an accessible.
+ */
+addAccessibleTask(`<p>hello world</p>`, async function (browser, docAcc) {
+ const originalTree = { DOCUMENT: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }] };
+
+ testAccessibleTree(docAcc, originalTree);
+ info("Adding div element");
+ await contentSpawnMutation(
+ browser,
+ { unexpected: [[EVENT_REORDER, docAcc]] },
+ function () {
+ const d = content.document.createElement("div");
+ content.document.body.appendChild(d);
+ }
+ );
+
+ testAccessibleTree(docAcc, originalTree);
+ info("Adding overflow:hidden styling to div");
+ await contentSpawnMutation(
+ browser,
+ { expected: [[EVENT_REORDER, docAcc]] },
+ function () {
+ content.document.body.lastElementChild.setAttribute(
+ "style",
+ "overflow:hidden;"
+ );
+ }
+ );
+
+ testAccessibleTree(docAcc, {
+ DOCUMENT: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }, { TEXT_CONTAINER: [] }],
+ });
+});
+
+/**
+ * Verify adding `overflow:scroll;` styling to a div causes
+ * it to get an accessible.
+ */
+addAccessibleTask(`<p>hello world</p>`, async function (browser, docAcc) {
+ const originalTree = { DOCUMENT: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }] };
+
+ testAccessibleTree(docAcc, originalTree);
+ info("Adding div element");
+ await contentSpawnMutation(
+ browser,
+ { unexpected: [[EVENT_REORDER, docAcc]] },
+ function () {
+ const d = content.document.createElement("div");
+ content.document.body.appendChild(d);
+ }
+ );
+
+ testAccessibleTree(docAcc, originalTree);
+ info("Adding overflow:scroll styling to div");
+ await contentSpawnMutation(
+ browser,
+ { expected: [[EVENT_REORDER, docAcc]] },
+ function () {
+ content.document.body.lastElementChild.setAttribute(
+ "style",
+ "overflow:scroll;"
+ );
+ }
+ );
+
+ testAccessibleTree(docAcc, {
+ DOCUMENT: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }, { TEXT_CONTAINER: [] }],
+ });
+});
+
+/**
+ * Verify adding `overflow:auto;` styling to a div causes
+ * it to get an accessible, but `overflow:visible` does not.
+ */
+addAccessibleTask(`<p>hello world</p>`, async function (browser, docAcc) {
+ const originalTree = { DOCUMENT: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }] };
+
+ testAccessibleTree(docAcc, originalTree);
+ info("Adding div element");
+ await contentSpawnMutation(
+ browser,
+ { unexpected: [[EVENT_REORDER, docAcc]] },
+ function () {
+ const d = content.document.createElement("div");
+ content.document.body.appendChild(d);
+ }
+ );
+
+ testAccessibleTree(docAcc, originalTree);
+ info("Adding overflow:visible styling to div");
+ await contentSpawnMutation(
+ browser,
+ { unexpected: [[EVENT_REORDER, docAcc]] },
+ function () {
+ content.document.body.lastElementChild.setAttribute(
+ "style",
+ "overflow:visible;"
+ );
+ }
+ );
+
+ testAccessibleTree(docAcc, originalTree);
+ info("Adding overflow:auto styling to div");
+ await contentSpawnMutation(
+ browser,
+ { expected: [[EVENT_REORDER, docAcc]] },
+ function () {
+ content.document.body.lastElementChild.setAttribute(
+ "style",
+ "overflow:auto;"
+ );
+ }
+ );
+
+ testAccessibleTree(docAcc, {
+ DOCUMENT: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }, { TEXT_CONTAINER: [] }],
+ });
+});
diff --git a/accessible/tests/browser/tree/browser_lazy_tabs.js b/accessible/tests/browser/tree/browser_lazy_tabs.js
new file mode 100644
index 0000000000..f7aa9bdeb2
--- /dev/null
+++ b/accessible/tests/browser/tree/browser_lazy_tabs.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that lazy background tabs aren't unintentionally loaded when building
+// the a11y tree (bug 1700708).
+addAccessibleTask(``, async function (browser, accDoc) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.sessionstore.restore_on_demand", true]],
+ });
+
+ info("Opening a new window");
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ // Window is opened with a blank tab.
+ info("Loading second tab");
+ await BrowserTestUtils.openNewForegroundTab({
+ gBrowser: win.gBrowser,
+ url: "data:text/html,2",
+ });
+ info("Loading third tab");
+ await BrowserTestUtils.openNewForegroundTab({
+ gBrowser: win.gBrowser,
+ url: "data:text/html,3",
+ });
+ info("Closing the window");
+ await BrowserTestUtils.closeWindow(win);
+
+ is(SessionStore.getClosedWindowCount(), 1, "Should have a window to restore");
+ info("Restoring the window");
+ win = SessionStore.undoCloseWindow(0);
+ await BrowserTestUtils.waitForEvent(win, "SSWindowStateReady");
+ await BrowserTestUtils.waitForEvent(
+ win.gBrowser.tabContainer,
+ "SSTabRestored"
+ );
+ is(win.gBrowser.tabs.length, 3, "3 tabs restored");
+ ok(win.gBrowser.tabs[2].selected, "Third tab selected");
+ ok(getAccessible(win.gBrowser.tabs[1]), "Second tab has accessible");
+ ok(!win.gBrowser.browsers[1].isConnected, "Second tab is lazy");
+ info("Closing the restored window");
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/accessible/tests/browser/tree/browser_searchbar.js b/accessible/tests/browser/tree/browser_searchbar.js
new file mode 100644
index 0000000000..ef68307b91
--- /dev/null
+++ b/accessible/tests/browser/tree/browser_searchbar.js
@@ -0,0 +1,84 @@
+"use strict";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+// eslint-disable-next-line camelcase
+add_task(async function test_searchbar_a11y_tree() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.widget.inNavBar", true]],
+ });
+
+ // This used to rely on the implied 100ms initial timer of
+ // TestUtils.waitForCondition. See bug 1700735.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 100));
+ let searchbar = await TestUtils.waitForCondition(
+ () => document.getElementById("searchbar"),
+ "wait for search bar to appear"
+ );
+
+ // Make sure the popup has been rendered so it shows up in the a11y tree.
+ let popup = document.getElementById("PopupSearchAutoComplete");
+ let promise = Promise.all([
+ BrowserTestUtils.waitForEvent(popup, "popupshown", false),
+ waitForEvent(EVENT_SHOW, popup),
+ ]);
+ searchbar.textbox.openPopup();
+ await promise;
+
+ let TREE = {
+ role: ROLE_EDITCOMBOBOX,
+
+ children: [
+ // input element
+ {
+ role: ROLE_ENTRY,
+ children: [],
+ },
+
+ // context menu
+ {
+ role: ROLE_COMBOBOX_LIST,
+ children: [],
+ },
+
+ // result list
+ {
+ role: ROLE_GROUPING,
+ // not testing the structure inside the result list
+ },
+ ],
+ };
+
+ testAccessibleTree(searchbar, TREE);
+
+ promise = Promise.all([
+ BrowserTestUtils.waitForEvent(popup, "popuphidden", false),
+ waitForEvent(EVENT_HIDE, popup),
+ ]);
+ searchbar.textbox.closePopup();
+ await promise;
+
+ TREE = {
+ role: ROLE_EDITCOMBOBOX,
+
+ children: [
+ // input element
+ {
+ role: ROLE_ENTRY,
+ children: [],
+ },
+
+ // context menu
+ {
+ role: ROLE_COMBOBOX_LIST,
+ children: [],
+ },
+
+ // the result list should be removed from the tree on popuphidden
+ ],
+ };
+
+ testAccessibleTree(searchbar, TREE);
+});
diff --git a/accessible/tests/browser/tree/browser_shadowdom.js b/accessible/tests/browser/tree/browser_shadowdom.js
new file mode 100644
index 0000000000..6d9f06f9ff
--- /dev/null
+++ b/accessible/tests/browser/tree/browser_shadowdom.js
@@ -0,0 +1,98 @@
+/* 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 REORDER = { expected: [[EVENT_REORDER, "container"]] };
+
+// Dynamically inserted slotted accessible elements should be in
+// the accessible tree.
+const snippet = `
+<script>
+customElements.define("x-el", class extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({ mode: "open" });
+ this.shadowRoot.innerHTML =
+ "<div role='presentation'><slot></slot></div>";
+ }
+});
+</script>
+<x-el id="container" role="group"><label id="l1">label1</label></x-el>
+`;
+
+addAccessibleTask(snippet, async function (browser, accDoc) {
+ let container = findAccessibleChildByID(accDoc, "container");
+
+ testChildrenIds(container, ["l1"]);
+
+ await contentSpawnMutation(browser, REORDER, function () {
+ let labelEl = content.document.createElement("label");
+ labelEl.id = "l2";
+
+ let containerEl = content.document.getElementById("container");
+ containerEl.appendChild(labelEl);
+ });
+
+ testChildrenIds(container, ["l1", "l2"]);
+});
+
+// Dynamically inserted not accessible custom element containing an accessible
+// in its shadow DOM.
+const snippet2 = `
+<script>
+customElements.define("x-el2", class extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({ mode: "open" });
+ this.shadowRoot.innerHTML = "<input id='input'>";
+ }
+});
+</script>
+<div role="group" id="container"></div>
+`;
+
+addAccessibleTask(snippet2, async function (browser, accDoc) {
+ let container = findAccessibleChildByID(accDoc, "container");
+
+ await contentSpawnMutation(browser, REORDER, function () {
+ content.document.getElementById("container").innerHTML = "<x-el2></x-el2>";
+ });
+
+ testChildrenIds(container, ["input"]);
+});
+
+/**
+ * Ensure that changing the slot on the body while moving the body doesn't
+ * try to remove the DocAccessible. We test this here instead of in
+ * accessible/tests/mochitest/treeupdate/test_shadow_slots.html because this
+ * messes with the body element and we don't want that to impact other tests.
+ */
+addAccessibleTask(
+ `
+<div id="host"></div>
+<script>
+ const host = document.getElementById("host");
+ host.attachShadow({ mode: "open" });
+ const emptyScript = document.createElement("script");
+ emptyScript.id = "emptyScript";
+ document.head.append(emptyScript);
+</script>
+ `,
+ async function (browser, docAcc) {
+ info("Moving body and setting slot on body");
+ let reordered = waitForEvent(EVENT_REORDER, docAcc);
+ await invokeContentTask(browser, [], () => {
+ const host = content.document.getElementById("host");
+ const emptyScript = content.document.getElementById("emptyScript");
+ const body = content.document.body;
+ emptyScript.append(host);
+ host.append(body);
+ body.slot = "";
+ });
+ await reordered;
+ is(docAcc.childCount, 0, "document has no children after body move");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/tree/browser_test_nsIAccessibleDocument_URL.js b/accessible/tests/browser/tree/browser_test_nsIAccessibleDocument_URL.js
new file mode 100644
index 0000000000..623f2640f0
--- /dev/null
+++ b/accessible/tests/browser/tree/browser_test_nsIAccessibleDocument_URL.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function promiseEventDocumentLoadComplete(expectedURL) {
+ return new Promise(resolve => {
+ waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, event => {
+ try {
+ if (
+ event.accessible.QueryInterface(nsIAccessibleDocument).URL ==
+ expectedURL
+ ) {
+ resolve(event.accessible.QueryInterface(nsIAccessibleDocument));
+ return true;
+ }
+ return false;
+ } catch (e) {
+ return false;
+ }
+ });
+ });
+}
+
+add_task(async function testInDataURI() {
+ const kURL = "data:text/html,Some text";
+ const waitForDocumentLoadComplete = promiseEventDocumentLoadComplete("");
+ await BrowserTestUtils.withNewTab(kURL, async browser => {
+ is(
+ (await waitForDocumentLoadComplete).URL,
+ "",
+ "nsIAccessibleDocument.URL shouldn't return data URI"
+ );
+ });
+});
+
+add_task(async function testInHTTPSURIContainingPrivateThings() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.auth.confirmAuth.enabled", false]],
+ });
+ const kURL =
+ "https://username:password@example.com/browser/toolkit/content/tests/browser/file_empty.html?query=some#ref";
+ const kURLWithoutUserPass =
+ "https://example.com/browser/toolkit/content/tests/browser/file_empty.html?query=some#ref";
+ const waitForDocumentLoadComplete =
+ promiseEventDocumentLoadComplete(kURLWithoutUserPass);
+ await BrowserTestUtils.withNewTab(kURL, async browser => {
+ is(
+ (await waitForDocumentLoadComplete).URL,
+ kURLWithoutUserPass,
+ "nsIAccessibleDocument.URL shouldn't contain user/pass section"
+ );
+ });
+});
diff --git a/accessible/tests/browser/tree/head.js b/accessible/tests/browser/tree/head.js
new file mode 100644
index 0000000000..b9c787e9e2
--- /dev/null
+++ b/accessible/tests/browser/tree/head.js
@@ -0,0 +1,33 @@
+/* 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";
+
+/* exported testChildrenIds */
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js.
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR }
+);
+
+/*
+ * A test function for comparing the IDs of an accessible's children
+ * with an expected array of IDs.
+ */
+function testChildrenIds(acc, expectedIds) {
+ let ids = arrayFromChildren(acc).map(child => getAccessibleDOMNodeID(child));
+ Assert.deepEqual(
+ ids,
+ expectedIds,
+ `Children for ${getAccessibleDOMNodeID(acc)} are wrong.`
+ );
+}
diff --git a/accessible/tests/crashtests/1072792.xhtml b/accessible/tests/crashtests/1072792.xhtml
new file mode 100644
index 0000000000..f99c64c5d3
--- /dev/null
+++ b/accessible/tests/crashtests/1072792.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body onload="document.body.appendChild(x);">
+<table><tbody><div id="x"></div></tbody></table>
+</body>
+</html>
diff --git a/accessible/tests/crashtests/1380199.html b/accessible/tests/crashtests/1380199.html
new file mode 100644
index 0000000000..c690597173
--- /dev/null
+++ b/accessible/tests/crashtests/1380199.html
@@ -0,0 +1,15 @@
+<script>
+window.onload=function(){
+ let o=document.getElementById('a');
+ let n=document.createElement('plaintext');
+ o.parentNode.replaceChild(n,o);
+ n.setAttribute('id','a');
+ document.getElementById('a').setAttribute('aria-owns','c b');
+ document.getElementById('d').setAttribute('width',1);
+}
+</script>
+>
+<script id='a'></script>
+<hgroup id='b'>
+<h6 id='c'>
+<video id='d' controls width='169' height='187'>
diff --git a/accessible/tests/crashtests/1402999.html b/accessible/tests/crashtests/1402999.html
new file mode 100644
index 0000000000..567f20b2df
--- /dev/null
+++ b/accessible/tests/crashtests/1402999.html
@@ -0,0 +1,11 @@
+<script>
+document.addEventListener("DOMContentLoaded", function(){
+ document.getElementById('a').click();
+ window.frames[0].document.body.appendChild(document.getElementById('b'));
+});
+</script>
+<cite id='b'>
+<label>
+<input/>
+<strong id='a'></strong>
+<iframe>
diff --git a/accessible/tests/crashtests/1415667.html b/accessible/tests/crashtests/1415667.html
new file mode 100644
index 0000000000..174099837d
--- /dev/null
+++ b/accessible/tests/crashtests/1415667.html
@@ -0,0 +1 @@
+<iframe role="row">
diff --git a/accessible/tests/crashtests/1463962.html b/accessible/tests/crashtests/1463962.html
new file mode 100644
index 0000000000..c3d233af5c
--- /dev/null
+++ b/accessible/tests/crashtests/1463962.html
@@ -0,0 +1,4 @@
+<style>
+#a { display: contents }
+</style>
+<li id="a">
diff --git a/accessible/tests/crashtests/1472024-1.html b/accessible/tests/crashtests/1472024-1.html
new file mode 100644
index 0000000000..2b745a635e
--- /dev/null
+++ b/accessible/tests/crashtests/1472024-1.html
@@ -0,0 +1,7 @@
+<style>
+.a { position: absolute }
+</style>
+<table>
+<tbody class="a">
+<tr>b</tr>
+<th>
diff --git a/accessible/tests/crashtests/1472024-2.html b/accessible/tests/crashtests/1472024-2.html
new file mode 100644
index 0000000000..74719cb91a
--- /dev/null
+++ b/accessible/tests/crashtests/1472024-2.html
@@ -0,0 +1,20 @@
+<html class="reftest-wait">
+<script>
+function tweak() {
+ document.getElementById('a').appendChild(document.createTextNode('hZ'));
+ document.documentElement.removeAttribute("class");
+}
+
+window.onload = () => {
+ // double-rAF (to ensure layout/paints have been flushed) before we make
+ // the dynamic modification. This seems to make the bug more likely to
+ // reproduce, in unpatched builds.
+ requestAnimationFrame(() => { requestAnimationFrame(() => {
+ tweak();
+ }); });
+};
+</script>
+<table>
+<tfoot id='a'>
+<th>
+<textarea>Text
diff --git a/accessible/tests/crashtests/1484778.html b/accessible/tests/crashtests/1484778.html
new file mode 100644
index 0000000000..9d3fc33f59
--- /dev/null
+++ b/accessible/tests/crashtests/1484778.html
@@ -0,0 +1,26 @@
+<style>
+#a { border-left: solid -moz-hyperlinktext 93em }
+</style>
+<script>
+/*
+ I dont't know why but this seems to be required to trigger the crash...
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+*/
+function go() {
+ var b = document.elementFromPoint(0,0);
+ window.scroll({left: 97, top: -1});
+ document.adoptNode(b);
+}
+</script>
+<body onload=go()>
+<ins id="a">
diff --git a/accessible/tests/crashtests/1494707.html b/accessible/tests/crashtests/1494707.html
new file mode 100644
index 0000000000..f2feb1572d
--- /dev/null
+++ b/accessible/tests/crashtests/1494707.html
@@ -0,0 +1,15 @@
+<html>
+ <head>
+ <style>
+ *::after {
+ content: counters(bar, '', none);
+ }
+ </style>
+ </head>
+ <body>
+ <table>
+ <thead></thead>
+ <th></th>
+ </table>
+ </body>
+</html>
diff --git a/accessible/tests/crashtests/1503964.html b/accessible/tests/crashtests/1503964.html
new file mode 100644
index 0000000000..bc4c359b52
--- /dev/null
+++ b/accessible/tests/crashtests/1503964.html
@@ -0,0 +1,4 @@
+<table>
+<th>X</th>
+<tfoot style="display: table-row">
+<tr role="columnheader">X</tr>
diff --git a/accessible/tests/crashtests/1572811.html b/accessible/tests/crashtests/1572811.html
new file mode 100644
index 0000000000..61dc78ca7b
--- /dev/null
+++ b/accessible/tests/crashtests/1572811.html
@@ -0,0 +1,9 @@
+<script>
+function go() {
+ a.style.overflow = "auto";
+}
+</script>
+<body onload=go()>
+<table id="a" background="3">
+<th>
+<textarea style="position:absolute">A</textarea>
diff --git a/accessible/tests/crashtests/1578282.html b/accessible/tests/crashtests/1578282.html
new file mode 100644
index 0000000000..31cc1ce02b
--- /dev/null
+++ b/accessible/tests/crashtests/1578282.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+<head>
+<script>
+function onload() {
+ document.documentElement.style.display = "none"
+ document.documentElement.getBoundingClientRect()
+ document.documentElement.style.display = ""
+ window.setTimeout(() => {
+ document.documentElement.removeAttribute("class");
+ }, 1000);
+}
+</script>
+</head>
+<body onload="onload()">
+<marquee style="display: inline-grid">
+<video></video>
+<bgsound></bgsound>
+&lt;
+</marquee>
+</body>
+</html> \ No newline at end of file
diff --git a/accessible/tests/crashtests/1585851.html b/accessible/tests/crashtests/1585851.html
new file mode 100644
index 0000000000..3acac2de7d
--- /dev/null
+++ b/accessible/tests/crashtests/1585851.html
@@ -0,0 +1,21 @@
+<style>
+* {
+ border-collapse: collapse;
+}
+</style>
+<script>
+window.onload = () => {
+ c.style.setProperty("border-collapse", "separate")
+}
+function eh() {
+ a.options.add(f)
+ a.add(b, 9)
+ a.add(f, d)
+}
+</script>
+<select id="a"></select>
+<option id="b">
+<dd id="c">
+<audio id="d" controls>
+<details open="true" ontoggle="eh()">
+<optgroup id="f">
diff --git a/accessible/tests/crashtests/1655983.html b/accessible/tests/crashtests/1655983.html
new file mode 100644
index 0000000000..88fee3ec5d
--- /dev/null
+++ b/accessible/tests/crashtests/1655983.html
@@ -0,0 +1,6 @@
+<script>
+ document.addEventListener('DOMContentLoaded', () => {
+ document.getElementById('id_0').type = ''
+ })
+</script>
+<input id="id_0" type="time">
diff --git a/accessible/tests/crashtests/448064.xhtml b/accessible/tests/crashtests/448064.xhtml
new file mode 100644
index 0000000000..c1a25ca550
--- /dev/null
+++ b/accessible/tests/crashtests/448064.xhtml
@@ -0,0 +1,70 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+<div id="mw_b">
+<div id="mw_f">
+<div id="mw_g" style="display: none;"/>
+</div>
+</div>
+
+<div id="mw_c" style="display: none;">
+<div id="mw_d">
+<div id="mw_e"></div>
+</div>
+</div>
+
+<input id="mw_a"/>
+
+
+<script>
+function dumpAccessibleNode(aNode, level) {
+ var msg = "";
+
+ try {
+ msg += "name=\"" + aNode.name + "\" ";
+ } catch (e) {
+ msg += " noName ";
+ }
+
+ dump(msg + "\n");
+}
+
+
+function dumpAccessibleTree(aNode, level) {
+ level = level || 0;
+
+ dumpAccessibleNode(aNode, level);
+ try {
+ var child = aNode.firstChild;
+ while (child) {
+ dumpAccessibleTree(child, level + 1);
+ child = child.nextSibling;
+ }
+ } catch (e) {
+ dump("Error visiting child nodes: " + e + "\n");
+ }
+}
+
+function A(o) {
+ var acc = SpecialPowers.Cc["@mozilla.org/accessibilityService;1"]
+ .getService(SpecialPowers.Ci.nsIAccessibilityService);
+ return acc.getAccessibleFor(o);
+}
+
+function beginAccessible() {
+ dumpAccessibleTree(A(document), 0);
+}
+setTimeout(beginAccessible, 100);
+
+
+setTimeout(doe, 200);
+function doe() {
+ document.getElementById("mw_a").appendChild(document.getElementById("mw_b"));
+ document.getElementById("mw_c").appendChild(document.getElementById("mw_d"));
+ document.getElementById("mw_e").appendChild(document.getElementById("mw_f"));
+ document.getElementById("mw_g").appendChild(document.getElementById("mw_b"));
+}
+</script>
+</body>
+</html>
diff --git a/accessible/tests/crashtests/884202.html b/accessible/tests/crashtests/884202.html
new file mode 100644
index 0000000000..f29ef55f4a
--- /dev/null
+++ b/accessible/tests/crashtests/884202.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function f()
+{
+ window.removeEventListener("pagehide", f, false);
+ var root = document.documentElement;
+ document.removeChild(root);
+ document.appendChild(root);
+}
+
+function boom()
+{
+ window.addEventListener("pagehide", f, false);
+ window.location = "data:text/html,4";
+}
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/accessible/tests/crashtests/890760.html b/accessible/tests/crashtests/890760.html
new file mode 100644
index 0000000000..ecc76160b9
--- /dev/null
+++ b/accessible/tests/crashtests/890760.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ window.getSelection().collapse(document.createTextNode("."), 0);
+}
+
+</script>
+</head>
+<body onload="setTimeout(boom, 0);"></body>
+</html>
diff --git a/accessible/tests/crashtests/893515.html b/accessible/tests/crashtests/893515.html
new file mode 100644
index 0000000000..a9cc12c928
--- /dev/null
+++ b/accessible/tests/crashtests/893515.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+function boom()
+{
+ document.designMode = 'on';
+ var otherDoc = document.implementation.createDocument("", "", null);
+ otherDoc.adoptNode(document.getElementById("t"));
+}
+</script>
+</head>
+<body onload="setTimeout(boom, 0);"><textarea id="t"></textarea></body>
+</html>
diff --git a/accessible/tests/crashtests/crashtests.list b/accessible/tests/crashtests/crashtests.list
new file mode 100644
index 0000000000..8c80c1e29a
--- /dev/null
+++ b/accessible/tests/crashtests/crashtests.list
@@ -0,0 +1,22 @@
+load 448064.xhtml # This test instantiates a11y, so be careful about adding tests before it
+asserts-if(!browserIsRemote,2) load 884202.html
+load 1572811.html
+load 1578282.html
+load 890760.html
+load 893515.html
+load 1072792.xhtml
+load 1380199.html
+load 1402999.html
+load 1463962.html
+load 1472024-1.html
+load 1472024-2.html
+load 1484778.html
+load 1494707.html
+load 1503964.html
+load 1415667.html
+load 1585851.html
+load 1655983.html
+
+# last_test_to_unload_testsuite.xhtml MUST be the last test in the list because it
+# is responsible for shutting down accessibility service affecting later tests.
+skip-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)&&/^aarch64-msvc/.test(xulRuntime.XPCOMABI)) load chrome://reftest/content/crashtests/accessible/tests/crashtests/last_test_to_unload_testsuite.xhtml
diff --git a/accessible/tests/crashtests/last_test_to_unload_testsuite.xhtml b/accessible/tests/crashtests/last_test_to_unload_testsuite.xhtml
new file mode 100644
index 0000000000..9a70c41dcf
--- /dev/null
+++ b/accessible/tests/crashtests/last_test_to_unload_testsuite.xhtml
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="bug 471493 'crash [@ nsPropertyTable::GetPropertyInternal]'"
+ onload="shutdown();" class="reftest-wait">
+
+ <script type="application/javascript">
+ <![CDATA[
+ function shutdown() {
+ !SpecialPowers.Services.appinfo.accessibilityEnabled &&
+ dump("### Error : Accessibility is expected to be enabled.\n");
+
+ // Force garbage collection. We try really hard to garbage collect
+ // everythin here to ensure that all a11y xpcom bits are shut down and
+ // avoid intermittent timeouts.
+ SpecialPowers.gc();
+ SpecialPowers.forceShrinkingGC();
+ SpecialPowers.forceCC();
+ SpecialPowers.gc();
+ SpecialPowers.forceShrinkingGC();
+ SpecialPowers.forceCC();
+
+ if (SpecialPowers.Services.appinfo.accessibilityEnabled) {
+ let observe = (subject, topic, data) => {
+ SpecialPowers.Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+ data === "0" && document.documentElement.removeAttribute("class");
+ };
+ SpecialPowers.Services.obs.addObserver(observe, "a11y-init-or-shutdown");
+ } else {
+ document.documentElement.removeAttribute("class");
+ }
+ }
+ ]]>
+ </script>
+</window>
diff --git a/accessible/tests/mochitest/.eslintrc.js b/accessible/tests/mochitest/.eslintrc.js
new file mode 100644
index 0000000000..2ce1c5017a
--- /dev/null
+++ b/accessible/tests/mochitest/.eslintrc.js
@@ -0,0 +1,24 @@
+"use strict";
+
+module.exports = {
+ rules: {
+ // XXX These are rules that are enabled in the recommended configuration, but
+ // disabled here due to failures when initially implemented. They should be
+ // removed (and hence enabled) at some stage.
+ "no-nested-ternary": "off",
+ },
+
+ overrides: [
+ {
+ files: [
+ // Bug 1602061 TODO: These tests access DOM elements via
+ // id-as-variable-name, which eslint doesn't have support for yet.
+ "attributes/test_listbox.html",
+ "treeupdate/test_ariaowns.html",
+ ],
+ rules: {
+ "no-undef": "off",
+ },
+ },
+ ],
+};
diff --git a/accessible/tests/mochitest/a11y.ini b/accessible/tests/mochitest/a11y.ini
new file mode 100644
index 0000000000..885cb56e4a
--- /dev/null
+++ b/accessible/tests/mochitest/a11y.ini
@@ -0,0 +1,21 @@
+[DEFAULT]
+support-files =
+ ../../../dom/media/test/bug461281.ogg
+ ../../../dom/security/test/csp/dummy.pdf
+ ../../../image/test/mochitest/animated-gif-finalframe.gif
+ ../../../image/test/mochitest/animated-gif.gif
+ dumbfile.zip
+ formimage.png
+ letters.gif
+ moz.png
+ longdesc_src.html
+ *.js
+ treeview.css
+
+[test_aria_token_attrs.html]
+[test_bug420863.html]
+[test_custom_element_accessibility_defaults.html]
+[test_descr.html]
+[test_nsIAccessibleDocument.html]
+[test_nsIAccessibleImage.html]
+[test_OuterDocAccessible.html]
diff --git a/accessible/tests/mochitest/actions.js b/accessible/tests/mochitest/actions.js
new file mode 100644
index 0000000000..dc2f7d929d
--- /dev/null
+++ b/accessible/tests/mochitest/actions.js
@@ -0,0 +1,231 @@
+/* import-globals-from common.js */
+/* import-globals-from events.js */
+
+// //////////////////////////////////////////////////////////////////////////////
+// Event constants
+
+const MOUSEDOWN_EVENT = 1;
+const MOUSEUP_EVENT = 2;
+const CLICK_EVENT = 4;
+const COMMAND_EVENT = 8;
+const FOCUS_EVENT = 16;
+
+const CLICK_EVENTS = MOUSEDOWN_EVENT | MOUSEUP_EVENT | CLICK_EVENT;
+const XUL_EVENTS = CLICK_EVENTS | COMMAND_EVENT;
+
+// //////////////////////////////////////////////////////////////////////////////
+// Public functions
+
+/**
+ * Test default accessible actions.
+ *
+ * Action tester interface is:
+ *
+ * var actionObj = {
+ * // identifier of accessible to perform an action on
+ * get ID() {},
+ *
+ * // index of the action
+ * get actionIndex() {},
+ *
+ * // name of the action
+ * get actionName() {},
+ *
+ * // DOM events (see constants defined above)
+ * get events() {},
+ *
+ * // [optional] identifier of target DOM events listeners are registered on,
+ * // used with 'events', if missing then 'ID' is used instead.
+ * get targetID() {},
+ *
+ * // [optional] true to match DOM events bubbled up to the target,
+ * // false (default) to only match events fired directly on the target.
+ * get allowBubbling() {},
+ *
+ * // [optional] perform checks when 'click' event is handled if 'events'
+ * // is used.
+ * checkOnClickEvent: function() {},
+ *
+ * // [optional] an array of invoker's checker objects (see eventQueue
+ * // constructor events.js)
+ * get eventSeq() {}
+ * };
+ *
+ *
+ * @param aArray [in] an array of action cheker objects
+ */
+function testActions(aArray) {
+ gActionsQueue = new eventQueue();
+
+ for (var idx = 0; idx < aArray.length; idx++) {
+ var actionObj = aArray[idx];
+ var accOrElmOrID = actionObj.ID;
+ var actionIndex = actionObj.actionIndex;
+ var actionName = actionObj.actionName;
+ var events = actionObj.events;
+ var accOrElmOrIDOfTarget = actionObj.targetID
+ ? actionObj.targetID
+ : accOrElmOrID;
+
+ var eventSeq = [];
+ if (events) {
+ var elm = getNode(accOrElmOrIDOfTarget);
+ if (events & MOUSEDOWN_EVENT) {
+ eventSeq.push(new checkerOfActionInvoker("mousedown", elm, actionObj));
+ }
+
+ if (events & MOUSEUP_EVENT) {
+ eventSeq.push(new checkerOfActionInvoker("mouseup", elm, actionObj));
+ }
+
+ if (events & CLICK_EVENT) {
+ eventSeq.push(new checkerOfActionInvoker("click", elm, actionObj));
+ }
+
+ if (events & COMMAND_EVENT) {
+ eventSeq.push(new checkerOfActionInvoker("command", elm, actionObj));
+ }
+
+ if (events & FOCUS_EVENT) {
+ eventSeq.push(new focusChecker(elm));
+ }
+ }
+
+ if (actionObj.eventSeq) {
+ eventSeq = eventSeq.concat(actionObj.eventSeq);
+ }
+
+ var invoker = new actionInvoker(
+ accOrElmOrID,
+ actionIndex,
+ actionName,
+ eventSeq
+ );
+ gActionsQueue.push(invoker);
+ }
+
+ gActionsQueue.invoke();
+}
+
+/**
+ * Test action names and descriptions.
+ */
+function testActionNames(aID, aActions) {
+ var actions = typeof aActions == "string" ? [aActions] : aActions || [];
+
+ var acc = getAccessible(aID);
+ is(acc.actionCount, actions.length, "Wong number of actions.");
+ for (var i = 0; i < actions.length; i++) {
+ is(
+ acc.getActionName(i),
+ actions[i],
+ "Wrong action name at " + i + " index."
+ );
+ is(
+ acc.getActionDescription(0),
+ gActionDescrMap[actions[i]],
+ "Wrong action description at " + i + "index."
+ );
+ }
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Private
+
+var gActionsQueue = null;
+
+function actionInvoker(aAccOrElmOrId, aActionIndex, aActionName, aEventSeq) {
+ this.invoke = function actionInvoker_invoke() {
+ var acc = getAccessible(aAccOrElmOrId);
+ if (!acc) {
+ return INVOKER_ACTION_FAILED;
+ }
+
+ var isThereActions = acc.actionCount > 0;
+ ok(
+ isThereActions,
+ "No actions on the accessible for " + prettyName(aAccOrElmOrId)
+ );
+
+ if (!isThereActions) {
+ return INVOKER_ACTION_FAILED;
+ }
+
+ is(
+ acc.getActionName(aActionIndex),
+ aActionName,
+ "Wrong action name of the accessible for " + prettyName(aAccOrElmOrId)
+ );
+
+ try {
+ acc.doAction(aActionIndex);
+ } catch (e) {
+ ok(false, "doAction(" + aActionIndex + ") failed with: " + e.name);
+ return INVOKER_ACTION_FAILED;
+ }
+ return null;
+ };
+
+ this.eventSeq = aEventSeq;
+
+ this.getID = function actionInvoker_getID() {
+ return (
+ "invoke an action " +
+ aActionName +
+ " at index " +
+ aActionIndex +
+ " on " +
+ prettyName(aAccOrElmOrId)
+ );
+ };
+}
+
+function checkerOfActionInvoker(aType, aTarget, aActionObj) {
+ this.type = aType;
+
+ this.target = aTarget;
+
+ if (aActionObj && "eventTarget" in aActionObj) {
+ this.eventTarget = aActionObj.eventTarget;
+ }
+
+ if (aActionObj && aActionObj.allowBubbling) {
+ // Normally, we add event listeners on the document. To catch bubbled
+ // events, we need to add the listener on the target itself.
+ this.eventTarget = "element";
+ // Normally, we only match an event fired directly on the target. Override
+ // this to match a bubbled event.
+ this.match = function (aEvent) {
+ return aEvent.currentTarget == aTarget;
+ };
+ }
+
+ this.phase = false;
+
+ this.getID = function getID() {
+ return aType + " event handling";
+ };
+
+ this.check = function check(aEvent) {
+ if (aType == "click" && aActionObj && "checkOnClickEvent" in aActionObj) {
+ aActionObj.checkOnClickEvent(aEvent);
+ }
+ };
+}
+
+var gActionDescrMap = {
+ jump: "Jump",
+ press: "Press",
+ check: "Check",
+ uncheck: "Uncheck",
+ select: "Select",
+ open: "Open",
+ close: "Close",
+ switch: "Switch",
+ click: "Click",
+ collapse: "Collapse",
+ expand: "Expand",
+ activate: "Activate",
+ cycle: "Cycle",
+ "click ancestor": "Click ancestor",
+};
diff --git a/accessible/tests/mochitest/actions/a11y.ini b/accessible/tests/mochitest/actions/a11y.ini
new file mode 100644
index 0000000000..5669e8d963
--- /dev/null
+++ b/accessible/tests/mochitest/actions/a11y.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/dom/media/test/bug461281.ogg
+
+[test_anchors.html]
+[test_aria.html]
+[test_controls.html]
+[test_general.html]
+[test_general.xhtml]
+[test_keys.html]
+[test_keys.xhtml]
+[test_link.html]
+[test_media.html]
+[test_select.html]
+[test_tree.xhtml]
+[test_treegrid.xhtml]
diff --git a/accessible/tests/mochitest/actions/test_anchors.html b/accessible/tests/mochitest/actions/test_anchors.html
new file mode 100644
index 0000000000..6ee1e0c450
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_anchors.html
@@ -0,0 +1,146 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing for HTML links that
+ scroll the page to named anchors</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Event checkers
+
+ function scrollingChecker(aAcc) {
+ this.type = EVENT_SCROLLING_START;
+ this.target = aAcc;
+ this.getID = function scrollingChecker_getID() {
+ return "scrolling start handling for " + prettyName(aAcc);
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug"; // debug stuff
+ // gA11yEventDumpToConsole = true; // debug stuff
+
+ function doTest() {
+ var actionsArray = [
+ {
+ ID: "anchor1",
+ actionName: "jump",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new scrollingChecker(getAccessible("bottom1")),
+ ],
+ },
+ { // jump again (test for bug 437607)
+ ID: "anchor1",
+ actionName: "jump",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new scrollingChecker(getAccessible("bottom1")),
+ ],
+ },
+ {
+ ID: "anchor2",
+ actionName: "jump",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new scrollingChecker(getAccessible("bottom2")),
+ ],
+ },
+ ];
+
+ testActions(actionsArray);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=506389"
+ title="Some same page links do not fire EVENT_SYSTEM_SCROLLINGSTART">
+ Mozilla Bug 506389
+ </a><br>
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=437607"
+ title="Clicking the 'Skip to main content' link once works, second time fails to initiate a V cursor jump">
+ Mozilla Bug 437607
+ </a><br>
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=519303"
+ title="Same page links to targets with content fires scrolling start accessible event on leaf text node">
+ Mozilla Bug 519303
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="debug"></div>
+
+ <h1>This is a test page for anchors</h1>
+ This is a top anchor<a name="Top">
+ </a><a id="anchor1" href="#bottom1">Link to anchor</a>
+ <a id="anchor2" href="#bottom2">Link to div</a>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br>This is some text in the middle<br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ This is some text.
+ This is a bottom anchor<a id="bottom1"></a>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <div id="bottom2">This is a div</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_aria.html b/accessible/tests/mochitest/actions/test_aria.html
new file mode 100644
index 0000000000..7ec0f8ed35
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_aria.html
@@ -0,0 +1,200 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var actionsArray = [
+ {
+ ID: "clickable",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "button",
+ actionName: "press",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "checkbox_unchecked",
+ actionName: "check",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "checkbox_checked",
+ actionName: "uncheck",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "checkbox_mixed",
+ actionName: "cycle",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "combobox_collapsed",
+ actionName: "open",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "combobox_expanded",
+ actionName: "close",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "link",
+ actionName: "jump",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "menuitem",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "menuitemcheckbox",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "menuitemradio",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "option",
+ actionName: "select",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "radio",
+ actionName: "select",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "switch_unchecked",
+ actionName: "check",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "switch_checked",
+ actionName: "uncheck",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "tab",
+ actionName: "switch",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "textbox",
+ actionName: "activate",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "treeitem",
+ actionName: "activate",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "sortable",
+ actionName: "sort",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "expandable",
+ actionName: "expand",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "collapseable",
+ actionName: "collapse",
+ events: CLICK_EVENTS,
+ },
+ ];
+ testActions(actionsArray);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=410765"
+ title="nsIAccessible actions testing">
+ Mozilla Bug 410765
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="clickable" onclick="">Clickable text</div>
+
+ <div id="button" role="button">Button</div>
+
+ <div id="checkbox_unchecked" role="checkbox">Checkbox</div>
+
+ <div id="checkbox_checked" role="checkbox" aria-checked="true">Checkbox</div>
+
+ <div id="checkbox_mixed" role="checkbox" aria-checked="mixed">Checkbox</div>
+
+ <div id="combobox_collapsed" role="combobox">
+ <div id="option" role="option">Option of collapsed combobox</div>
+ </div>
+
+ <div id="combobox_expanded" role="combobox" aria-expanded="true">
+ <div role="option">Option of expanded combobox</div>
+ </div>
+
+ <div id="link" role="link">Link</div>
+
+ <div role="menu">
+ <div id="menuitem" role="menuitem">Menuitem</div>
+ <div id="menuitemcheckbox" role="menuitemcheckbox">Menuitem checkbox</div>
+ <div id="menuitemradio" role="menuitemradio">Menuitem radio</div>
+ </div>
+
+ <div role="radiogroup">
+ <div id="radio" role="radio">Radio</div>
+ </div>
+
+ <div id="switch_unchecked" role="switch">Switch</div>
+
+ <div id="switch_checked" role="switch" aria-checked="true">Switch</div>
+
+ <div role="tablist">
+ <div id="tab" role="tab">Tab</div>
+ </div>
+
+ <div id="textbox" role="textbox">Textbox</div>
+
+ <div role="tree">
+ <div id="treeitem" role="treeitem">Treeitem</div>
+ </div>
+
+ <div role="grid">
+ <div id="sortable" role="columnheader" aria-sort="ascending">
+ Columnheader
+ </div>
+ </div>
+
+ <div id="expandable" aria-expanded="false">collapsed</div>
+ <div id="collapseable" aria-expanded="true">expanded</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_controls.html b/accessible/tests/mochitest/actions/test_controls.html
new file mode 100644
index 0000000000..8b6f413619
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_controls.html
@@ -0,0 +1,107 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing for inputs</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var actionsArray = [
+ {
+ ID: "button",
+ actionName: "press",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "input_button",
+ actionName: "press",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "checkbox_unchecked",
+ actionName: "check",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "checkbox_checked",
+ actionName: "uncheck",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "checkbox_mixed",
+ actionName: "cycle",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "radio",
+ actionName: "select",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "textarea",
+ actionName: "activate",
+ events: FOCUS_EVENT,
+ },
+ {
+ ID: "textinput",
+ actionName: "activate",
+ events: FOCUS_EVENT,
+ },
+
+ ];
+ document.getElementById("checkbox_mixed").indeterminate = true;
+
+ testActions(actionsArray);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=477975"
+ title="nsIAccessible actions testing">
+ Mozilla Bug 477975
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <button id="button">Button</button>
+
+ <input id="input_button" type="button" value="normal">
+
+ <input id="checkbox_unchecked" type="checkbox">Checkbox</input>
+
+ <input id="checkbox_checked" type="checkbox" checked="true">Checkbox</input>
+
+ <input id="checkbox_mixed" type="checkbox">Checkbox</input>
+
+ <fieldset>
+ <input id="radio" type="radio">Radio</input>
+ </fieldset>
+
+ <textarea id="textarea" placeholder="What's happening?"></textarea>
+
+ <input id="textinput" type="text">
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_general.html b/accessible/tests/mochitest/actions/test_general.html
new file mode 100644
index 0000000000..025b18f175
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_general.html
@@ -0,0 +1,105 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing on HTML elements</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var actionsArray = [
+ {
+ ID: "li_clickable1",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "li_clickable2",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "li_clickable3",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "onclick_img",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "label1",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+
+ ];
+
+ testActions(actionsArray);
+
+ is(getAccessible("label1").firstChild.actionCount, 1, "label text should have 1 action");
+
+ getAccessible("onclick_img").takeFocus();
+ is(getAccessible("link1").actionCount, 1, "links should have one action");
+ is(getAccessible("link2").actionCount, 1, "link with onclick handler should have 1 action");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=523789"
+ title="nsHTMLLiAccessible shouldn't be inherited from linkable accessible">
+ Mozilla Bug 523789
+ </a><br>
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409"
+ title="Expose click action if mouseup and mousedown are registered">
+ Mozilla Bug 423409
+ </a>
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=659620"
+ title="hang when trying to edit a page on wikimo with NVDA running">
+ Mozilla Bug 659620
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ul>
+ <li id="li_clickable1" onclick="">Clickable list item</li>
+ <li id="li_clickable2" onmousedown="">Clickable list item</li>
+ <li id="li_clickable3" onmouseup="">Clickable list item</li>
+ </ul>
+
+ <!-- linkable accessibles -->
+ <img id="onclick_img" onclick="" src="../moz.png">
+
+ <a id="link1" href="www">linkable textleaf accessible</a>
+ <div id="link2" onclick="">linkable textleaf accessible</div>
+
+ <div>
+ <label for="TextBox_t2" id="label1">
+ <span>Explicit</span>
+ </label>
+ <input name="in2" id="TextBox_t2" type="text" maxlength="17">
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_general.xhtml b/accessible/tests/mochitest/actions/test_general.xhtml
new file mode 100644
index 0000000000..5b376b9624
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_general.xhtml
@@ -0,0 +1,167 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../nsIAccessible_name.css"
+ type="text/css"?>
+
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="nsIAccessible actions testing">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../actions.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ //gA11yEventDumpToConsole = true;
+ //enableLogging("tree,verbose"); // debug
+
+ SimpleTest.expectAssertions(0, 1);
+
+ function doTest()
+ {
+ var actionsArray = [
+ {
+ ID: "menu",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ // Wait for the submenu to show up.
+ eventSeq: [
+ new invokerChecker(EVENT_SHOW, getNode("submenu"))
+ ]
+ },
+ {
+ ID: "submenu",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "menuitem",
+ actionName: "click",
+ events: XUL_EVENTS
+ },
+ {
+ ID: "button",
+ actionName: "press",
+ events: XUL_EVENTS
+ },
+ {
+ ID: "buttonmenu",
+ actionName: "press",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "name_entry_label",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "labelWithPopup",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "toolbarbutton_label",
+ actionName: "click",
+ targetID: "toolbarbutton",
+ events: XUL_EVENTS,
+ allowBubbling: true
+ },
+ {
+ ID: "menulist_label",
+ actionName: "click",
+ // focusChecker expects a unique focus event. However, there might
+ // still be pending focus events not caught by previous tests.
+ eventSeq: [
+ new invokerChecker(EVENT_FOCUS, getNode("menulist"))
+ ]
+ }/*, // XXX: bug 490288
+ {
+ ID: "buttonmenu_item",
+ actionName: "click",
+ events: CLICK_EVENTS
+ }*/
+ ];
+
+ is(getAccessible("name_entry_label").firstChild.actionCount, 1, "label text should have 1 action");
+
+ testActions(actionsArray);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=410765"
+ title="nsIAccessible actions testing">
+ Mozilla Bug 410765
+ </a>
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=504252"
+ title="Expose STATE_HASPOPUP on XUL elements that have an @popup attribute">
+ Mozilla Bug 504252
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menubar>
+ <menu label="menu" id="menu">
+ <menupopup>
+ <menuitem label="menu item" id="menuitem"/>
+ <menu label="submenu" id="submenu">
+ <menupopup>
+ <menuitem label="menu item"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <button label="button" id="button"/>
+
+ <button type="menu" id="buttonmenu" label="button">
+ <menupopup>
+ <menuitem label="item1" id="buttonmenu_item"/>
+ <menuitem label="item1"/>
+ </menupopup>
+ </button>
+
+ <label id="labelWithPopup" value="file name"
+ popup="fileContext"
+ tabindex="0"/>
+ <hbox>
+ <label id="name_entry_label" value="Name" control="name_entry"/>
+ <html:input id="name_entry"/>
+ </hbox>
+ <toolbarbutton id="toolbarbutton">
+ <label id="toolbarbutton_label">toolbarbutton</label>
+ </toolbarbutton>
+ <hbox>
+ <label id="menulist_label" control="menulist">menulist</label>
+ <menulist id="menulist"/>
+ </hbox>
+ </vbox>
+ </hbox>
+</window>
+
diff --git a/accessible/tests/mochitest/actions/test_keys.html b/accessible/tests/mochitest/actions/test_keys.html
new file mode 100644
index 0000000000..acacb34c09
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_keys.html
@@ -0,0 +1,57 @@
+<html>
+
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+ <title>Keyboard shortcuts tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+
+ <script type="application/javascript">
+ function testAcessKey(aAccOrElmOrID, aKey) {
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc)
+ return;
+
+ is(acc.accessKey, aKey,
+ "Wrong keyboard shortcut on " + prettyName(aAccOrElmOrID));
+ }
+
+ function doTest() {
+ testAcessKey("input1", "");
+ testAcessKey("input2", MAC ? "⌃⌥b" : "Alt+Shift+b");
+ testAcessKey("link", MAC ? "⌃⌥l" : "Alt+Shift+l");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=381599"
+ title="Inverse relations cache">
+ Mozilla Bug 381599
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <label accesskey="a">
+ <input id="input1"/>
+ </label>
+ <label accesskey="b" for="input2">
+ <input id="input2"/>
+ <a id="link" accesskey="l">link</a>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_keys.xhtml b/accessible/tests/mochitest/actions/test_keys.xhtml
new file mode 100644
index 0000000000..46d936166a
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_keys.xhtml
@@ -0,0 +1,124 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Accessible XUL access keys and shortcut keys tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function openMenu(aMenuID, aMenuitemID)
+ {
+ this.menuNode = getNode(aMenuID);
+ this.menuitemNode = getNode(aMenuitemID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.menuNode)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ // Show menu.
+ this.menuNode.open = true;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ var menu = getAccessible(aMenuID);
+ is(menu.accessKey, (MAC ? "u" : "Alt+u"),
+ "Wrong accesskey on " + prettyName(this.menuitemNode));
+
+ var menuitem = getAccessible(aMenuitemID);
+ is(menuitem.accessKey, "p",
+ "Wrong accesskey on " + prettyName(this.menuitemNode));
+ is(menuitem.keyboardShortcut, (MAC ? "⌃l" : "Ctrl+l"),
+ "Wrong keyboard shortcut on " + prettyName(this.menuitemNode));
+ }
+
+ this.getID = function openMenu_getID()
+ {
+ return "menuitem accesskey and shortcut test " +
+ prettyName(this.menuItemNode);
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ // HTML element should get accessKey from associated XUL label.
+ let input = getAccessible("input");
+ is(input.accessKey, (MAC ? "⌃⌥i" : "Alt+Shift+i"),
+ "Wrong accessKey on input");
+
+ // Test accessKey on HTML element inside shadow DOM.
+ let shadowButton = getAccessible(
+ document.getElementById("buttonShadow").shadowRoot.firstElementChild);
+ is(shadowButton.accessKey, (MAC ? "⌃⌥t" : "Alt+Shift+t"),
+ "Wrong accessKey on shadow button");
+
+ gQueue = new eventQueue();
+ gQueue.push(new openMenu("menu", "menuitem"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=672092"
+ title="Reorganize access key and keyboard shortcut handling code">
+ Mozilla Bug 672092
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <label control="input" accesskey="i">input</label>
+ <html:input id="input"/>
+
+ <html:div id="buttonShadow"/>
+ <script>
+ <![CDATA[
+ let host = document.getElementById("buttonShadow");
+ let shadow = host.attachShadow({mode: "open"});
+ let button = document.createElement("button");
+ button.setAttribute("accesskey", "t");
+ shadow.append(button);
+ ]]>
+ </script>
+
+ <keyset>
+ <key key="l" modifiers="control" id="key1"/>
+ </keyset>
+
+ <menubar>
+ <menu label="menu" id="menu" accesskey="u">
+ <menupopup>
+ <menuitem accesskey="p" key="key1" label="item1" id="menuitem"/>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/actions/test_link.html b/accessible/tests/mochitest/actions/test_link.html
new file mode 100644
index 0000000000..cf10f4bec0
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_link.html
@@ -0,0 +1,145 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing on HTML links (HTML:a)</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+
+ <script type="application/javascript">
+ function getAnchorTargetDocumentAcc() {
+ var thisTabDocAcc = getTabDocAccessible();
+ var thisDocTabPanelAcc = thisTabDocAcc.parent.parent;
+ var tabPanelsAcc = thisDocTabPanelAcc.parent;
+ var newDocTabPanelAcc = tabPanelsAcc.firstChild;
+ var nextAcc = newDocTabPanelAcc;
+
+ while ((nextAcc = nextAcc.nextSibling)) {
+ // Find the last accessible for a browser with about:mozilla loaded.
+ if (nextAcc.firstChild.DOMNode.currentURI.spec == "about:mozilla") {
+ newDocTabPanelAcc = nextAcc;
+ }
+ }
+
+ return newDocTabPanelAcc.firstChild.firstChild;
+ }
+
+ function linkChecker(aID) {
+ this.type = EVENT_DOCUMENT_LOAD_COMPLETE;
+ this.__defineGetter__("target", getAnchorTargetDocumentAcc);
+
+ this.check = function linkChecker_check() {
+ var anchorTargetWindow =
+ getAccessible(getAnchorTargetDocumentAcc(), [nsIAccessibleDocument]).
+ window;
+ anchorTargetWindow.close();
+ };
+
+ this.getID = function linkChecker_getID() {
+ return "link '" + aID + "' states check ";
+ };
+ }
+
+ // gA11yEventDumpToConsole = true;
+ // enableLogging("tree,eventTree,verbose");
+
+ function doTest() {
+ var actionsArray = [
+ {
+ ID: "link1",
+ actionName: "jump",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new linkChecker("link1"),
+ ],
+ },
+ {
+ ID: "img1",
+ targetID: "link1",
+ actionName: "click ancestor",
+ events: CLICK_EVENTS,
+ allowBubbling: true,
+ eventSeq: [
+ new linkChecker("link1"),
+ ],
+ },
+ {
+ ID: "link2",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "img2",
+ targetID: "link2",
+ actionName: "click ancestor",
+ events: CLICK_EVENTS,
+ allowBubbling: true,
+ },
+ {
+ ID: "link3",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "img3",
+ targetID: "link3",
+ actionName: "click ancestor",
+ events: CLICK_EVENTS,
+ allowBubbling: true,
+ },
+ {
+ ID: "link4",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "img4",
+ targetID: "link4",
+ actionName: "click ancestor",
+ events: CLICK_EVENTS,
+ allowBubbling: true,
+ },
+ ];
+ testActions(actionsArray);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409"
+ title="Expose click action if mouseup and mousedown are registered">
+ Mozilla Bug 423409
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <a href="about:mozilla" id="link1" target="_blank" rel="opener">
+ <img src="../moz.png" id="img1">
+ </a>
+ <a id="link2" onmousedown="">
+ <img src="../moz.png" id="img2">
+ </a>
+ <a id="link3" onclick="">
+ <img src="../moz.png" id="img3">
+ </a>
+ <a id="link4" onmouseup="">
+ <img src="../moz.png" id="img4">
+ </a>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_media.html b/accessible/tests/mochitest/actions/test_media.html
new file mode 100644
index 0000000000..f6232e51e9
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_media.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=483573
+-->
+<head>
+ <title>HTML5 audio/video tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+
+ // gA11yEventDumpID = "eventDump";
+ // gA11yEventDumpToConsole = true; // debug stuff
+
+ function focusChecker(aAcc) {
+ this.type = EVENT_FOCUS;
+ this.target = aAcc;
+ this.getID = function focusChecker_getID() {
+ return "focus handling";
+ };
+ this.check = function focusChecker_check(aEvent) {
+ testStates(this.target, STATE_FOCUSED);
+ };
+ }
+
+ function nameChecker(aAcc, aName) {
+ this.type = EVENT_NAME_CHANGE;
+ this.target = aAcc;
+ this.getID = function nameChecker_getID() {
+ return "name change handling";
+ };
+ this.check = function nameChecker_check(aEvent) {
+ is(aEvent.accessible.name, aName,
+ "Wrong name of " + prettyName(aEvent.accessible) + " on focus");
+ };
+ }
+
+ async function loadAudioSource() {
+ /**
+ * Setting the source dynamically and wait for it to load,
+ * so we can test the accessibility tree of the control in its ready and
+ * stable state.
+ *
+ * See bug 1484048 comment 25 for discussion on how it switches UI when
+ * loading a statically declared source.
+ */
+ await new Promise(resolve => {
+ let el = document.getElementById("audio");
+ el.addEventListener("canplaythrough", resolve, {once: true});
+ el.src = "../bug461281.ogg";
+ });
+
+ doTest();
+ }
+
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // test actions of audio controls
+
+ todo(false, "Focus test are disabled until bug 494175 is fixed.");
+
+ var audioElm = getAccessible("audio");
+ var playBtn = audioElm.firstChild;
+ // var scrubber = playBtn.nextSibling.nextSibling.nextSibling;
+ var muteBtn = audioElm.lastChild.previousSibling;
+
+ var actions = [
+ {
+ ID: muteBtn,
+ actionName: "press",
+ eventTarget: "element",
+ eventSeq: [
+ // new focusChecker(muteBtn),
+ new nameChecker(muteBtn, "Unmute"),
+ ],
+ },
+ // {
+ // ID: scrubber,
+ // actionName: "activate",
+ // events: null,
+ // eventSeq: [
+ // new focusChecker(scrubber)
+ // ]
+ // },
+ {
+ ID: playBtn,
+ actionName: "press",
+ eventTarget: "element",
+ eventSeq: [
+ // new focusChecker(playBtn),
+ new nameChecker(playBtn, "Pause"),
+ ],
+ },
+ ];
+
+ testActions(actions); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(loadAudioSource);
+ </script>
+</head>
+<body>
+
+ <a target="_blank" rel="opener"
+ title="Expose HTML5 video and audio elements' embedded controls through accessibility APIs"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=483573">Mozilla Bug 483573</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <audio id="audio" controls="true"></audio>
+
+ <div id="eventDump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_select.html b/accessible/tests/mochitest/actions/test_select.html
new file mode 100644
index 0000000000..7f55dfc0ef
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_select.html
@@ -0,0 +1,67 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing for HTML select</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+
+ <script type="application/javascript">
+ // gA11yEventDumpToConsole = true; // debugging
+ function doTest() {
+ var actionsArray = [
+ {
+ ID: "lb_apple",
+ actionIndex: 0,
+ actionName: "select",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new focusChecker("lb_apple"),
+ ],
+ },
+ ];
+
+ testActions(actionsArray);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="listbox" size="2">
+ <option id="lb_orange">orange</option>
+ <option id="lb_apple">apple</option>
+ </select>
+
+ <select id="combobox">
+ <option id="cb_orange">orange</option>
+ <option id="cb_apple">apple</option>
+ </select>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_tree.xhtml b/accessible/tests/mochitest/actions/test_tree.xhtml
new file mode 100644
index 0000000000..17710cbdce
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_tree.xhtml
@@ -0,0 +1,127 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree actions tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../actions.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Accessible tree testers
+
+ function stateFocusChecker(aAcc, aStates)
+ {
+ this.__proto__ = new focusChecker(aAcc);
+
+ this.check = function focusChecker_check(aEvent)
+ {
+ var states = aStates ? aStates : 0;
+ testStates(this.target, STATE_FOCUSED | STATE_SELECTED | states);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+ //gA11yEventDumpToConsole = true; // debug
+
+ function doTest()
+ {
+ var treeNode = getNode("tree");
+
+ var treeBodyNode = treeNode.treeBody;
+
+ var tree = getAccessible(treeNode);
+ var expandedTreeItem = tree.getChildAt(2);
+ var collapsedTreeItem = tree.getChildAt(5);
+
+ var actions = [
+ {
+ ID: expandedTreeItem,
+ actionName: "activate",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ eventSeq: [
+ new stateFocusChecker(expandedTreeItem, STATE_EXPANDED)
+ ]
+ },
+ {
+ ID: collapsedTreeItem,
+ actionName: "expand",
+ actionIndex: 1,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ checkOnClickEvent: function check(aEvent)
+ {
+ testStates(this.ID, STATE_EXPANDED);
+ }
+ },
+ {
+ ID: collapsedTreeItem,
+ actionName: "collapse",
+ actionIndex: 1,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ checkOnClickEvent: function check(aEvent)
+ {
+ testStates(this.ID, STATE_COLLAPSED);
+ }
+ }
+ ];
+
+ testActions(actions); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTreeTreeView());
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tree" flex="1" minheight="100px">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/actions/test_treegrid.xhtml b/accessible/tests/mochitest/actions/test_treegrid.xhtml
new file mode 100644
index 0000000000..1c6e1bb8aa
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_treegrid.xhtml
@@ -0,0 +1,190 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../treeview.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree actions tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../actions.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Accessible tree testers
+
+ function focusChecker(aAcc, aStates)
+ {
+ this.type = EVENT_FOCUS;
+ this.target = aAcc;
+ this.getID = function focusChecker_getID()
+ {
+ return "focus handling";
+ }
+ this.check = function focusChecker_check(aEvent)
+ {
+ var states = aStates ? aStates : 0;
+ testStates(this.target, STATE_FOCUSED | STATE_SELECTED | states);
+ }
+ }
+
+ function stateChangeChecker(aAcc, aIsEnabled)
+ {
+ this.type = EVENT_STATE_CHANGE;
+ this.target = aAcc;
+ this.getID = function stateChangeChecker_getID()
+ {
+ return "state change handling";
+ }
+ this.check = function stateChangeChecker_check(aEvent)
+ {
+ if (aIsEnabled)
+ testStates(this.target, STATE_CHECKED);
+ else
+ testStates(this.target, STATE_CHECKABLE, 0, STATE_CHECKED);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTestActions()
+ {
+ var treeNode = getNode("tabletree");
+
+ var treeBodyNode = treeNode.treeBody;
+ treeNode.focus();
+
+ var tree = getAccessible(treeNode);
+
+ var expandedTreeItem = tree.getChildAt(2);
+ var collapsedTreeItem = tree.getChildAt(5);
+ var cycleCell = expandedTreeItem.getChildAt(0);
+ var checkableCell = expandedTreeItem.getChildAt(3);
+
+ var actions = [
+ {
+ ID: expandedTreeItem,
+ actionName: "activate",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ eventSeq: [
+ new focusChecker(expandedTreeItem, STATE_EXPANDED)
+ ]
+ },
+ {
+ ID: collapsedTreeItem,
+ actionName: "expand",
+ actionIndex: 1,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ check: function check(aEvent)
+ {
+ testStates(this.ID, STATE_EXPANDED);
+ }
+ },
+ {
+ ID: collapsedTreeItem,
+ actionName: "collapse",
+ actionIndex: 1,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ check: function check(aEvent)
+ {
+ testStates(this.ID, STATE_COLLAPSED);
+ }
+ },
+ {
+ ID: cycleCell,
+ actionName: "cycle",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode
+ },
+ {
+ ID: checkableCell,
+ actionName: "uncheck",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ eventSeq: [
+ new stateChangeChecker(checkableCell, false)
+ ]
+ },
+ {
+ ID: checkableCell,
+ actionName: "check",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ eventSeq: [
+ new stateChangeChecker(checkableCell, true)
+ ]
+ }
+ ];
+
+ testActions(actions); // Will call SimpleTest.finish();
+ }
+
+ // gA11yEventDumpID = "debug";
+
+ function doTest()
+ {
+ var treeNode = getNode("tabletree");
+ waitForEvent(EVENT_REORDER, treeNode, doTestActions);
+ treeNode.view = new nsTreeTreeView();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank" rel="opener"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tabletree" flex="1" editable="true">
+ <treecols>
+ <treecol id="tabletree_col1" cycler="true" label="cycler"/>
+ <treecol id="tabletree_col2" flex="1" primary="true" label="column1"/>
+ <treecol id="tabletree_col3" flex="1" label="column2"/>
+ <treecol id="tabletree_col4" flex="1" label="checker"
+ type="checkbox" editable="true"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/aom/a11y.ini b/accessible/tests/mochitest/aom/a11y.ini
new file mode 100644
index 0000000000..03085c1deb
--- /dev/null
+++ b/accessible/tests/mochitest/aom/a11y.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_general.html]
diff --git a/accessible/tests/mochitest/aom/test_general.html b/accessible/tests/mochitest/aom/test_general.html
new file mode 100644
index 0000000000..dc63fb659b
--- /dev/null
+++ b/accessible/tests/mochitest/aom/test_general.html
@@ -0,0 +1,208 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Accessibility API: generic</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script>
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+ const finish = SimpleTest.finish.bind(SimpleTest);
+ enablePref()
+ .then(createIframe)
+ .then(checkImplementation)
+ .catch(err => {
+ dump(`${err}: ${err.stack}`);
+ finish();
+ });
+
+ function enablePref() {
+ const ops = {
+ "set": [
+ [ "accessibility.AOM.enabled", true ],
+ ],
+ };
+ return SpecialPowers.pushPrefEnv(ops);
+ }
+
+ // WebIDL conditional annotations for an interface are evaluated once per
+ // global, so we need to create an iframe to see the effects of calling
+ // enablePref().
+ function createIframe() {
+ return new Promise((resolve) => {
+ let iframe = document.createElement("iframe");
+ iframe.src = `data:text/html,<html><body>hey</body></html>`;
+ iframe.onload = () => resolve(iframe.contentDocument);
+ document.body.appendChild(iframe);
+ document.body.offsetTop; // We rely on the a11y tree being created
+ // already, and it's created off layout.
+ });
+ }
+
+ function testStringProp(anode, prop) {
+ is(anode[prop], null, `anode.${prop} should be null`);
+ let text = "This is a string test";
+ anode[prop] = text;
+ is(anode[prop], text, `anode.${prop} was assigned "${text}"`);
+ anode[prop] = null;
+ is(anode[prop], null, `anode.${prop} was assigned null`);
+ }
+
+ function testBoolProp(anode, prop) {
+ is(anode[prop], null, `anode.${prop} should be null`);
+ anode[prop] = true;
+ is(anode[prop], true, `anode.${prop} was assigned true`);
+ anode[prop] = false;
+ is(anode[prop], false, `anode.${prop} was assigned false`);
+ anode[prop] = null;
+ is(anode[prop], null, `anode.${prop} was assigned null`);
+ }
+
+ function testDoubleProp(anode, prop) {
+ is(anode[prop], null, `anode.${prop} should be null`);
+ anode[prop] = Number.MAX_VALUE;
+ is(anode[prop], Number.MAX_VALUE, `anode.${prop} was assigned ${Number.MAX_VALUE}`);
+ anode[prop] = null;
+ is(anode[prop], null, `anode.${prop} was assigned null`);
+ }
+
+ function testIntProp(anode, prop) {
+ is(anode[prop], null, `anode.${prop} should be null`);
+ anode[prop] = -1;
+ is(anode[prop], -1, `anode.${prop} was assigned -1`);
+ anode[prop] = null;
+ is(anode[prop], null, `anode.${prop} was assigned null`);
+ }
+
+ function testUIntProp(anode, prop) {
+ is(anode[prop], null, `anode.${prop} should be null`);
+ anode[prop] = 4294967295;
+ is(anode[prop], 4294967295, `anode.${prop} was assigned 4294967295`);
+ anode[prop] = null;
+ is(anode[prop], null, `anode.${prop} was assigned null`);
+ }
+
+ function testRelationProp(anode, node, prop) {
+ is(anode[prop], null, `anode.${prop} should be null`);
+ anode[prop] = node.accessibleNode;
+ is(anode[prop], node.accessibleNode, `anode.${prop} was assigned AccessibleNode`);
+ anode[prop] = null;
+ is(anode[prop], null, `anode.${prop} was assigned null`);
+ }
+ // Check that the WebIDL is as expected.
+ function checkImplementation(ifrDoc) {
+ let anode = ifrDoc.accessibleNode;
+ ok(anode, "DOM document has accessible node");
+
+ is(anode.computedRole, "document", "correct role of a document accessible node");
+ is(anode.DOMNode, ifrDoc, "correct DOM Node of a document accessible node");
+
+ // States may differ depending on the document state, for example, if it is
+ // loaded or is loading still.
+ var states = null;
+ switch (anode.states.length) {
+ case 5:
+ states = [
+ "readonly", "focusable", "opaque", "enabled", "sensitive",
+ ];
+ break;
+ case 6:
+ states = [
+ "readonly", "busy", "focusable", "opaque", "enabled", "sensitive",
+ ];
+ break;
+ case 7:
+ states = [
+ "readonly", "busy", "focusable", "opaque", "stale", "enabled", "sensitive",
+ ];
+ break;
+ default:
+ ok(false, "Unexpected amount of states: " + JSON.stringify(anode.states));
+ }
+ if (states) {
+ for (let i = 0; i < states.length; i++) {
+ is(anode.states[i], states[i], `${states[i]} state is expected at ${i}th index`);
+ }
+ }
+
+ ok(anode.is("document", "focusable"),
+ "correct role and state on an accessible node");
+
+ is(anode.get("explicit-name"), "true",
+ "correct object attribute value on an accessible node");
+
+ ok(anode.has("explicit-name"),
+ "object attributes are present");
+
+ var attrs = [ "explicit-name" ];
+ if (anode.attributes.length > 1) {
+ attrs = [
+ "margin-left", "text-align", "text-indent", "margin-right",
+ "tag", "margin-top", "margin-bottom", "display",
+ "explicit-name",
+ ];
+ }
+
+ is(anode.attributes.length, attrs.length, "correct number of attributes");
+ for (let i = 0; i < attrs.length; i++) {
+ ok(attrs.includes(anode.attributes[i]),
+ `${anode.attributes[i]} attribute is expected and found`);
+ }
+
+ const strProps = ["autocomplete", "checked", "current", "hasPopUp", "invalid",
+ "keyShortcuts", "label", "live", "orientation", "placeholder",
+ "pressed", "relevant", "role", "roleDescription", "sort",
+ "valueText"];
+
+ for (const strProp of strProps) {
+ testStringProp(anode, strProp);
+ }
+
+ const boolProps = ["atomic", "busy", "disabled", "expanded", "hidden", "modal",
+ "multiline", "multiselectable", "readOnly", "required", "selected"];
+
+ for (const boolProp of boolProps) {
+ testBoolProp(anode, boolProp);
+ }
+
+ const doubleProps = ["valueMax", "valueMin", "valueNow"];
+
+ for (const doubleProp of doubleProps) {
+ testDoubleProp(anode, doubleProp);
+ }
+
+ const intProps = ["colCount", "rowCount", "setSize"];
+
+ for (const intProp of intProps) {
+ testIntProp(anode, intProp);
+ }
+
+ const uintProps = ["colIndex", "colSpan", "level", "posInSet", "rowIndex", "rowSpan"];
+
+ for (const uintProp of uintProps) {
+ testUIntProp(anode, uintProp);
+ }
+
+ // Check if an AccessibleNode is properly cached.
+ let node = ifrDoc.createElement("div");
+ anode = node.accessibleNode;
+ is(anode, node.accessibleNode, "an AccessibleNode is properly cached");
+
+ // Adopting node to another document doesn't change .accessibleNode
+ let anotherDoc = ifrDoc.implementation.createDocument("", "", null);
+ let adopted_node = anotherDoc.adoptNode(node);
+ is(anode, adopted_node.accessibleNode, "adopting node to another document doesn't change node.accessibleNode");
+
+ const relationProps = ["activeDescendant", "details", "errorMessage"];
+
+ for (const relationProp of relationProps) {
+ testRelationProp(anode, node, relationProp);
+ }
+
+ finish();
+ }
+ </script>
+</head>
diff --git a/accessible/tests/mochitest/attributes.js b/accessible/tests/mochitest/attributes.js
new file mode 100644
index 0000000000..ebb5a54b85
--- /dev/null
+++ b/accessible/tests/mochitest/attributes.js
@@ -0,0 +1,516 @@
+/* import-globals-from common.js */
+
+// //////////////////////////////////////////////////////////////////////////////
+// Object attributes.
+
+/**
+ * Test object attributes.
+ *
+ * @param aAccOrElmOrID [in] the accessible identifier
+ * @param aAttrs [in] the map of expected object attributes
+ * (name/value pairs)
+ * @param aSkipUnexpectedAttrs [in] points this function doesn't fail if
+ * unexpected attribute is encountered
+ * @param aTodo [in] true if this is a 'todo'
+ */
+function testAttrs(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs, aTodo) {
+ testAttrsInternal(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs, null, aTodo);
+}
+
+/**
+ * Test object attributes that must not be present.
+ *
+ * @param aAccOrElmOrID [in] the accessible identifier
+ * @param aAbsentAttrs [in] map of attributes that should not be
+ * present (name/value pairs)
+ * @param aTodo [in] true if this is a 'todo'
+ */
+function testAbsentAttrs(aAccOrElmOrID, aAbsentAttrs, aTodo) {
+ testAttrsInternal(aAccOrElmOrID, {}, true, aAbsentAttrs, aTodo);
+}
+
+/**
+ * Test object attributes that aren't right, but should be (todo)
+ *
+ * @param aAccOrElmOrID [in] the accessible identifier
+ * @param aKey [in] attribute name
+ * @param aExpectedValue [in] expected attribute value
+ */
+function todoAttr(aAccOrElmOrID, aKey, aExpectedValue) {
+ testAttrs(
+ aAccOrElmOrID,
+ Object.fromEntries([[aKey, aExpectedValue]]),
+ true,
+ true
+ );
+}
+
+/**
+ * Test CSS based object attributes.
+ */
+function testCSSAttrs(aID) {
+ var node = document.getElementById(aID);
+ var computedStyle = document.defaultView.getComputedStyle(node);
+
+ var attrs = {
+ display: computedStyle.display,
+ "text-align": computedStyle.textAlign,
+ "text-indent": computedStyle.textIndent,
+ "margin-left": computedStyle.marginLeft,
+ "margin-right": computedStyle.marginRight,
+ "margin-top": computedStyle.marginTop,
+ "margin-bottom": computedStyle.marginBottom,
+ };
+ testAttrs(aID, attrs, true);
+}
+
+/**
+ * Test the accessible that it doesn't have CSS-based object attributes.
+ */
+function testAbsentCSSAttrs(aID) {
+ var attrs = {
+ display: "",
+ "text-align": "",
+ "text-indent": "",
+ "margin-left": "",
+ "margin-right": "",
+ "margin-top": "",
+ "margin-bottom": "",
+ };
+ testAbsentAttrs(aID, attrs);
+}
+
+/**
+ * Test group object attributes (posinset, setsize and level) and
+ * nsIAccessible::groupPosition() method.
+ *
+ * @param aAccOrElmOrID [in] the ID, DOM node or accessible
+ * @param aPosInSet [in] the value of 'posinset' attribute
+ * @param aSetSize [in] the value of 'setsize' attribute
+ * @param aLevel [in, optional] the value of 'level' attribute
+ */
+function testGroupAttrs(aAccOrElmOrID, aPosInSet, aSetSize, aLevel, aTodo) {
+ var acc = getAccessible(aAccOrElmOrID);
+ var levelObj = {},
+ posInSetObj = {},
+ setSizeObj = {};
+ acc.groupPosition(levelObj, setSizeObj, posInSetObj);
+
+ let groupPos = {},
+ expectedGroupPos = {};
+
+ if (aPosInSet && aSetSize) {
+ groupPos.setsize = String(setSizeObj.value);
+ groupPos.posinset = String(posInSetObj.value);
+
+ expectedGroupPos.setsize = String(aSetSize);
+ expectedGroupPos.posinset = String(aPosInSet);
+ }
+
+ if (aLevel) {
+ groupPos.level = String(levelObj.value);
+
+ expectedGroupPos.level = String(aLevel);
+ }
+
+ compareSimpleObjects(
+ groupPos,
+ expectedGroupPos,
+ false,
+ "wrong groupPos",
+ aTodo
+ );
+
+ testAttrs(aAccOrElmOrID, expectedGroupPos, true, aTodo);
+}
+
+function testGroupParentAttrs(
+ aAccOrElmOrID,
+ aChildItemCount,
+ aIsHierarchical,
+ aTodo
+) {
+ testAttrs(
+ aAccOrElmOrID,
+ { "child-item-count": String(aChildItemCount) },
+ true,
+ aTodo
+ );
+
+ if (aIsHierarchical) {
+ testAttrs(aAccOrElmOrID, { tree: "true" }, true, aTodo);
+ } else {
+ testAbsentAttrs(aAccOrElmOrID, { tree: "true" });
+ }
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Text attributes.
+
+/**
+ * Test text attributes.
+ *
+ * @param aID [in] the ID of DOM element having text
+ * accessible
+ * @param aOffset [in] the offset inside text accessible to fetch
+ * text attributes
+ * @param aAttrs [in] the map of expected text attributes
+ * (name/value pairs) exposed at the offset
+ * @param aDefAttrs [in] the map of expected text attributes
+ * (name/value pairs) exposed on hyper text
+ * accessible
+ * @param aStartOffset [in] expected start offset where text attributes
+ * are applied
+ * @param aEndOffset [in] expected end offset where text attribute
+ * are applied
+ * @param aSkipUnexpectedAttrs [in] points the function doesn't fail if
+ * unexpected attribute is encountered
+ */
+function testTextAttrs(
+ aID,
+ aOffset,
+ aAttrs,
+ aDefAttrs,
+ aStartOffset,
+ aEndOffset,
+ aSkipUnexpectedAttrs
+) {
+ var accessible = getAccessible(aID, [nsIAccessibleText]);
+ if (!accessible) {
+ return;
+ }
+
+ var startOffset = { value: -1 };
+ var endOffset = { value: -1 };
+
+ // do not include attributes exposed on hyper text accessible
+ var attrs = getTextAttributes(
+ aID,
+ accessible,
+ false,
+ aOffset,
+ startOffset,
+ endOffset
+ );
+
+ if (!attrs) {
+ return;
+ }
+
+ var errorMsg = " for " + aID + " at offset " + aOffset;
+
+ is(startOffset.value, aStartOffset, "Wrong start offset" + errorMsg);
+ is(endOffset.value, aEndOffset, "Wrong end offset" + errorMsg);
+
+ compareAttrs(errorMsg, attrs, aAttrs, aSkipUnexpectedAttrs);
+
+ // include attributes exposed on hyper text accessible
+ var expectedAttrs = {};
+ for (let name in aAttrs) {
+ expectedAttrs[name] = aAttrs[name];
+ }
+
+ for (let name in aDefAttrs) {
+ if (!(name in expectedAttrs)) {
+ expectedAttrs[name] = aDefAttrs[name];
+ }
+ }
+
+ attrs = getTextAttributes(
+ aID,
+ accessible,
+ true,
+ aOffset,
+ startOffset,
+ endOffset
+ );
+
+ if (!attrs) {
+ return;
+ }
+
+ compareAttrs(errorMsg, attrs, expectedAttrs, aSkipUnexpectedAttrs);
+}
+
+/**
+ * Test default text attributes.
+ *
+ * @param aID [in] the ID of DOM element having text
+ * accessible
+ * @param aDefAttrs [in] the map of expected text attributes
+ * (name/value pairs)
+ * @param aSkipUnexpectedAttrs [in] points the function doesn't fail if
+ * unexpected attribute is encountered
+ */
+function testDefaultTextAttrs(aID, aDefAttrs, aSkipUnexpectedAttrs) {
+ var accessible = getAccessible(aID, [nsIAccessibleText]);
+ if (!accessible) {
+ return;
+ }
+
+ var defAttrs = null;
+ try {
+ defAttrs = accessible.defaultTextAttributes;
+ } catch (e) {}
+
+ if (!defAttrs) {
+ ok(false, "Can't get default text attributes for " + aID);
+ return;
+ }
+
+ var errorMsg = ". Getting default text attributes for " + aID;
+ compareAttrs(errorMsg, defAttrs, aDefAttrs, aSkipUnexpectedAttrs);
+}
+
+/**
+ * Test text attributes for wrong offset.
+ */
+function testTextAttrsWrongOffset(aID, aOffset) {
+ var res = false;
+ try {
+ var s = {},
+ e = {};
+ // Bug 1602031
+ // eslint-disable-next-line no-undef
+ var acc = getAccessible(ID, [nsIAccessibleText]);
+ acc.getTextAttributes(false, 157, s, e);
+ } catch (ex) {
+ res = true;
+ }
+
+ ok(
+ res,
+ "text attributes are calculated successfully at wrong offset " +
+ aOffset +
+ " for " +
+ prettyName(aID)
+ );
+}
+
+const kNormalFontWeight = function equalsToNormal(aWeight) {
+ return aWeight <= 400;
+};
+
+const kBoldFontWeight = function equalsToBold(aWeight) {
+ return aWeight > 400;
+};
+
+let isNNT = SpecialPowers.getBoolPref("widget.non-native-theme.enabled");
+// The pt font size of the input element can vary by Linux distro.
+const kInputFontSize =
+ WIN || (MAC && isNNT)
+ ? "10pt"
+ : MAC
+ ? "8pt"
+ : function () {
+ return true;
+ };
+
+const kAbsentFontFamily = function (aFontFamily) {
+ return aFontFamily != "sans-serif";
+};
+const kInputFontFamily = function (aFontFamily) {
+ return aFontFamily != "sans-serif";
+};
+
+const kMonospaceFontFamily = function (aFontFamily) {
+ return aFontFamily != "monospace";
+};
+const kSansSerifFontFamily = function (aFontFamily) {
+ return aFontFamily != "sans-serif";
+};
+const kSerifFontFamily = function (aFontFamily) {
+ return aFontFamily != "serif";
+};
+
+const kCursiveFontFamily = LINUX ? "DejaVu Serif" : "Comic Sans MS";
+
+/**
+ * Return used font from the given computed style.
+ */
+function fontFamily(aComputedStyle) {
+ var name = aComputedStyle.fontFamily;
+ switch (name) {
+ case "monospace":
+ return kMonospaceFontFamily;
+ case "sans-serif":
+ return kSansSerifFontFamily;
+ case "serif":
+ return kSerifFontFamily;
+ default:
+ return name;
+ }
+}
+
+/**
+ * Returns a computed system color for this document.
+ */
+function getSystemColor(aColor) {
+ let { r, g, b, a } = InspectorUtils.colorToRGBA(aColor, document);
+ return a == 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${a})`;
+}
+
+/**
+ * Build an object of default text attributes expected for the given accessible.
+ *
+ * @param aID [in] identifier of accessible
+ * @param aFontSize [in] font size
+ * @param aFontWeight [in, optional] kBoldFontWeight or kNormalFontWeight,
+ * default value is kNormalFontWeight
+ */
+function buildDefaultTextAttrs(aID, aFontSize, aFontWeight, aFontFamily) {
+ var elm = getNode(aID);
+ var computedStyle = document.defaultView.getComputedStyle(elm);
+ var bgColor =
+ computedStyle.backgroundColor == "rgba(0, 0, 0, 0)"
+ ? getSystemColor("Canvas")
+ : computedStyle.backgroundColor;
+
+ var defAttrs = {
+ "font-style": computedStyle.fontStyle,
+ "font-size": aFontSize,
+ "background-color": bgColor,
+ "font-weight": aFontWeight ? aFontWeight : kNormalFontWeight,
+ color: computedStyle.color,
+ "font-family": aFontFamily ? aFontFamily : fontFamily(computedStyle),
+ "text-position": computedStyle.verticalAlign,
+ };
+
+ return defAttrs;
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Private.
+
+function getTextAttributes(
+ aID,
+ aAccessible,
+ aIncludeDefAttrs,
+ aOffset,
+ aStartOffset,
+ aEndOffset
+) {
+ // This function expects the passed in accessible to already be queried for
+ // nsIAccessibleText.
+ var attrs = null;
+ try {
+ attrs = aAccessible.getTextAttributes(
+ aIncludeDefAttrs,
+ aOffset,
+ aStartOffset,
+ aEndOffset
+ );
+ } catch (e) {}
+
+ if (attrs) {
+ return attrs;
+ }
+
+ ok(false, "Can't get text attributes for " + aID);
+ return null;
+}
+
+function testAttrsInternal(
+ aAccOrElmOrID,
+ aAttrs,
+ aSkipUnexpectedAttrs,
+ aAbsentAttrs,
+ aTodo
+) {
+ var accessible = getAccessible(aAccOrElmOrID);
+ if (!accessible) {
+ return;
+ }
+
+ var attrs = null;
+ try {
+ attrs = accessible.attributes;
+ } catch (e) {}
+
+ if (!attrs) {
+ ok(false, "Can't get object attributes for " + prettyName(aAccOrElmOrID));
+ return;
+ }
+
+ var errorMsg = " for " + prettyName(aAccOrElmOrID);
+ compareAttrs(
+ errorMsg,
+ attrs,
+ aAttrs,
+ aSkipUnexpectedAttrs,
+ aAbsentAttrs,
+ aTodo
+ );
+}
+
+function compareAttrs(
+ aErrorMsg,
+ aAttrs,
+ aExpectedAttrs,
+ aSkipUnexpectedAttrs,
+ aAbsentAttrs,
+ aTodo
+) {
+ // Check if all obtained attributes are expected and have expected value.
+ let attrObject = {};
+ for (let prop of aAttrs.enumerate()) {
+ attrObject[prop.key] = prop.value;
+ }
+
+ // Create expected attributes set by using the return values from
+ // embedded functions to determine the entry's value.
+ let expectedObj = Object.fromEntries(
+ Object.entries(aExpectedAttrs).map(([k, v]) => {
+ if (v instanceof Function) {
+ // If value is a function that returns true given the received
+ // attribute value, assign the attribute value to the entry.
+ // If it is false, stringify the function for good error reporting.
+ let value = v(attrObject[k]) ? attrObject[k] : v.toString();
+ return [k, value];
+ }
+
+ return [k, v];
+ })
+ );
+
+ compareSimpleObjects(
+ attrObject,
+ expectedObj,
+ aSkipUnexpectedAttrs,
+ aErrorMsg,
+ aTodo
+ );
+
+ // Check if all unexpected attributes are absent.
+ if (aAbsentAttrs) {
+ let presentAttrs = Object.keys(attrObject).filter(
+ k => aAbsentAttrs[k] !== undefined
+ );
+ if (presentAttrs.length) {
+ (aTodo ? todo : ok)(
+ false,
+ `There were unexpected attributes: ${presentAttrs}`
+ );
+ }
+ }
+}
+
+function compareSimpleObjects(
+ aObj,
+ aExpectedObj,
+ aSkipUnexpectedAttrs,
+ aMessage,
+ aTodo
+) {
+ let keys = aSkipUnexpectedAttrs
+ ? Object.keys(aExpectedObj).sort()
+ : Object.keys(aObj).sort();
+ let o1 = JSON.stringify(aObj, keys);
+ let o2 = JSON.stringify(aExpectedObj, keys);
+
+ if (aTodo) {
+ todo_is(o1, o2, `${aMessage} - Got ${o1}, expected ${o2}`);
+ } else {
+ is(o1, o2, aMessage);
+ }
+}
diff --git a/accessible/tests/mochitest/attributes/a11y.ini b/accessible/tests/mochitest/attributes/a11y.ini
new file mode 100644
index 0000000000..ab880e4fcb
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/a11y.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_dpub_aria_xml-roles.html]
+[test_graphics_aria_xml-roles.html]
+[test_listbox.html]
+[test_obj.html]
+[test_obj_css.html]
+[test_obj_group.html]
+[test_obj_group.xhtml]
+[test_obj_group_tree.xhtml]
+[test_tag.html]
+[test_xml-roles.html]
diff --git a/accessible/tests/mochitest/attributes/test_dpub_aria_xml-roles.html b/accessible/tests/mochitest/attributes/test_dpub_aria_xml-roles.html
new file mode 100644
index 0000000000..a6b4dd4840
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_dpub_aria_xml-roles.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>XML roles tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // DPub ARIA roles should be exposed via the xml-roles object attribute.
+ let dpub_attrs = [
+ "doc-abstract",
+ "doc-acknowledgments",
+ "doc-afterword",
+ "doc-appendix",
+ "doc-backlink",
+ "doc-biblioentry",
+ "doc-bibliography",
+ "doc-biblioref",
+ "doc-chapter",
+ "doc-colophon",
+ "doc-conclusion",
+ "doc-cover",
+ "doc-credit",
+ "doc-credits",
+ "doc-dedication",
+ "doc-endnote",
+ "doc-endnotes",
+ "doc-epigraph",
+ "doc-epilogue",
+ "doc-errata",
+ "doc-example",
+ "doc-footnote",
+ "doc-foreword",
+ "doc-glossary",
+ "doc-glossref",
+ "doc-index",
+ "doc-introduction",
+ "doc-noteref",
+ "doc-notice",
+ "doc-pagebreak",
+ "doc-pagelist",
+ "doc-part",
+ "doc-preface",
+ "doc-prologue",
+ "doc-pullquote",
+ "doc-qna",
+ "doc-subtitle",
+ "doc-tip",
+ "doc-toc",
+ ];
+ for (let attr of dpub_attrs) {
+ testAttrs(attr, {"xml-roles": attr}, true);
+ }
+ SimpleTest.finish();
+ }
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1343537"
+ title="implement ARIA DPUB extension">
+ Bug 1343537
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <div id="doc-abstract" role="doc-abstract">abstract</div>
+ <div id="doc-acknowledgments" role="doc-acknowledgments">acknowledgments</div>
+ <div id="doc-afterword" role="doc-afterword">afterword</div>
+ <div id="doc-appendix" role="doc-appendix">appendix</div>
+ <div id="doc-backlink" role="doc-backlink">backlink</div>
+ <div id="doc-biblioentry" role="doc-biblioentry">biblioentry</div>
+ <div id="doc-bibliography" role="doc-bibliography">bibliography</div>
+ <div id="doc-biblioref" role="doc-biblioref">biblioref</div>
+ <div id="doc-chapter" role="doc-chapter">chapter</div>
+ <div id="doc-colophon" role="doc-colophon">colophon</div>
+ <div id="doc-conclusion" role="doc-conclusion">conclusion</div>
+ <div id="doc-cover" role="doc-cover">cover</div>
+ <div id="doc-credit" role="doc-credit">credit</div>
+ <div id="doc-credits" role="doc-credits">credits</div>
+ <div id="doc-dedication" role="doc-dedication">dedication</div>
+ <div id="doc-endnote" role="doc-endnote">endnote</div>
+ <div id="doc-endnotes" role="doc-endnotes">endnotes</div>
+ <div id="doc-epigraph" role="doc-epigraph">epigraph</div>
+ <div id="doc-epilogue" role="doc-epilogue">epilogue</div>
+ <div id="doc-errata" role="doc-errata">errata</div>
+ <div id="doc-example" role="doc-example">example</div>
+ <div id="doc-footnote" role="doc-footnote">footnote</div>
+ <div id="doc-foreword" role="doc-foreword">foreword</div>
+ <div id="doc-glossary" role="doc-glossary">glossary</div>
+ <div id="doc-glossref" role="doc-glossref">glossref</div>
+ <div id="doc-index" role="doc-index">index</div>
+ <div id="doc-introduction" role="doc-introduction">introduction</div>
+ <div id="doc-noteref" role="doc-noteref">noteref</div>
+ <div id="doc-notice" role="doc-notice">notice</div>
+ <div id="doc-pagebreak" role="doc-pagebreak">pagebreak</div>
+ <div id="doc-pagelist" role="doc-pagelist">pagelist</div>
+ <div id="doc-part" role="doc-part">part</div>
+ <div id="doc-preface" role="doc-preface">preface</div>
+ <div id="doc-prologue" role="doc-prologue">prologue</div>
+ <div id="doc-pullquote" role="doc-pullquote">pullquote</div>
+ <div id="doc-qna" role="doc-qna">qna</div>
+ <div id="doc-subtitle" role="doc-subtitle">subtitle</div>
+ <div id="doc-tip" role="doc-tip">tip</div>
+ <div id="doc-toc" role="doc-toc">toc</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/attributes/test_graphics_aria_xml-roles.html b/accessible/tests/mochitest/attributes/test_graphics_aria_xml-roles.html
new file mode 100644
index 0000000000..45d5e2fa0b
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_graphics_aria_xml-roles.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>XML roles tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // Graphics ARIA roles should be exposed via the xml-roles object attribute.
+ let graphics_attrs = [
+ "graphics-document",
+ "graphics-object",
+ "graphics-symbol",
+ ];
+ for (let attr of graphics_attrs) {
+ testAttrs(attr, {"xml-roles": attr}, true);
+ }
+ SimpleTest.finish();
+ }
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432513"
+ title="implement ARIA Graphics roles">
+ Bug 1432513
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <div id="graphics-document" role="graphics-document">document</div>
+ <div id="graphics-object" role="graphics-object">object</div>
+ <div id="graphics-symbol" role="graphics-symbol">symbol</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/attributes/test_listbox.html b/accessible/tests/mochitest/attributes/test_listbox.html
new file mode 100644
index 0000000000..5489e74b74
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_listbox.html
@@ -0,0 +1,82 @@
+<html>
+
+<head>
+ <title>Listbox group attribute tests</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+ async function doTest() {
+ // First test the whole lot.
+ testGroupAttrs("a", 1, 6);
+ testGroupAttrs("b", 2, 6);
+ testGroupAttrs("c", 3, 6);
+ testGroupAttrs("d", 4, 6);
+ testGroupAttrs("e", 5, 6);
+ testGroupAttrs("f", 6, 6);
+ // Remove c, reducing the set to 5.
+ let listbox = getAccessible("listbox");
+ let updated = waitForEvent(EVENT_REORDER, listbox);
+ c.remove();
+ await updated;
+ testGroupAttrs("a", 1, 5);
+ testGroupAttrs("b", 2, 5);
+ testGroupAttrs("d", 3, 5);
+ testGroupAttrs("e", 4, 5);
+ testGroupAttrs("f", 5, 5);
+ // Now, remove the first element.
+ updated = waitForEvent(EVENT_REORDER, listbox);
+ a.remove();
+ await updated;
+ testGroupAttrs("b", 1, 4);
+ testGroupAttrs("d", 2, 4);
+ testGroupAttrs("e", 3, 4);
+ testGroupAttrs("f", 4, 4);
+ // Remove the last item.
+ updated = waitForEvent(EVENT_REORDER, listbox);
+ f.remove();
+ await updated;
+ testGroupAttrs("b", 1, 3);
+ testGroupAttrs("d", 2, 3);
+ testGroupAttrs("e", 3, 3);
+ // Finally, remove the middle item.
+ updated = waitForEvent(EVENT_REORDER, listbox);
+ d.remove();
+ await updated;
+ testGroupAttrs("b", 1, 2);
+ testGroupAttrs("e", 2, 2);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Group information updated after removal of list items, bug 1515186 -->
+ <div id="listbox" role="listbox">
+ <div id="a" role="option">Option a</div>
+ <div id="b" role="option">Option b</div>
+ <div id="c" role="option">Option c</div>
+ <div id="d" role="option">Option d</div>
+ <div id="e" role="option">Option e</div>
+ <div id="f" role="option">Option f</div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/attributes/test_obj.html b/accessible/tests/mochitest/attributes/test_obj.html
new file mode 100644
index 0000000000..fedf6a1b7b
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj.html
@@ -0,0 +1,292 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=475006
+https://bugzilla.mozilla.org/show_bug.cgi?id=391829
+https://bugzilla.mozilla.org/show_bug.cgi?id=581952
+https://bugzilla.mozilla.org/show_bug.cgi?id=558036
+-->
+<head>
+ <title>Group attributes tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // aria
+ testAttrs("atomic", {"atomic": "true", "container-atomic": "true"}, true);
+ testAttrs(getNode("atomic").firstChild, {"container-atomic": "true"}, true);
+ testAbsentAttrs("atomic_false", {"atomic": "false", "container-atomic": "false"});
+ testAbsentAttrs(getNode("atomic_false").firstChild, {"container-atomic": "false"});
+
+ testAttrs("autocomplete", {"autocomplete": "true"}, true);
+ testAttrs("checkbox", {"checkable": "true"}, true);
+ testAttrs("checkedCheckbox", {"checkable": "true"}, true);
+ testAbsentAttrs("checkedMenuitem", {"checkable": "true"});
+ testAttrs("checkedMenuitemCheckbox", {"checkable": "true"}, true);
+ testAttrs("checkedMenuitemRadio", {"checkable": "true"}, true);
+ testAttrs("checkedOption", {"checkable": "true"}, true);
+ testAttrs("checkedRadio", {"checkable": "true"}, true);
+ testAttrs("checkedTreeitem", {"checkable": "true"}, true);
+ testAttrs("dropeffect", {"dropeffect": "copy"}, true);
+ testAttrs("grabbed", {"grabbed": "true"}, true);
+ testAttrs("haspopupTrue", { "haspopup": "true" }, true);
+ testAbsentAttrs("haspopupFalse", { "haspopup": "false" });
+ testAbsentAttrs("haspopupEmpty", { "haspopup": "" });
+ testAttrs("haspopupDialog", { "haspopup": "dialog" }, true);
+ testAttrs("haspopupListbox", { "haspopup": "listbox" }, true);
+ testAttrs("haspopupMenu", { "haspopup": "menu" }, true);
+ testAttrs("haspopupTree", { "haspopup": "tree" }, true);
+ testAbsentAttrs("modal", {"modal": "true"});
+ testAttrs("sortAscending", {"sort": "ascending"}, true);
+ testAttrs("sortDescending", {"sort": "descending"}, true);
+ testAttrs("sortNone", {"sort": "none"}, true);
+ testAttrs("sortOther", {"sort": "other"}, true);
+ testAttrs("roledescr", {"roledescription": "spreadshit"}, true);
+ testAttrs("currentPage", {"current": "page"}, true);
+ testAttrs("currentStep", {"current": "step"}, true);
+ testAttrs("currentLocation", {"current": "location"}, true);
+ testAttrs("currentDate", {"current": "date"}, true);
+ testAttrs("currentTime", {"current": "time"}, true);
+ testAttrs("currentTrue", {"current": "true"}, true);
+ testAttrs("currentOther", {"current": "true"}, true);
+ testAbsentAttrs("currentFalse", {"current": "true"});
+ testAttrs("currentSpan", {"current": "page"}, true);
+
+ // live object attribute
+
+ // HTML
+ testAttrs("output", {"live": "polite"}, true);
+
+ // ARIA
+ testAttrs("live", {"live": "polite"}, true);
+ testAttrs("live2", {"live": "polite"}, true);
+ testAbsentAttrs("live3", {"live": ""});
+ if (MAC) {
+ testAttrs("alert", {"live": "assertive"}, true);
+ } else {
+ testAbsentAttrs("alert", {"live": "assertive"});
+ }
+ testAttrs("log", {"live": "polite"}, true);
+ testAttrs("logAssertive", {"live": "assertive"}, true);
+ testAttrs("marquee", {"live": "off"}, true);
+ testAttrs("status", {"live": "polite"}, true);
+ testAttrs("timer", {"live": "off"}, true);
+ testAbsentAttrs("tablist", {"live": "polite"});
+
+ // container-live object attribute
+ testAttrs("liveChild", {"container-live": "polite"}, true);
+ testAttrs("live2Child", {"container-live": "polite"}, true);
+ if (MAC) {
+ testAttrs("alertChild", {"container-live": "assertive"}, true);
+ } else {
+ testAbsentAttrs("alertChild", {"container-live": "assertive"});
+ }
+ testAttrs("logChild", {"container-live": "polite"}, true);
+ testAttrs("logAssertiveChild", {"container-live": "assertive"}, true);
+ testAttrs("marqueeChild", {"container-live": "off"}, true);
+ testAttrs("statusChild", {"container-live": "polite"}, true);
+ testAttrs("timerChild", {"container-live": "off"}, true);
+ testAbsentAttrs("tablistChild", {"container-live": "polite"});
+ testAttrs("containerLiveOutput", {"container-live": "polite"}, true);
+ testAttrs("containerLiveOutput1", {"container-live": "polite"}, true);
+ testAttrs("containerLiveOutput2", {"container-live": "polite"}, true);
+
+ // container-live-role object attribute
+ testAttrs("log", {"container-live-role": "log"}, true);
+ testAttrs("logAssertive", {"container-live-role": "log"}, true);
+ testAttrs("marquee", {"container-live-role": "marquee"}, true);
+ testAttrs("status", {"container-live-role": "status"}, true);
+ testAttrs("timer", {"container-live-role": "timer"}, true);
+ testAttrs("logChild", {"container-live-role": "log"}, true);
+ testAttrs("logAssertive", {"container-live-role": "log"}, true);
+ testAttrs("logAssertiveChild", {"container-live-role": "log"}, true);
+ testAttrs("marqueeChild", {"container-live-role": "marquee"}, true);
+ testAttrs("statusChild", {"container-live-role": "status"}, true);
+ testAttrs("timerChild", {"container-live-role": "timer"}, true);
+ testAbsentAttrs("tablistChild", {"container-live-role": "tablist"});
+
+ // absent aria-label and aria-labelledby object attribute
+ testAbsentAttrs("label", {"label": "foo"});
+ testAbsentAttrs("labelledby", {"labelledby": "label"});
+
+ // container that has no default live attribute
+ testAttrs("liveGroup", {"live": "polite"}, true);
+ testAttrs("liveGroupChild", {"container-live": "polite"}, true);
+ testAttrs("liveGroup", {"container-live-role": "group"}, true);
+ testAttrs("liveGroupChild", {"container-live-role": "group"}, true);
+
+ // text input type
+ testAbsentAttrs("button", { "text-input-type": "button"});
+ testAbsentAttrs("checkbox", { "text-input-type": "checkbox"});
+ testAbsentAttrs("radio", { "text-input-type": "radio"});
+ testAttrs("email", {"text-input-type": "email"}, true);
+ testAttrs("search", {"text-input-type": "search"}, true);
+ testAttrs("tel", {"text-input-type": "tel"}, true);
+ testAttrs("url", {"text-input-type": "url"}, true);
+ testAttrs("number", {"text-input-type": "number"}, true);
+
+ // ARIA
+ testAttrs("searchbox", {"text-input-type": "search"}, true);
+
+ // html
+ testAttrs("radio", {"checkable": "true"}, true);
+ testAttrs("checkbox", {"checkable": "true"}, true);
+ testAttrs("draggable", {"draggable": "true"}, true);
+ testAttrs("th1", { "abbr": "SS#" }, true);
+ testAttrs("th2", { "abbr": "SS#" }, true);
+ testAttrs("th2", { "axis": "social" }, true);
+
+ // don't barf on an empty abbr element.
+ testAbsentAttrs("th3", { "abbr": "" });
+
+ // application accessible
+ if (WIN) {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].
+ getService(Ci.nsIGfxInfo);
+ var attrs = {
+ "D2D": (gfxInfo.D2DEnabled ? "true" : "false"),
+ };
+ testAttrs(getApplicationAccessible(), attrs, false);
+ }
+
+ // no object attributes
+ testAbsentAttrs(getAccessible("listitem").firstChild, { "tag": "" });
+
+ // experimental aria
+ testAttrs("experimental", {"blah": "true"}, true);
+
+ // HTML5 aside element xml-roles
+ testAttrs("aside0", {"xml-roles": "note"}, true);
+ testAttrs("aside1", {"xml-roles": "group"}, true);
+ testAttrs("aside2", {"xml-roles": "complementary"}, true);
+
+ // non-standard data-at-shortcutkeys attribute:
+ testAttrs("shortcuts", {'data-at-shortcutkeys': '{"n":"New message","r":"Reply to message"}'}, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- container live -->
+ <output id="containerLiveOutput"><div id="containerLiveOutput1"><div id="containerLiveOutput2">Test</div></div></output>
+
+ <!-- aria -->
+ <div id="atomic" aria-atomic="true">live region</div>
+ <div id="atomic_false" aria-atomic="false">live region</div>
+ <div id="autocomplete" role="textbox" aria-autocomplete="true"></div>
+ <div id="checkbox" role="checkbox"></div>
+ <div id="checkedCheckbox" role="checkbox" aria-checked="true"></div>
+ <div id="checkedMenuitem" role="menuitem" aria-checked="true"></div>
+ <div id="checkedMenuitemCheckbox" role="menuitemcheckbox" aria-checked="true"></div>
+ <div id="checkedMenuitemRadio" role="menuitemradio" aria-checked="true"></div>
+ <div id="checkedOption" role="option" aria-checked="true"></div>
+ <div id="checkedRadio" role="radio" aria-checked="true"></div>
+ <div id="checkedTreeitem" role="treeitem" aria-checked="true"></div>
+ <div id="dropeffect" aria-dropeffect="copy"></div>
+ <div id="grabbed" aria-grabbed="true"></div>
+ <div id="haspopupTrue" aria-haspopup="true"></div>
+ <div id="haspopupFalse" aria-haspopup="false"></div>
+ <div id="haspopupEmpty" aria-haspopup=""></div>
+ <div id="haspopupDialog" aria-haspopup="dialog"></div>
+ <div id="haspopupListbox" aria-haspopup="listbox"></div>
+ <div id="haspopupMenu" aria-haspopup="menu"></div>
+ <div id="haspopupTree" aria-haspopup="tree"></div>
+ <div id="modal" aria-modal="true"></div>
+ <div id="sortAscending" role="columnheader" aria-sort="ascending"></div>
+ <div id="sortDescending" role="columnheader" aria-sort="descending"></div>
+ <div id="sortNone" role="columnheader" aria-sort="none"></div>
+ <div id="sortOther" role="columnheader" aria-sort="other"></div>
+ <div id="roledescr" aria-roledescription="spreadshit"></div>
+ <div id="currentPage" aria-current="page"></div>
+ <div id="currentStep" aria-current="step"></div>
+ <div id="currentLocation" aria-current="location"></div>
+ <div id="currentDate" aria-current="date"></div>
+ <div id="currentTime" aria-current="time"></div>
+ <div id="currentTrue" aria-current="true"></div>
+ <div id="currentOther" aria-current="other"></div>
+ <div id="currentFalse" aria-current="false"></div>
+
+ <!-- aria-current on a span which must create an accessible -->
+ <ol>
+ <li><a href="...">Page 1</a></li>
+ <li><a href="...">Page 2</a></li>
+ <li><span id="currentSpan" aria-current="page">This page</span></li>
+ </ol>
+
+ <!-- html -->
+ <output id="output"></output>
+
+ <!-- back to aria -->
+ <div id="live" aria-live="polite">excuse <div id="liveChild">me</div></div>
+ <div id="live2" role="marquee" aria-live="polite">excuse <div id="live2Child">me</div></div>
+ <div id="live3" role="region">excuse</div>
+ <div id="alert" role="alert">excuse <div id="alertChild">me</div></div>
+ <div id="log" role="log">excuse <div id="logChild">me</div></div>
+ <div id="logAssertive" role="log" aria-live="assertive">excuse <div id="logAssertiveChild">me</div></div>
+ <div id="marquee" role="marquee">excuse <div id="marqueeChild">me</div></div>
+ <div id="status" role="status">excuse <div id="statusChild">me</div></div>
+ <div id="tablist" role="tablist">tablist <div id="tablistChild">tab</div></div>
+ <div id="timer" role="timer">excuse <div id="timerChild">me</div></div>
+
+ <!-- aria-label[ledby] should not be an object attribute -->
+ <div id="label" role="checkbox" aria-label="foo"></div>
+ <div id="labelledby" role="checkbox" aria-labelledby="label"></div>
+
+ <!-- unusual live case -->
+ <div id="liveGroup" role="group" aria-live="polite">
+ excuse <div id="liveGroupChild">me</div>
+ </div>
+
+ <!-- text input type -->
+ <input id="button" type="button"/>
+ <input id="email" type="email"/>
+ <input id="search" type="search"/>
+ <input id="tel" type="tel"/>
+ <input id="url" type="url"/>
+ <input id="number" type="number"/>
+ <div id="searchbox" role="searchbox"></div>
+
+ <!-- html -->
+ <input id="radio" type="radio"/>
+ <input id="checkbox" type="checkbox"/>
+ <div id="draggable" draggable="true">Draggable div</div>
+ <table>
+ <tr>
+ <th id="th1"><abbr title="Social Security Number">SS#</abbr></th>
+ <th id="th2" abbr="SS#" axis="social">Social Security Number</th>
+ <th id="th3"><abbr></abbr></th>
+ </tr>
+ </table>
+
+ <ul>
+ <li id="listitem">item
+ </ul>
+
+ <!-- experimental aria -->
+ <div id="experimental" aria-blah="true">Fake beer</div>
+
+ <!-- HTML5 aside elements -->
+ <aside id="aside0" role="note">aside 0</aside>
+ <aside id="aside1" role="group">aside 1</aside>
+ <aside id="aside2">aside 2</aside>
+
+ <!-- Shortcuts for web applications -->
+ <div id="shortcuts" data-at-shortcutkeys='{"n":"New message","r":"Reply to message"}'></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/attributes/test_obj_css.html b/accessible/tests/mochitest/attributes/test_obj_css.html
new file mode 100644
index 0000000000..6c702ba5a6
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj_css.html
@@ -0,0 +1,225 @@
+<html>
+<head>
+ <title>CSS-like attributes tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ var gQueue = null;
+
+ function removeElm(aID) {
+ this.node = getNode(aID);
+ this.accessible = getAccessible(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.accessible),
+ ];
+
+ this.invoke = function removeElm_invoke() {
+ this.node.remove();
+ };
+
+ this.check = function removeElm_check() {
+ testAbsentCSSAttrs(this.accessible);
+ };
+
+ this.getID = function removeElm_getID() {
+ return "test CSS-based attributes on removed accessible";
+ };
+ }
+
+ function doTest() {
+ // CSS display
+ testCSSAttrs("display_block");
+ testCSSAttrs("display_inline");
+ testCSSAttrs("display_inline-block");
+ testCSSAttrs("display_list-item");
+ testCSSAttrs("display_table");
+ testCSSAttrs("display_inline-table");
+ testCSSAttrs("display_table-row-group");
+ testCSSAttrs("display_table-column");
+ testCSSAttrs("display_table-column-group");
+ testCSSAttrs("display_table-header-group");
+ testCSSAttrs("display_table-footer-group");
+ testCSSAttrs("display_table-row");
+ testCSSAttrs("display_table-cell");
+ testCSSAttrs("display_table-caption");
+
+ // CSS text-align
+ testCSSAttrs("text-align_left");
+ testCSSAttrs("text-align_right");
+ testCSSAttrs("text-align_center");
+ testCSSAttrs("text-align_justify");
+ testCSSAttrs("text-align_inherit");
+
+ // CSS text-indent
+ testCSSAttrs("text-indent_em");
+ testCSSAttrs("text-indent_ex");
+ testCSSAttrs("text-indent_in");
+ testCSSAttrs("text-indent_cm");
+ testCSSAttrs("text-indent_mm");
+ testCSSAttrs("text-indent_pt");
+ testCSSAttrs("text-indent_pc");
+ testCSSAttrs("text-indent_px");
+ testCSSAttrs("text-indent_percent");
+ testCSSAttrs("text-indent_inherit");
+
+ // CSS margin
+ testCSSAttrs("margin_em");
+ testCSSAttrs("margin_ex");
+ testCSSAttrs("margin_in");
+ testCSSAttrs("margin_cm");
+ testCSSAttrs("margin_mm");
+ testCSSAttrs("margin_pt");
+ testCSSAttrs("margin_pc");
+ testCSSAttrs("margin_px");
+ testCSSAttrs("margin_percent");
+ testCSSAttrs("margin_auto");
+ testCSSAttrs("margin_inherit");
+
+ testCSSAttrs("margin-left");
+ testCSSAttrs("margin-right");
+ testCSSAttrs("margin-top");
+ testCSSAttrs("margin-bottom");
+
+ // Elements
+ testCSSAttrs("span");
+ testCSSAttrs("div");
+ testCSSAttrs("p");
+ testCSSAttrs("input");
+ testCSSAttrs("table");
+ testCSSAttrs("tr");
+ testCSSAttrs("td");
+
+ // no CSS-based object attributes
+ testAbsentCSSAttrs(getAccessible("listitem").firstChild);
+
+ gQueue = new eventQueue();
+ gQueue.push(new removeElm("div"));
+ gQueue.invoke(); // SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=439566"
+ title="Include the css display property as an IAccessible2 object attribute">
+ Mozilla Bug 439566
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=460932"
+ title="text-indent and text-align should really be object attribute">
+ Mozilla Bug 460932
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=689540"
+ title="Expose IA2 margin- object attributes">
+ Mozilla Bug 689540
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=714579"
+ title="Don't use GetComputedStyle for object attribute calculation">
+ Mozilla Bug 714579
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=729831"
+ title="Don't expose CSS-based object attributes on not in tree accessible and accessible having no DOM element">
+ Mozilla Bug 729831
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="display_block" role="img"
+ style="display: block;">display: block</div>
+ <div id="display_inline" role="img"
+ style="display: inline;">display: inline</div>
+ <div id="display_inline-block" role="img"
+ style="display: inline-block;">display: inline-block</div>
+ <div id="display_list-item" role="img"
+ style="display: list-item;">display: list-item</div>
+ <div id="display_table" role="img"
+ style="display: table;">display: table</div>
+ <div id="display_inline-table" role="img"
+ style="display: inline-table;">display: inline-table</div>
+ <div id="display_table-row-group" role="img"
+ style="display: table-row-group;">display: table-row-group</div>
+ <div id="display_table-column" role="img"
+ style="display: table-column;">display: table-column</div>
+ <div id="display_table-column-group" role="img"
+ style="display: table-column-group;">display: table-column-group</div>
+ <div id="display_table-header-group" role="img"
+ style="display: table-header-group;">display: table-header-group</div>
+ <div id="display_table-footer-group" role="img"
+ style="display: table-footer-group;">display: table-footer-group</div>
+ <div id="display_table-row" role="img"
+ style="display: table-row;">display: table-row</div>
+ <div id="display_table-cell" role="img"
+ style="display: table-cell;">display: table-cell</div>
+ <div id="display_table-caption" role="img"
+ style="display: table-caption;">display: table-caption</div>
+
+ <p id="text-align_left" style="text-align: left;">text-align: left</p>
+ <p id="text-align_right" style="text-align: right;">text-align: right</p>
+ <p id="text-align_center" style="text-align: center;">text-align: center</p>
+ <p id="text-align_justify" style="text-align: justify;">text-align: justify</p>
+ <p id="text-align_inherit" style="text-align: inherit;">text-align: inherit</p>
+
+ <p id="text-indent_em" style="text-indent: 0.5em;">text-indent: 0.5em</p>
+ <p id="text-indent_ex" style="text-indent: 1ex;">text-indent: 1ex</p>
+ <p id="text-indent_in" style="text-indent: 0.5in;">text-indent: 0.5in</p>
+ <p id="text-indent_cm" style="text-indent: 2cm;">text-indent: 2cm</p>
+ <p id="text-indent_mm" style="text-indent: 10mm;">text-indent: 10mm</p>
+ <p id="text-indent_pt" style="text-indent: 30pt;">text-indent: 30pt</p>
+ <p id="text-indent_pc" style="text-indent: 2pc;">text-indent: 2pc</p>
+ <p id="text-indent_px" style="text-indent: 5px;">text-indent: 5px</p>
+ <p id="text-indent_percent" style="text-indent: 10%;">text-indent: 10%</p>
+ <p id="text-indent_inherit" style="text-indent: inherit;">text-indent: inherit</p>
+
+ <p id="margin_em" style="margin: 0.5em;">margin: 0.5em</p>
+ <p id="margin_ex" style="margin: 1ex;">margin: 1ex</p>
+ <p id="margin_in" style="margin: 0.5in;">margin: 0.5in</p>
+ <p id="margin_cm" style="margin: 2cm;">margin: 2cm</p>
+ <p id="margin_mm" style="margin: 10mm;">margin: 10mm</p>
+ <p id="margin_pt" style="margin: 30pt;">margin: 30pt</p>
+ <p id="margin_pc" style="margin: 2pc;">margin: 2pc</p>
+ <p id="margin_px" style="margin: 5px;">margin: 5px</p>
+ <p id="margin_percent" style="margin: 10%;">margin: 10%</p>
+ <p id="margin_auto" style="margin: auto;">margin: auto</p>
+ <p id="margin_inherit" style="margin: inherit;">margin: inherit</p>
+
+ <p id="margin-left" style="margin-left: 11px;">margin-left: 11px</p>
+ <p id="margin-right" style="margin-right: 21px;">margin-right</p>
+ <p id="margin-top" style="margin-top: 31px;">margin-top: 31px</p>
+ <p id="margin-bottom" style="margin-bottom: 41px;">margin-bottom: 41px</p>
+
+ <span id="span" role="group">It's span</span>
+ <div id="div">It's div</div>
+ <p id="p">It's paragraph"</p>
+ <input id="input"/>
+ <table id="table" style="margin: 2px; text-align: center; text-indent: 10%;">
+ <tr id="tr" role="group">
+ <td id="td">td</td>
+ </tr>
+ </table>
+
+ <ul>
+ <li id="listitem">item
+ </ul>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/attributes/test_obj_group.html b/accessible/tests/mochitest/attributes/test_obj_group.html
new file mode 100644
index 0000000000..f245d485c7
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj_group.html
@@ -0,0 +1,564 @@
+<html>
+
+<head>
+ <title>Group attributes tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML select with no size attribute.
+ testGroupAttrs("opt1-nosize", 1, 4);
+ testGroupAttrs("opt2-nosize", 2, 4);
+ testGroupAttrs("opt3-nosize", 3, 4);
+ testGroupAttrs("opt4-nosize", 4, 4);
+
+ // Container should have item count and not hierarchical
+ testGroupParentAttrs(getAccessible("opt1-nosize").parent, 4, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML select
+ testGroupAttrs("opt1", 1, 2);
+ testGroupAttrs("opt2", 2, 2);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML select with optgroup
+ testGroupAttrs("select2_opt3", 1, 2, 1);
+ testGroupAttrs("select2_opt4", 2, 2, 1);
+ testGroupAttrs("select2_opt1", 1, 2, 2);
+ testGroupAttrs("select2_opt2", 2, 2, 2);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML input@type="radio" within form
+ testGroupAttrs("radio1", 1, 2);
+ testGroupAttrs("radio2", 2, 2);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML input@type="radio" within document
+ testGroupAttrs("radio3", 1, 2);
+ testGroupAttrs("radio4", 2, 2);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Hidden HTML input@type="radio"
+ testGroupAttrs("radio5", 1, 1);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML ul/ol
+ testGroupAttrs("li1", 1, 3);
+ testGroupAttrs("li2", 2, 3);
+ testGroupAttrs("li3", 3, 3);
+
+ // ul should have item count and not hierarchical
+ testGroupParentAttrs("ul", 3, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML ul/ol (nested lists)
+
+ testGroupAttrs("li4", 1, 3, 1);
+ testGroupAttrs("li5", 2, 3, 1);
+ testGroupAttrs("li6", 3, 3, 1);
+ // ol with nested list should have 1st level item count and be hierarchical
+ testGroupParentAttrs("ol", 3, true);
+
+ testGroupAttrs("n_li4", 1, 3, 2);
+ testGroupAttrs("n_li5", 2, 3, 2);
+ testGroupAttrs("n_li6", 3, 3, 2);
+ // nested ol should have item count and be hierarchical
+ testGroupParentAttrs("ol_nested", 3, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA list
+ testGroupAttrs("li7", 1, 3);
+ testGroupAttrs("li8", 2, 3);
+ testGroupAttrs("li9", 3, 3);
+ // simple flat aria list
+ testGroupParentAttrs("aria-list_1", 3, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA list (nested lists: list -> listitem -> list -> listitem)
+ testGroupAttrs("li10", 1, 3, 1);
+ testGroupAttrs("li11", 2, 3, 1);
+ testGroupAttrs("li12", 3, 3, 1);
+ // aria list with nested list
+ testGroupParentAttrs("aria-list_2", 3, true);
+
+ testGroupAttrs("n_li10", 1, 3, 2);
+ testGroupAttrs("n_li11", 2, 3, 2);
+ testGroupAttrs("n_li12", 3, 3, 2);
+ // nested aria list.
+ testGroupParentAttrs("aria-list_2_1", 3, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA list (nested lists: list -> listitem -> group -> listitem)
+ testGroupAttrs("lgt_li1", 1, 2, 1);
+ testGroupAttrs("lgt_li1_nli1", 1, 2, 2);
+ testGroupAttrs("lgt_li1_nli2", 2, 2, 2);
+ testGroupAttrs("lgt_li2", 2, 2, 1);
+ testGroupAttrs("lgt_li2_nli1", 1, 2, 2);
+ testGroupAttrs("lgt_li2_nli2", 2, 2, 2);
+ // aria list with nested list
+ testGroupParentAttrs("aria-list_3", 2, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA menu (menuitem, separator, menuitemradio and menuitemcheckbox)
+ testGroupAttrs("menu_item1", 1, 2);
+ testGroupAttrs("menu_item2", 2, 2);
+ testGroupAttrs("menu_item1.1", 1, 2);
+ testGroupAttrs("menu_item1.2", 2, 2);
+ testGroupAttrs("menu_item1.3", 1, 3);
+ testGroupAttrs("menu_item1.4", 2, 3);
+ testGroupAttrs("menu_item1.5", 3, 3);
+ // menu bar item count
+ testGroupParentAttrs("menubar", 2, false);
+ // Bug 1492529. Menu should have total number of items 5 from both sets,
+ // but only has the first 2 item set.
+ todoAttr("menu", "child-item-count", "5");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA tab
+ testGroupAttrs("tab_1", 1, 3);
+ testGroupAttrs("tab_2", 2, 3);
+ testGroupAttrs("tab_3", 3, 3);
+ // tab list tab count
+ testGroupParentAttrs("tablist_1", 3, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA radio
+ testGroupAttrs("r1", 1, 3);
+ testGroupAttrs("r2", 2, 3);
+ testGroupAttrs("r3", 3, 3);
+ // explicit aria radio group
+ testGroupParentAttrs("rg1", 3, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA tree
+ testGroupAttrs("ti1", 1, 3, 1);
+ testGroupAttrs("ti2", 1, 2, 2);
+ testGroupAttrs("ti3", 2, 2, 2);
+ testGroupAttrs("ti4", 2, 3, 1);
+ testGroupAttrs("ti5", 1, 3, 2);
+ testGroupAttrs("ti6", 2, 3, 2);
+ testGroupAttrs("ti7", 3, 3, 2);
+ testGroupAttrs("ti8", 3, 3, 1);
+ testGroupParentAttrs("tree_1", 3, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA tree (tree -> treeitem -> group -> treeitem)
+ testGroupAttrs("tree2_ti1", 1, 2, 1);
+ testGroupAttrs("tree2_ti1a", 1, 2, 2);
+ testGroupAttrs("tree2_ti1b", 2, 2, 2);
+ testGroupAttrs("tree2_ti2", 2, 2, 1);
+ testGroupAttrs("tree2_ti2a", 1, 2, 2);
+ testGroupAttrs("tree2_ti2b", 2, 2, 2);
+ testGroupParentAttrs("tree_2", 2, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA tree (tree -> treeitem, group -> treeitem)
+ testGroupAttrs("tree3_ti1", 1, 2, 1);
+ testGroupAttrs("tree3_ti1a", 1, 2, 2);
+ testGroupAttrs("tree3_ti1b", 2, 2, 2);
+ testGroupAttrs("tree3_ti2", 2, 2, 1);
+ testGroupAttrs("tree3_ti2a", 1, 2, 2);
+ testGroupAttrs("tree3_ti2b", 2, 2, 2);
+ testGroupParentAttrs("tree_3", 2, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA grid
+ testGroupAttrs("grid_row1", 1, 2);
+ testAbsentAttrs("grid_cell1", {"posinset": "", "setsize": ""});
+ testAbsentAttrs("grid_cell2", {"posinset": "", "setsize": ""});
+
+ testGroupAttrs("grid_row2", 2, 2);
+ testAbsentAttrs("grid_cell3", {"posinset": "", "setsize": ""});
+ testAbsentAttrs("grid_cell4", {"posinset": "", "setsize": ""});
+ testGroupParentAttrs("grid", 2, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA treegrid
+ testGroupAttrs("treegrid_row1", 1, 2, 1);
+ testAbsentAttrs("treegrid_cell1", {"posinset": "", "setsize": ""});
+ testAbsentAttrs("treegrid_cell2", {"posinset": "", "setsize": ""});
+
+ testGroupAttrs("treegrid_row2", 1, 1, 2);
+ testAbsentAttrs("treegrid_cell3", {"posinset": "", "setsize": ""});
+ testAbsentAttrs("treegrid_cell4", {"posinset": "", "setsize": ""});
+
+ testGroupAttrs("treegrid_row3", 2, 2, 1);
+ testAbsentAttrs("treegrid_cell5", {"posinset": "", "setsize": ""});
+ testAbsentAttrs("treegrid_cell6", {"posinset": "", "setsize": ""});
+
+ testGroupParentAttrs("treegrid", 2, true);
+ // row child item count provided by parent grid's aria-colcount
+ testGroupParentAttrs("treegrid_row1", 4, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML headings
+ testGroupAttrs("h1", 0, 0, 1);
+ testGroupAttrs("h2", 0, 0, 2);
+ testGroupAttrs("h3", 0, 0, 3);
+ testGroupAttrs("h4", 0, 0, 4);
+ testGroupAttrs("h5", 0, 0, 5);
+ testGroupAttrs("h6", 0, 0, 6);
+ testGroupAttrs("ariaHeadingNoLevel", 0, 0, 2);
+ // No child item counts or "tree" flag for parent of headings
+ testAbsentAttrs("headings", {"child-item-count": "", "tree": ""});
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA combobox
+ testGroupAttrs("combo1_opt1", 1, 4);
+ testGroupAttrs("combo1_opt2", 2, 4);
+ testGroupAttrs("combo1_opt3", 3, 4);
+ testGroupAttrs("combo1_opt4", 4, 4);
+ testGroupParentAttrs("combo1", 4, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA table
+ testGroupAttrs("table_cell", 3, 4);
+ testGroupAttrs("table_row", 2, 2);
+
+ // grid child item count provided by aria-rowcount
+ testGroupParentAttrs("table", 2, false);
+ // row child item count provided by parent grid's aria-colcount
+ testGroupParentAttrs("table_row", 4, false);
+
+ // Attributes calculated even when row is wrapped in a div.
+ testGroupAttrs("wrapped_row_1", 1, 2);
+ testGroupAttrs("wrapped_row_2", 2, 2);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA list constructed by ARIA owns
+ testGroupAttrs("t1_li1", 1, 3);
+ testGroupAttrs("t1_li2", 2, 3);
+ testGroupAttrs("t1_li3", 3, 3);
+ testGroupParentAttrs("aria-list_4", 3, false);
+
+ // Test group attributes of ARIA comments
+ testGroupAttrs("comm_single_1", 1, 2, 1);
+ testGroupAttrs("comm_single_2", 2, 2, 1);
+ testGroupAttrs("comm_nested_1", 1, 3, 1);
+ testGroupAttrs("comm_nested_1_1", 1, 2, 2);
+ testGroupAttrs("comm_nested_1_2", 2, 2, 2);
+ testGroupAttrs("comm_nested_2", 2, 3, 1);
+ testGroupAttrs("comm_nested_2_1", 1, 1, 2);
+ testGroupAttrs("comm_nested_2_1_1", 1, 1, 3);
+ testGroupAttrs("comm_nested_3", 3, 3, 1);
+
+ // Test that group position information updates after deleting node.
+ testGroupAttrs("tree4_ti1", 1, 2, 1);
+ testGroupAttrs("tree4_ti2", 2, 2, 1);
+ testGroupParentAttrs("tree4", 2, true);
+
+ var tree4element = document.getElementById("tree4_ti1");
+ var tree4acc = getAccessible("tree4");
+ tree4element.remove();
+ waitForEvent(EVENT_REORDER, tree4acc, function() {
+ testGroupAttrs("tree4_ti2", 1, 1, 1);
+ testGroupParentAttrs("tree4", 1, true);
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=468418"
+ title="Expose level for nested lists in HTML">
+ Mozilla Bug 468418
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=844023"
+ title="group info might not be properly updated when flat trees mutate">
+ Bug 844023
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=864224"
+ title="Support nested ARIA listitems structured by role='group'">
+ Bug 864224
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=907682"
+ title=" HTML:option group position is not correct when select is collapsed">
+ Mozilla Bug 907682
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select>
+ <option id="opt1-nosize">option1</option>
+ <option id="opt2-nosize">option2</option>
+ <option id="opt3-nosize">option3</option>
+ <option id="opt4-nosize">option4</option>
+ </select>
+
+ <select size="4">
+ <option id="opt1">option1</option>
+ <option id="opt2">option2</option>
+ </select>
+
+ <select size="4">
+ <optgroup id="select2_optgroup" label="group">
+ <option id="select2_opt1">option1</option>
+ <option id="select2_opt2">option2</option>
+ </optgroup>
+ <option id="select2_opt3">option3</option>
+ <option id="select2_opt4">option4</option>
+ </select>
+
+ <form>
+ <input type="radio" id="radio1" name="group1"/>
+ <input type="radio" id="radio2" name="group1"/>
+ </form>
+
+ <input type="radio" id="radio3" name="group2"/>
+ <input type="radio" id="radio4" name="group2"/>
+
+ <ul id="ul">
+ <li id="li1">Oranges</li>
+ <li id="li2">Apples</li>
+ <li id="li3">Bananas</li>
+ </ul>
+
+ <ol id="ol">
+ <li id="li4">Oranges</li>
+ <li id="li5">Apples</li>
+ <li id="li6">Bananas
+ <ul id="ol_nested">
+ <li id="n_li4">Oranges</li>
+ <li id="n_li5">Apples</li>
+ <li id="n_li6">Bananas</li>
+ </ul>
+ </li>
+ </ol>
+
+ <span role="list" id="aria-list_1">
+ <span role="listitem" id="li7">Oranges</span>
+ <span role="listitem" id="li8">Apples</span>
+ <span role="listitem" id="li9">Bananas</span>
+ </span>
+
+ <span role="list" id="aria-list_2">
+ <span role="listitem" id="li10">Oranges</span>
+ <span role="listitem" id="li11">Apples</span>
+ <span role="listitem" id="li12">Bananas
+ <span role="list" id="aria-list_2_1">
+ <span role="listitem" id="n_li10">Oranges</span>
+ <span role="listitem" id="n_li11">Apples</span>
+ <span role="listitem" id="n_li12">Bananas</span>
+ </span>
+ </span>
+ </span>
+
+ <div role="list" id="aria-list_3">
+ <div role="listitem" id="lgt_li1">Item 1
+ <div role="group">
+ <div role="listitem" id="lgt_li1_nli1">Item 1A</div>
+ <div role="listitem" id="lgt_li1_nli2">Item 1B</div>
+ </div>
+ </div>
+ <div role="listitem" id="lgt_li2">Item 2
+ <div role="group">
+ <div role="listitem" id="lgt_li2_nli1">Item 2A</div>
+ <div role="listitem" id="lgt_li2_nli2">Item 2B</div>
+ </div>
+ </div>
+ </div>
+
+ <ul role="menubar" id="menubar">
+ <li role="menuitem" aria-haspopup="true" id="menu_item1">File
+ <ul role="menu" id="menu">
+ <li role="menuitem" id="menu_item1.1">New</li>
+ <li role="menuitem" id="menu_item1.2">Open…</li>
+ <li role="separator">-----</li>
+ <li role="menuitem" id="menu_item1.3">Item</li>
+ <li role="menuitemradio" id="menu_item1.4">Radio</li>
+ <li role="menuitemcheckbox" id="menu_item1.5">Checkbox</li>
+ </ul>
+ </li>
+ <li role="menuitem" aria-haspopup="false" id="menu_item2">Help</li>
+ </ul>
+
+ <ul id="tablist_1" role="tablist">
+ <li id="tab_1" role="tab">Crust</li>
+ <li id="tab_2" role="tab">Veges</li>
+ <li id="tab_3" role="tab">Carnivore</li>
+ </ul>
+
+ <ul id="rg1" role="radiogroup">
+ <li id="r1" role="radio" aria-checked="false">Thai</li>
+ <li id="r2" role="radio" aria-checked="false">Subway</li>
+ <li id="r3" role="radio" aria-checked="false">Jimmy Johns</li>
+ </ul>
+
+ <table role="tree" id="tree_1">
+ <tr role="presentation">
+ <td role="treeitem" aria-expanded="true" aria-level="1"
+ id="ti1">vegetables</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti2">cucumber</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti3">carrot</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-expanded="false" aria-level="1"
+ id="ti4">cars</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti5">mercedes</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti6">BMW</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti7">Audi</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="1" id="ti8">people</td>
+ </tr>
+ </table>
+
+ <ul role="tree" id="tree_2">
+ <li role="treeitem" id="tree2_ti1">Item 1
+ <ul role="group">
+ <li role="treeitem" id="tree2_ti1a">Item 1A</li>
+ <li role="treeitem" id="tree2_ti1b">Item 1B</li>
+ </ul>
+ </li>
+ <li role="treeitem" id="tree2_ti2">Item 2
+ <ul role="group">
+ <li role="treeitem" id="tree2_ti2a">Item 2A</li>
+ <li role="treeitem" id="tree2_ti2b">Item 2B</li>
+ </ul>
+ </li>
+ </div>
+
+ <div role="tree" id="tree_3">
+ <div role="treeitem" id="tree3_ti1">Item 1</div>
+ <div role="group">
+ <li role="treeitem" id="tree3_ti1a">Item 1A</li>
+ <li role="treeitem" id="tree3_ti1b">Item 1B</li>
+ </div>
+ <div role="treeitem" id="tree3_ti2">Item 2</div>
+ <div role="group">
+ <div role="treeitem" id="tree3_ti2a">Item 2A</div>
+ <div role="treeitem" id="tree3_ti2b">Item 2B</div>
+ </div>
+ </div>
+
+ <!-- IMPORTANT: Need to have no whitespace between elements in this tree. -->
+ <div role="tree" id="tree4"><div role="treeitem"
+ id="tree4_ti1">Item 1</div><div role="treeitem"
+ id="tree4_ti2">Item 2</div></div>
+
+ <table role="grid" id="grid">
+ <tr role="row" id="grid_row1">
+ <td role="gridcell" id="grid_cell1">cell1</td>
+ <td role="gridcell" id="grid_cell2">cell2</td>
+ </tr>
+ <tr role="row" id="grid_row2">
+ <td role="gridcell" id="grid_cell3">cell3</td>
+ <td role="gridcell" id="grid_cell4">cell4</td>
+ </tr>
+ </table>
+
+ <div role="treegrid" id="treegrid" aria-colcount="4">
+ <div role="row" aria-level="1" id="treegrid_row1">
+ <div role="gridcell" id="treegrid_cell1">cell1</div>
+ <div role="gridcell" id="treegrid_cell2">cell2</div>
+ </div>
+ <div role="row" aria-level="2" id="treegrid_row2">
+ <div role="gridcell" id="treegrid_cell3">cell1</div>
+ <div role="gridcell" id="treegrid_cell4">cell2</div>
+ </div>
+ <div role="row" id="treegrid_row3">
+ <div role="gridcell" id="treegrid_cell5">cell1</div>
+ <div role="gridcell" id="treegrid_cell6">cell2</div>
+ </div>
+ </div>
+
+ <div id="headings">
+ <h1 id="h1">heading1</h1>
+ <h2 id="h2">heading2</h2>
+ <h3 id="h3">heading3</h3>
+ <h4 id="h4">heading4</h4>
+ <h5 id="h5">heading5</h5>
+ <h6 id="h6">heading6</h6>
+ <div id="ariaHeadingNoLevel" role="heading">ariaHeadingNoLevel</div>
+ </div>
+
+ <ul id="combo1" role="combobox">Password
+ <li id="combo1_opt1" role="option">Xyzzy</li>
+ <li id="combo1_opt2" role="option">Plughs</li>
+ <li id="combo1_opt3" role="option">Shazaam</li>
+ <li id="combo1_opt4" role="option">JoeSentMe</li>
+ </ul>
+
+ <form>
+ <input type="radio" style="display: none;" name="group3">
+ <input type="radio" id="radio5" name="group3">
+ </form>
+
+ <div role="table" aria-colcount="4" aria-rowcount="2" id="table">
+ <div role="row" id="table_row" aria-rowindex="2">
+ <div role="cell" id="table_cell" aria-colindex="3">cell</div>
+ </div>
+ </div>
+
+ <div role="grid" aria-readonly="true">
+ <div tabindex="-1">
+ <div role="row" id="wrapped_row_1">
+ <div role="gridcell">cell content</div>
+ </div>
+ </div>
+ <div tabindex="-1">
+ <div role="row" id="wrapped_row_2">
+ <div role="gridcell">cell content</div>
+ </div>
+ </div>
+ </div>
+
+ <div role="list" aria-owns="t1_li1 t1_li2 t1_li3" id="aria-list_4">
+ <div role="listitem" id="t1_li2">Apples</div>
+ <div role="listitem" id="t1_li1">Oranges</div>
+ </div>
+ <div role="listitem" id="t1_li3">Bananas</div>
+
+ <!-- ARIA comments, 1 level, group pos and size calculation -->
+ <article>
+ <p id="comm_single_1" role="comment">Comment 1</p>
+ <p id="comm_single_2" role="comment">Comment 2</p>
+ </article>
+
+ <!-- Nested comments -->
+ <article>
+ <div id="comm_nested_1" role="comment"><p>Comment 1 level 1</p>
+ <div id="comm_nested_1_1" role="comment"><p>Comment 1 level 2</p></div>
+ <div id="comm_nested_1_2" role="comment"><p>Comment 2 level 2</p></div>
+ </div>
+ <div id="comm_nested_2" role="comment"><p>Comment 2 level 1</p>
+ <div id="comm_nested_2_1" role="comment"><p>Comment 3 level 2</p>
+ <div id="comm_nested_2_1_1" role="comment"><p>Comment 1 level 3</p></div>
+ </div>
+ </div>
+ <div id="comm_nested_3" role="comment"><p>Comment 3 level 1</p></div>
+ </article>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/attributes/test_obj_group.xhtml b/accessible/tests/mochitest/attributes/test_obj_group.xhtml
new file mode 100644
index 0000000000..0eda4b6f2d
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj_group.xhtml
@@ -0,0 +1,215 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Group Attributes ('level', 'setsize', 'posinset') Test.">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../attributes.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function openMenu(aID)
+ {
+ this.menuNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.menuNode)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ this.menuNode.open = true;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ testGroupAttrs("menu_item1.1", 1, 1);
+ testGroupAttrs("menu_item1.2", 1, 3);
+ testGroupAttrs("menu_item1.4", 2, 3);
+ testGroupAttrs("menu_item2", 3, 3);
+ }
+
+ this.getID = function openMenu_getID()
+ {
+ return "open menu " + prettyName(aID);
+ }
+ }
+
+ function openSubMenu(aID)
+ {
+ this.menuNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.menuNode)
+ ];
+
+ this.invoke = function openSubMenu_invoke()
+ {
+ this.menuNode.open = true;
+ }
+
+ this.finalCheck = function openSubMenu_finalCheck()
+ {
+ testGroupAttrs("menu_item2.1", 1, 2, 1);
+ testGroupAttrs("menu_item2.2", 2, 2, 1);
+ }
+
+ this.getID = function openSubMenu_getID()
+ {
+ return "open submenu " + prettyName(aID);
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // xul:listbox (bug 417317)
+ testGroupAttrs("listitem1", 1, 4);
+ testGroupAttrs("listitem2", 2, 4);
+ testGroupAttrs("listitem3", 3, 4);
+ testGroupAttrs("listitem4", 4, 4);
+
+ //////////////////////////////////////////////////////////////////////////
+ // xul:tab
+ testGroupAttrs("tab1", 1, 2);
+ testGroupAttrs("tab2", 2, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // xul:radio
+ testGroupAttrs("radio1", 1, 2);
+ testGroupAttrs("radio2", 2, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // xul:menulist
+ testGroupAttrs("menulist1.1", 1);
+ testGroupAttrs("menulist1.2", 2);
+ testGroupAttrs("menulist1.3", 3);
+ testGroupAttrs("menulist1.4", 4);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA menu (bug 441888)
+ testGroupAttrs("aria-menuitem", 1, 3);
+ testGroupAttrs("aria-menuitemcheckbox", 2, 3);
+ testGroupAttrs("aria-menuitemradio", 3, 3);
+ testGroupAttrs("aria-menuitem2", 1, 1);
+
+ //////////////////////////////////////////////////////////////////////////
+ // xul:menu (bug 443881)
+ gQueue = new eventQueue();
+ gQueue.push(new openMenu("menu_item1"));
+ gQueue.push(new openSubMenu("menu_item2"));
+ gQueue.invoke(); // SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=417317"
+ title="Certain types of LISTITEM accessibles no longer get attributes set like 'x of y', regression from fix for bug 389926">
+ Mozilla Bug 417317
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=443881"
+ title="take into account separators in xul menus when group attributes are calculating">
+ Mozilla Bug 443881
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=441888"
+ title="ARIA checked menu items are not included in the total list of menu items">
+ Mozilla Bug 441888
+ </a><br/>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <richlistbox>
+ <richlistitem id="listitem1"/>
+ <richlistitem id="listitem2"><label value="listitem2"/></richlistitem>
+ <richlistitem id="listitem3"/>
+ <richlistitem id="listitem4"><label value="listitem4"/></richlistitem>
+ </richlistbox>
+
+ <menubar>
+ <menu label="item1" id="menu_item1">
+ <menupopup>
+ <menuitem label="item1.1" id="menu_item1.1"/>
+ <menuseparator/>
+ <menuitem label="item1.2" id="menu_item1.2"/>
+ <menuitem label="item1.3" hidden="true"/>
+ <menuitem label="item1.4" id="menu_item1.4"/>
+ <menu label="item2" id="menu_item2">
+ <menupopup>
+ <menuitem label="item2.1" id="menu_item2.1"/>
+ <menuitem label="item2.2" id="menu_item2.2"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <tabbox>
+ <tabs>
+ <tab id="tab1" label="tab1"/>
+ <tab id="tab2" label="tab3"/>
+ </tabs>
+ <tabpanels>
+ <tabpanel/>
+ <tabpanel/>
+ </tabpanels>
+ </tabbox>
+
+ <radiogroup>
+ <radio id="radio1" label="radio1"/>
+ <radio id="radio2" label="radio2"/>
+ </radiogroup>
+
+ <menulist id="menulist1" label="Vehicle">
+ <menupopup>
+ <menuitem id="menulist1.1" label="Car"/>
+ <menuitem id="menulist1.2" label="Taxi"/>
+ <menuitem id="menulist1.3" label="Bus" selected="true"/>
+ <menuitem id="menulist1.4" label="Train"/>
+ </menupopup>
+ </menulist>
+
+ <vbox>
+ <description role="menuitem" id="aria-menuitem"
+ value="conventional menuitem"/>
+ <description role="menuitemcheckbox" id="aria-menuitemcheckbox"
+ value="conventional checkbox menuitem"/>
+ <description role="menuitem" hidden="true"/>
+ <description role="menuitemradio" id="aria-menuitemradio"
+ value="conventional radio menuitem"/>
+ <description role="separator"
+ value="conventional separator"/>
+ <description role="menuitem" id="aria-menuitem2"
+ value="conventional menuitem"/>
+ </vbox>
+
+ </vbox>
+ </hbox>
+</window>
+
diff --git a/accessible/tests/mochitest/attributes/test_obj_group_tree.xhtml b/accessible/tests/mochitest/attributes/test_obj_group_tree.xhtml
new file mode 100644
index 0000000000..287ee7989e
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj_group_tree.xhtml
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree attributes tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../attributes.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ var treeNode = getNode("tree");
+
+ var tree = getAccessible(treeNode);
+ var treeitem1 = tree.firstChild.nextSibling;
+ testGroupAttrs(treeitem1, 1, 4, 1);
+
+ var treeitem2 = treeitem1.nextSibling;
+ testGroupAttrs(treeitem2, 2, 4, 1);
+
+ var treeitem3 = treeitem2.nextSibling;
+ testGroupAttrs(treeitem3, 1, 2, 2);
+
+ var treeitem4 = treeitem3.nextSibling;
+ testGroupAttrs(treeitem4, 2, 2, 2);
+
+ var treeitem5 = treeitem4.nextSibling;
+ testGroupAttrs(treeitem5, 3, 4, 1);
+
+ var treeitem6 = treeitem5.nextSibling;
+ testGroupAttrs(treeitem6, 4, 4, 1);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTreeTreeView());
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/attributes/test_tag.html b/accessible/tests/mochitest/attributes/test_tag.html
new file mode 100644
index 0000000000..b57cc2dca8
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_tag.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML landmark tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // And some AT may look for this
+ testAttrs("nav", {"tag": "nav"}, true);
+ testAttrs("header", {"tag": "header"}, true);
+ testAttrs("footer", {"tag": "footer"}, true);
+ testAttrs("article", {"tag": "article"}, true);
+ testAttrs("aside", {"tag": "aside"}, true);
+ testAttrs("section", {"tag": "section"}, true);
+ testAttrs("main", {"tag": "article"}, true);
+ testAttrs("form", {"tag": "article"}, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Provide mappings for html5 <nav> <header> <footer> <article>"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=593368">
+ Bug 593368
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=613502"
+ title="Map <article> like we do aria role article">
+ Bug 613502
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=610650"
+ title="Change implementation of HTML5 landmark elements to conform">
+ Bug 610650
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=614310"
+ title="Map section to pane (like role=region)">
+ Mozilla Bug 614310
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=734982"
+ title="Map ARIA role FORM">
+ Bug 734982
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <nav id="nav">a nav</nav>
+ <header id="header">a header</header>
+ <footer id="footer">a footer</footer>
+ <aside id="aside">by the way I am an aside</aside>
+ <section id="section">a section</section>
+
+ <article id="article">an article</article>
+ <article id="main" role="main">a main area</article>
+ <article id="form" role="form">a form area</article>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/attributes/test_xml-roles.html b/accessible/tests/mochitest/attributes/test_xml-roles.html
new file mode 100644
index 0000000000..ff71f0da3a
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_xml-roles.html
@@ -0,0 +1,267 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>XML roles tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // Some AT may look for this
+ testAttrs("nav", {"xml-roles": "navigation"}, true);
+ testAttrs("header", {"xml-roles": "banner"}, true);
+ testAbsentAttrs("article_header", {"xml-roles": "banner"});
+ testAbsentAttrs("main_header", {"xml-roles": "banner"});
+ testAbsentAttrs("section_header", {"xml-roles": "banner"});
+ testAttrs("footer", {"xml-roles": "contentinfo"}, true);
+ testAbsentAttrs("article_footer", {"xml-roles": "contentinfo"});
+ testAbsentAttrs("main_footer", {"xml-roles": "contentinfo"});
+ testAbsentAttrs("section_footer", {"xml-roles": "contentinfo"});
+ testAttrs("aside", {"xml-roles": "complementary"}, true);
+ testAbsentAttrs("section", {"xml-roles": "region"});
+ testAttrs("main", {"xml-roles": "main"}, true); // // ARIA override
+ testAttrs("form", {"xml-roles": "form"}, true);
+ testAttrs("feed", {"xml-roles": "feed"}, true);
+ testAttrs("article", {"xml-roles": "article"}, true);
+ testAttrs("main_element", {"xml-roles": "main"}, true);
+ testAttrs("figure", {"xml-roles": "figure"}, true);
+
+ testAttrs("search", {"xml-roles": "searchbox"}, true);
+
+ testAttrs("code", {"xml-roles": "code"}, true);
+
+ testAttrs("open-1", {"xml-roles": "open-fence"}, true);
+ testAttrs("open-2", {"xml-roles": "open-fence"}, true);
+ testAttrs("open-3", {"xml-roles": "open-fence"}, true);
+ testAttrs("open-4", {"xml-roles": "open-fence"}, true);
+ testAttrs("open-5", {"xml-roles": "open-fence"}, true);
+ testAttrs("open-6", {"xml-roles": "open-fence"}, true);
+ testAttrs("open-7", {"xml-roles": "open-fence"}, true);
+
+ testAttrs("sep-1", {"xml-roles": "separator"}, true);
+ testAttrs("sep-2", {"xml-roles": "separator"}, true);
+ testAttrs("sep-3", {"xml-roles": "separator"}, true);
+ testAttrs("sep-4", {"xml-roles": "separator"}, true);
+ testAttrs("sep-5", {"xml-roles": "separator"}, true);
+ testAttrs("sep-6", {"xml-roles": "separator"}, true);
+ testAttrs("sep-7", {"xml-roles": "separator"}, true);
+
+ testAttrs("close-1", {"xml-roles": "close-fence"}, true);
+ testAttrs("close-2", {"xml-roles": "close-fence"}, true);
+ testAttrs("close-3", {"xml-roles": "close-fence"}, true);
+ testAttrs("close-4", {"xml-roles": "close-fence"}, true);
+ testAttrs("close-5", {"xml-roles": "close-fence"}, true);
+ testAttrs("close-6", {"xml-roles": "close-fence"}, true);
+ testAttrs("close-7", {"xml-roles": "close-fence"}, true);
+
+ testAttrs("num", {"xml-roles": "numerator"}, true);
+ testAttrs("den", {"xml-roles": "denominator"}, true);
+
+ testAttrs("sub-1", {"xml-roles": "subscript"}, true);
+ testAttrs("sub-2", {"xml-roles": "subscript"}, true);
+ testAttrs("sub-3", {"xml-roles": "subscript"}, true);
+ testAttrs("sup-1", {"xml-roles": "superscript"}, true);
+ testAttrs("sup-2", {"xml-roles": "superscript"}, true);
+ testAttrs("sup-3", {"xml-roles": "superscript"}, true);
+ testAttrs("sup-4", {"xml-roles": "superscript"}, true);
+ testAttrs("presub-1", {"xml-roles": "presubscript"}, true);
+ testAttrs("presub-2", {"xml-roles": "presubscript"}, true);
+ testAttrs("presup-1", {"xml-roles": "presuperscript"}, true);
+
+ testAttrs("under-1", {"xml-roles": "underscript"}, true);
+ testAttrs("under-2", {"xml-roles": "underscript"}, true);
+ testAttrs("over-1", {"xml-roles": "overscript"}, true);
+ testAttrs("over-2", {"xml-roles": "overscript"}, true);
+
+ testAttrs("root-index-1", {"xml-roles": "root-index"}, true);
+
+ testAttrs("base-1", {"xml-roles": "base"}, true);
+ testAttrs("base-2", {"xml-roles": "base"}, true);
+ testAttrs("base-3", {"xml-roles": "base"}, true);
+ testAttrs("base-4", {"xml-roles": "base"}, true);
+ testAttrs("base-5", {"xml-roles": "base"}, true);
+ testAttrs("base-6", {"xml-roles": "base"}, true);
+ testAttrs("base-7", {"xml-roles": "base"}, true);
+ testAttrs("base-8", {"xml-roles": "base"}, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Provide mappings for html5 <nav> <header> <footer> <article>"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=593368">
+ Bug 593368
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=613502"
+ title="Map <article> like we do aria role article">
+ Bug 613502
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=610650"
+ title="Change implementation of HTML5 landmark elements to conform">
+ Bug 610650
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=614310"
+ title="Map section to pane (like role=region)">
+ Mozilla Bug 614310
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=734982"
+ title="Map ARIA role FORM">
+ Bug 734982
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=761891"
+ title="HTML5 article element should expose xml-roles:article object attribute">
+ Bug 761891
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=849624"
+ title="modify HTML5 header and footer accessibility API mapping">
+ Bug 849624
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518"
+ title="ARIA 1.1: Support role 'searchbox'">
+ Bug 1121518
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1356049"
+ title="Map ARIA figure role">
+ Bug 1356049
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <nav id="nav">a nav</nav>
+ <header id="header">a header</header>
+ <footer id="footer">a footer</footer>
+ <article id="article_with_header_and_footer">
+ <header id="article_header">a header within an article</header>
+ <footer id="article_footer">a footer within an article</footer>
+ </article>
+ <main id="main_with_header_and_footer">
+ <header id="main_header">a header within a main</header>
+ <footer id="main_footer">a footer within a main</footer>
+ </main>
+ <section id="section_with_header_and_footer">
+ <header id="section_header">a header within an section</header>
+ <footer id="section_footer">a footer within an section</footer>
+ </section>
+ <aside id="aside">by the way I am an aside</aside>
+ <section id="section">a section</section>
+ <article id="main" role="main">a main area</article>
+ <article id="form" role="form">a form area</article>
+ <div id="feed" role="feed">a feed</div>
+ <article id="article">article</article>
+ <main id="main_element">another main area</main>
+ <div id="figure" role="figure">a figure</div>
+
+ <input id="search" type="search"/>
+
+ <div id="code" role="code"></div>
+
+ <!-- open-fence, separator, close-fence -->
+ <math><mo id="open-1">(</mo><mi>x</mi><mo id="sep-1">,</mo><mi>y</mi><mo id="close-1">)</mo></math>
+ <math><mrow><mo id="open-2">(</mo><mi>x</mi><mo id="sep-2">,</mo><mi>y</mi><mo id="close-2">)</mo></mrow></math>
+ <math><mstyle><mo id="open-3">(</mo><mi>x</mi><mo id="sep-3">,</mo><mi>y</mi><mo id="close-3">)</mo></mstyle></math>
+ <math><msqrt><mo id="open-4">(</mo><mi>x</mi><mo id="sep-4">,</mo><mi>y</mi><mo id="close-4">)</mo></msqrt></math>
+ <math><menclose><mo id="open-5">(</mo><mi>x</mi><mo id="sep-5">,</mo><mi>y</mi><mo id="close-5">)</mo></menclose></math>
+ <math><merror><mo id="open-6">(</mo><mi>x</mi><mo id="sep-6">,</mo><mi>y</mi><mo id="close-6">)</mo></merror></math>
+ <math><mtable><mtr><mtd><mo id="open-7">(</mo><mi>x</mi><mo id="sep-7">,</mo><mi>y</mi><mo id="close-7">)</mo></mtd></mtr></mtable></math>
+
+ <!-- numerator, denominator -->
+ <math>
+ <mfrac>
+ <mi id="num">a</mi>
+ <mi id="den">b</mi>
+ </mfrac>
+ </math>
+
+ <!-- subscript, superscript, presubscript, presuperscript -->
+ <math>
+ <msub>
+ <mi id="base-1">a</mi>
+ <mi id="sub-1">b</mi>
+ </msub>
+ </math>
+ <math>
+ <msup>
+ <mi id="base-2">a</mi>
+ <mi id="sup-1">b</mi>
+ </msup>
+ </math>
+ <math>
+ <msubsup>
+ <mi id="base-3">a</mi>
+ <mi id="sub-2">b</mi>
+ <mi id="sup-2">c</mi>
+ </msubsup>
+ </math>
+ <math>
+ <mmultiscripts>
+ <mi id="base-4">a</mi>
+ <mi id="sub-3">b</mi>
+ <mi id="sup-3">c</mi>
+ <none/>
+ <mi id="sup-4">d</mi>
+ <mprescripts/>
+ <mi id="presub-1">e</mi>
+ <none/>
+ <mi id="presub-2">f</mi>
+ <mi id="presup-1">g</mi>
+ </mmultiscripts>
+ </math>
+
+ <!-- underscript, overscript -->
+ <math>
+ <munder>
+ <mi id="base-5">a</mi>
+ <mi id="under-1">b</mi>
+ </munder>
+ </math>
+ <math>
+ <mover>
+ <mi id="base-6">a</mi>
+ <mi id="over-1">b</mi>
+ </mover>
+ </math>
+ <math>
+ <munderover>
+ <mi id="base-7">a</mi>
+ <mi id="under-2">b</mi>
+ <mi id="over-2">c</mi>
+ </munderover>
+ </math>
+
+ <!-- root-index -->
+ <math>
+ <mroot>
+ <mi id="base-8">a</mi>
+ <mi id="root-index-1">b</mi>
+ </mroot>
+ </math>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/autocomplete.js b/accessible/tests/mochitest/autocomplete.js
new file mode 100644
index 0000000000..bd5458c51d
--- /dev/null
+++ b/accessible/tests/mochitest/autocomplete.js
@@ -0,0 +1,198 @@
+const nsISupports = Ci.nsISupports;
+const nsIAutoCompleteResult = Ci.nsIAutoCompleteResult;
+const nsIAutoCompleteSearch = Ci.nsIAutoCompleteSearch;
+const nsIFactory = Ci.nsIFactory;
+const nsIUUIDGenerator = Ci.nsIUUIDGenerator;
+const nsIComponentRegistrar = Ci.nsIComponentRegistrar;
+
+var gDefaultAutoCompleteSearch = null;
+
+/**
+ * Register 'test-a11y-search' AutoCompleteSearch.
+ *
+ * @param aValues [in] set of possible results values
+ * @param aComments [in] set of possible results descriptions
+ */
+function initAutoComplete(aValues, aComments) {
+ var allResults = new ResultsHeap(aValues, aComments);
+ gDefaultAutoCompleteSearch = new AutoCompleteSearch(
+ "test-a11y-search",
+ allResults
+ );
+ registerAutoCompleteSearch(
+ gDefaultAutoCompleteSearch,
+ "Accessibility Test AutoCompleteSearch"
+ );
+}
+
+/**
+ * Unregister 'test-a11y-search' AutoCompleteSearch.
+ */
+function shutdownAutoComplete() {
+ unregisterAutoCompleteSearch(gDefaultAutoCompleteSearch);
+ gDefaultAutoCompleteSearch.cid = null;
+ gDefaultAutoCompleteSearch = null;
+}
+
+/**
+ * Register the given AutoCompleteSearch.
+ *
+ * @param aSearch [in] AutoCompleteSearch object
+ * @param aDescription [in] description of the search object
+ */
+function registerAutoCompleteSearch(aSearch, aDescription) {
+ var name = "@mozilla.org/autocomplete/search;1?name=" + aSearch.name;
+
+ var uuidGenerator =
+ Cc["@mozilla.org/uuid-generator;1"].getService(nsIUUIDGenerator);
+ var cid = uuidGenerator.generateUUID();
+
+ var componentManager = Components.manager.QueryInterface(
+ nsIComponentRegistrar
+ );
+ componentManager.registerFactory(cid, aDescription, name, aSearch);
+
+ // Keep the id on the object so we can unregister later.
+ aSearch.cid = cid;
+}
+
+/**
+ * Unregister the given AutoCompleteSearch.
+ */
+function unregisterAutoCompleteSearch(aSearch) {
+ var componentManager = Components.manager.QueryInterface(
+ nsIComponentRegistrar
+ );
+ componentManager.unregisterFactory(aSearch.cid, aSearch);
+}
+
+/**
+ * A container to keep all possible results of autocomplete search.
+ */
+function ResultsHeap(aValues, aComments) {
+ this.values = aValues;
+ this.comments = aComments;
+}
+
+ResultsHeap.prototype = {
+ constructor: ResultsHeap,
+
+ /**
+ * Return AutoCompleteResult for the given search string.
+ */
+ getAutoCompleteResultFor(aSearchString) {
+ var values = [],
+ comments = [];
+ for (var idx = 0; idx < this.values.length; idx++) {
+ if (this.values[idx].includes(aSearchString)) {
+ values.push(this.values[idx]);
+ comments.push(this.comments[idx]);
+ }
+ }
+ return new AutoCompleteResult(values, comments);
+ },
+};
+
+/**
+ * nsIAutoCompleteSearch implementation.
+ *
+ * @param aName [in] the name of autocomplete search
+ * @param aAllResults [in] ResultsHeap object
+ */
+function AutoCompleteSearch(aName, aAllResults) {
+ this.name = aName;
+ this.allResults = aAllResults;
+}
+
+AutoCompleteSearch.prototype = {
+ constructor: AutoCompleteSearch,
+
+ // nsIAutoCompleteSearch implementation
+ startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) {
+ var result = this.allResults.getAutoCompleteResultFor(aSearchString);
+ aListener.onSearchResult(this, result);
+ },
+
+ stopSearch() {},
+
+ // nsISupports implementation
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIFactory",
+ "nsIAutoCompleteSearch",
+ ]),
+
+ // nsIFactory implementation
+ createInstance(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // Search name. Used by AutoCompleteController.
+ name: null,
+
+ // Results heap.
+ allResults: null,
+};
+
+/**
+ * nsIAutoCompleteResult implementation.
+ */
+function AutoCompleteResult(aValues, aComments) {
+ this.values = aValues;
+ this.comments = aComments;
+
+ if (this.values.length) {
+ this.searchResult = nsIAutoCompleteResult.RESULT_SUCCESS;
+ } else {
+ this.searchResult = nsIAutoCompleteResult.NOMATCH;
+ }
+}
+
+AutoCompleteResult.prototype = {
+ constructor: AutoCompleteResult,
+
+ searchString: "",
+ searchResult: null,
+
+ defaultIndex: 0,
+
+ get matchCount() {
+ return this.values.length;
+ },
+
+ getValueAt(aIndex) {
+ return this.values[aIndex];
+ },
+
+ getLabelAt(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ getCommentAt(aIndex) {
+ return this.comments[aIndex];
+ },
+
+ getStyleAt(aIndex) {
+ return null;
+ },
+
+ getImageAt(aIndex) {
+ return "";
+ },
+
+ getFinalCompleteValueAt(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ isRemovableAt(aRowIndex) {
+ return true;
+ },
+
+ removeValueAt(aRowIndex) {},
+
+ // nsISupports implementation
+ QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteResult"]),
+
+ // Data
+ values: null,
+ comments: null,
+};
diff --git a/accessible/tests/mochitest/bounds/a11y.ini b/accessible/tests/mochitest/bounds/a11y.ini
new file mode 100644
index 0000000000..e9f8a3d4eb
--- /dev/null
+++ b/accessible/tests/mochitest/bounds/a11y.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_list.html]
diff --git a/accessible/tests/mochitest/bounds/test_list.html b/accessible/tests/mochitest/bounds/test_list.html
new file mode 100644
index 0000000000..7e5b75868d
--- /dev/null
+++ b/accessible/tests/mochitest/bounds/test_list.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Accessible boundaries when page is zoomed</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // Inside list
+ var li = getAccessible("insidelist_item");
+ testBounds(li);
+
+ var [xLI, yLI, widthLI, heightLI] = getBounds(li);
+ var bullet = li.firstChild;
+ var [x, y, width, height] = getBounds(bullet);
+ is(x, xLI,
+ "Bullet x should match to list item x");
+ ok(y >= yLI,
+ "Bullet y= " + y + " should be not less than list item y=" + yLI);
+ ok(width < widthLI,
+ "Bullet width should be lesser list item width");
+ ok(height <= heightLI,
+ "Bullet height= " + height + " should be not greater than list item height=" + heightLI);
+
+ // Outside list
+ li = getAccessible("outsidelist_item");
+ var [xLIElm, yLIElm, widthLIElm, heightLIElm] = getBoundsForDOMElm(li);
+ [xLI, yLI, widthLI, heightLI] = getBounds(li);
+
+ ok(xLI < xLIElm,
+ "Outside list item x=" + xLI + " should be lesser than list item element x=" + xLIElm);
+ is(yLI, yLIElm,
+ "Outside list item y should match to list item element y");
+ ok(widthLI > widthLIElm,
+ "Outside list item width=" + widthLI + " should be greater than list item element width=" + widthLIElm);
+ ok(heightLI >= Math.trunc(heightLIElm),
+ "Outside list item height=" + heightLI + " should not be less than list item element height=" + heightLIElm);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=754627"
+ title="GetBounds on bullet return wrong values">
+ Mozilla Bug 754627
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ul style="list-style-position: inside;">
+ <li id="insidelist_item">item</li>
+ </ul>
+
+ <ul style="list-style-position: outside;">
+ <li id="outsidelist_item">item</li>
+ </ul>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/browser.js b/accessible/tests/mochitest/browser.js
new file mode 100644
index 0000000000..b08214cfa8
--- /dev/null
+++ b/accessible/tests/mochitest/browser.js
@@ -0,0 +1,156 @@
+/* import-globals-from common.js */
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+/**
+ * Load the browser with the given url and then invokes the given function.
+ */
+function openBrowserWindow(aFunc, aURL, aRect) {
+ gBrowserContext.testFunc = aFunc;
+ gBrowserContext.startURL = aURL;
+ gBrowserContext.browserRect = aRect;
+
+ addLoadEvent(openBrowserWindowIntl);
+}
+
+/**
+ * Close the browser window.
+ */
+function closeBrowserWindow() {
+ gBrowserContext.browserWnd.close();
+}
+
+/**
+ * Return the browser window object.
+ */
+function browserWindow() {
+ return gBrowserContext.browserWnd;
+}
+
+/**
+ * Return the document of the browser window.
+ */
+function browserDocument() {
+ return browserWindow().document;
+}
+
+/**
+ * Return tab browser object.
+ */
+function tabBrowser() {
+ return browserWindow().gBrowser;
+}
+
+/**
+ * Return browser element of the current tab.
+ */
+function currentBrowser() {
+ return tabBrowser().selectedBrowser;
+}
+
+/**
+ * Return DOM document of the current tab.
+ */
+function currentTabDocument() {
+ return currentBrowser().contentDocument;
+}
+
+/**
+ * Return window of the current tab.
+ */
+function currentTabWindow() {
+ return currentTabDocument().defaultView;
+}
+
+/**
+ * Return browser element of the tab at the given index.
+ */
+function browserAt(aIndex) {
+ return tabBrowser().getBrowserAtIndex(aIndex);
+}
+
+/**
+ * Return DOM document of the tab at the given index.
+ */
+function tabDocumentAt(aIndex) {
+ return browserAt(aIndex).contentDocument;
+}
+
+/**
+ * Return input element of address bar.
+ */
+function urlbarInput() {
+ return browserWindow().document.getElementById("urlbar").inputField;
+}
+
+/**
+ * Return reload button.
+ */
+function reloadButton() {
+ return browserWindow().document.getElementById("urlbar-reload-button");
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// private section
+
+var gBrowserContext = {
+ browserWnd: null,
+ testFunc: null,
+ startURL: "",
+};
+
+function openBrowserWindowIntl() {
+ var params = "chrome,all,dialog=no,non-remote";
+ var rect = gBrowserContext.browserRect;
+ if (rect) {
+ if ("left" in rect) {
+ params += ",left=" + rect.left;
+ }
+ if ("top" in rect) {
+ params += ",top=" + rect.top;
+ }
+ if ("width" in rect) {
+ params += ",width=" + rect.width;
+ }
+ if ("height" in rect) {
+ params += ",height=" + rect.height;
+ }
+ }
+
+ gBrowserContext.browserWnd =
+ window.browsingContext.topChromeWindow.openDialog(
+ AppConstants.BROWSER_CHROME_URL,
+ "_blank",
+ params,
+ gBrowserContext.startURL || "data:text/html,<html></html>"
+ );
+
+ whenDelayedStartupFinished(browserWindow(), function () {
+ addA11yLoadEvent(startBrowserTests, browserWindow());
+ });
+}
+
+function startBrowserTests() {
+ if (gBrowserContext.startURL) {
+ // Make sure the window is the one loading our URL.
+ if (currentBrowser().contentWindow.location != gBrowserContext.startURL) {
+ setTimeout(startBrowserTests, 0);
+ return;
+ }
+ // wait for load
+ addA11yLoadEvent(gBrowserContext.testFunc, currentBrowser().contentWindow);
+ } else {
+ gBrowserContext.testFunc();
+ }
+}
+
+function whenDelayedStartupFinished(aWindow, aCallback) {
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ setTimeout(aCallback, 0);
+ }
+ }, "browser-delayed-startup-finished");
+}
diff --git a/accessible/tests/mochitest/common.js b/accessible/tests/mochitest/common.js
new file mode 100644
index 0000000000..1d42dea107
--- /dev/null
+++ b/accessible/tests/mochitest/common.js
@@ -0,0 +1,1046 @@
+// This file has circular dependencies that may require other files. Rather
+// than use import-globals-from, we list the globals individually here to save
+// confusing ESLint.
+// actions.js
+/* globals testActionNames */
+// attributes.js
+/* globals testAttrs, testAbsentAttrs, testTextAttrs */
+// relations.js
+/* globals testRelation */
+// role.js
+/* globals isRole */
+// state.js
+/* globals testStates */
+
+// //////////////////////////////////////////////////////////////////////////////
+// Interfaces
+
+const nsIAccessibilityService = Ci.nsIAccessibilityService;
+
+const nsIAccessibleEvent = Ci.nsIAccessibleEvent;
+const nsIAccessibleStateChangeEvent = Ci.nsIAccessibleStateChangeEvent;
+const nsIAccessibleCaretMoveEvent = Ci.nsIAccessibleCaretMoveEvent;
+const nsIAccessibleScrollingEvent = Ci.nsIAccessibleScrollingEvent;
+const nsIAccessibleTextChangeEvent = Ci.nsIAccessibleTextChangeEvent;
+const nsIAccessibleTextSelectionChangeEvent =
+ Ci.nsIAccessibleTextSelectionChangeEvent;
+const nsIAccessibleVirtualCursorChangeEvent =
+ Ci.nsIAccessibleVirtualCursorChangeEvent;
+const nsIAccessibleObjectAttributeChangedEvent =
+ Ci.nsIAccessibleObjectAttributeChangedEvent;
+const nsIAccessibleAnnouncementEvent = Ci.nsIAccessibleAnnouncementEvent;
+
+const nsIAccessibleStates = Ci.nsIAccessibleStates;
+const nsIAccessibleRole = Ci.nsIAccessibleRole;
+const nsIAccessibleScrollType = Ci.nsIAccessibleScrollType;
+const nsIAccessibleCoordinateType = Ci.nsIAccessibleCoordinateType;
+
+const nsIAccessibleRelation = Ci.nsIAccessibleRelation;
+const nsIAccessibleTextRange = Ci.nsIAccessibleTextRange;
+
+const nsIAccessible = Ci.nsIAccessible;
+
+const nsIAccessibleDocument = Ci.nsIAccessibleDocument;
+const nsIAccessibleApplication = Ci.nsIAccessibleApplication;
+
+const nsIAccessibleText = Ci.nsIAccessibleText;
+const nsIAccessibleEditableText = Ci.nsIAccessibleEditableText;
+
+const nsIAccessibleHyperLink = Ci.nsIAccessibleHyperLink;
+const nsIAccessibleHyperText = Ci.nsIAccessibleHyperText;
+
+const nsIAccessibleImage = Ci.nsIAccessibleImage;
+const nsIAccessiblePivot = Ci.nsIAccessiblePivot;
+const nsIAccessibleSelectable = Ci.nsIAccessibleSelectable;
+const nsIAccessibleTable = Ci.nsIAccessibleTable;
+const nsIAccessibleTableCell = Ci.nsIAccessibleTableCell;
+const nsIAccessibleTraversalRule = Ci.nsIAccessibleTraversalRule;
+const nsIAccessibleValue = Ci.nsIAccessibleValue;
+
+const nsIObserverService = Ci.nsIObserverService;
+
+const nsIDOMWindow = Ci.nsIDOMWindow;
+
+const nsIPropertyElement = Ci.nsIPropertyElement;
+
+// //////////////////////////////////////////////////////////////////////////////
+// OS detect
+
+const MAC = navigator.platform.includes("Mac");
+const LINUX = navigator.platform.includes("Linux");
+const SOLARIS = navigator.platform.includes("SunOS");
+const WIN = navigator.platform.includes("Win");
+
+// //////////////////////////////////////////////////////////////////////////////
+// Application detect
+// Firefox is assumed by default.
+
+const SEAMONKEY = navigator.userAgent.match(/ SeaMonkey\//);
+
+// //////////////////////////////////////////////////////////////////////////////
+// Accessible general
+
+const STATE_BUSY = nsIAccessibleStates.STATE_BUSY;
+
+const SCROLL_TYPE_TOP_EDGE = nsIAccessibleScrollType.SCROLL_TYPE_TOP_EDGE;
+const SCROLL_TYPE_ANYWHERE = nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE;
+
+const COORDTYPE_SCREEN_RELATIVE =
+ nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE;
+const COORDTYPE_WINDOW_RELATIVE =
+ nsIAccessibleCoordinateType.COORDTYPE_WINDOW_RELATIVE;
+const COORDTYPE_PARENT_RELATIVE =
+ nsIAccessibleCoordinateType.COORDTYPE_PARENT_RELATIVE;
+
+const kEmbedChar = String.fromCharCode(0xfffc);
+
+const kDiscBulletChar = String.fromCharCode(0x2022);
+const kDiscBulletText = kDiscBulletChar + " ";
+const kCircleBulletText = String.fromCharCode(0x25e6) + " ";
+const kSquareBulletText = String.fromCharCode(0x25aa) + " ";
+
+const MAX_TRIM_LENGTH = 100;
+
+/**
+ * Services to determine if e10s is enabled.
+ */
+
+/**
+ * nsIAccessibilityService service.
+ */
+var gAccService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ nsIAccessibilityService
+);
+
+/**
+ * Enable/disable logging.
+ */
+function enableLogging(aModules) {
+ gAccService.setLogging(aModules);
+}
+function disableLogging() {
+ gAccService.setLogging("");
+}
+function isLogged(aModule) {
+ return gAccService.isLogged(aModule);
+}
+
+/**
+ * Dumps the accessible tree into console.
+ */
+function dumpTree(aId, aMsg) {
+ function dumpTreeIntl(acc, indent) {
+ dump(indent + prettyName(acc) + "\n");
+
+ var children = acc.children;
+ for (var i = 0; i < children.length; i++) {
+ var child = children.queryElementAt(i, nsIAccessible);
+ dumpTreeIntl(child, indent + " ");
+ }
+ }
+
+ function dumpDOMTreeIntl(node, indent) {
+ dump(indent + prettyName(node) + "\n");
+
+ var children = node.childNodes;
+ for (var i = 0; i < children.length; i++) {
+ var child = children.item(i);
+ dumpDOMTreeIntl(child, indent + " ");
+ }
+ }
+
+ dump(aMsg + "\n");
+ var root = getAccessible(aId);
+ dumpTreeIntl(root, " ");
+
+ dump("DOM tree:\n");
+ dumpDOMTreeIntl(getNode(aId), " ");
+}
+
+/**
+ * Invokes the given function when document is loaded and focused. Preferable
+ * to mochitests 'addLoadEvent' function -- additionally ensures state of the
+ * document accessible is not busy.
+ *
+ * @param aFunc the function to invoke
+ */
+function addA11yLoadEvent(aFunc, aWindow) {
+ function waitForDocLoad() {
+ window.setTimeout(function () {
+ var targetDocument = aWindow ? aWindow.document : document;
+ var accDoc = getAccessible(targetDocument);
+ var state = {};
+ accDoc.getState(state, {});
+ if (state.value & STATE_BUSY) {
+ waitForDocLoad();
+ return;
+ }
+
+ window.setTimeout(aFunc, 0);
+ }, 0);
+ }
+
+ if (
+ aWindow &&
+ aWindow.document.activeElement &&
+ aWindow.document.activeElement.localName == "browser"
+ ) {
+ waitForDocLoad();
+ } else {
+ SimpleTest.waitForFocus(waitForDocLoad, aWindow);
+ }
+}
+
+/**
+ * Analogy of SimpleTest.is function used to compare objects.
+ */
+function isObject(aObj, aExpectedObj, aMsg) {
+ if (aObj == aExpectedObj) {
+ ok(true, aMsg);
+ return;
+ }
+
+ ok(
+ false,
+ aMsg +
+ " - got '" +
+ prettyName(aObj) +
+ "', expected '" +
+ prettyName(aExpectedObj) +
+ "'"
+ );
+}
+
+/**
+ * is() function checking the expected value is within the range.
+ */
+function isWithin(aExpected, aGot, aWithin, aMsg) {
+ if (Math.abs(aGot - aExpected) <= aWithin) {
+ ok(true, `${aMsg} - Got ${aGot}`);
+ } else {
+ ok(
+ false,
+ `${aMsg} - Got ${aGot}, expected ${aExpected} with error of ${aWithin}`
+ );
+ }
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Helpers for getting DOM node/accessible
+
+/**
+ * Return the DOM node by identifier (may be accessible, DOM node or ID).
+ */
+function getNode(aAccOrNodeOrID, aDocument) {
+ if (!aAccOrNodeOrID) {
+ return null;
+ }
+
+ if (Node.isInstance(aAccOrNodeOrID)) {
+ return aAccOrNodeOrID;
+ }
+
+ if (aAccOrNodeOrID instanceof nsIAccessible) {
+ return aAccOrNodeOrID.DOMNode;
+ }
+
+ var node = (aDocument || document).getElementById(aAccOrNodeOrID);
+ if (!node) {
+ ok(false, "Can't get DOM element for " + aAccOrNodeOrID);
+ return null;
+ }
+
+ return node;
+}
+
+/**
+ * Constants indicates getAccessible doesn't fail if there is no accessible.
+ */
+const DONOTFAIL_IF_NO_ACC = 1;
+
+/**
+ * Constants indicates getAccessible won't fail if accessible doesn't implement
+ * the requested interfaces.
+ */
+const DONOTFAIL_IF_NO_INTERFACE = 2;
+
+/**
+ * Return accessible for the given identifier (may be ID attribute or DOM
+ * element or accessible object) or null.
+ *
+ * @param aAccOrElmOrID [in] identifier to get an accessible implementing
+ * the given interfaces
+ * @param aInterfaces [in, optional] the interface or an array interfaces
+ * to query it/them from obtained accessible
+ * @param aElmObj [out, optional] object to store DOM element which
+ * accessible is obtained for
+ * @param aDoNotFailIf [in, optional] no error for special cases (see
+ * constants above)
+ */
+function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj, aDoNotFailIf) {
+ if (!aAccOrElmOrID) {
+ return null;
+ }
+
+ var elm = null;
+
+ if (aAccOrElmOrID instanceof nsIAccessible) {
+ try {
+ elm = aAccOrElmOrID.DOMNode;
+ } catch (e) {}
+ } else if (Node.isInstance(aAccOrElmOrID)) {
+ elm = aAccOrElmOrID;
+ } else {
+ elm = document.getElementById(aAccOrElmOrID);
+ if (!elm) {
+ ok(false, "Can't get DOM element for " + aAccOrElmOrID);
+ return null;
+ }
+ }
+
+ if (aElmObj && typeof aElmObj == "object") {
+ aElmObj.value = elm;
+ }
+
+ var acc = aAccOrElmOrID instanceof nsIAccessible ? aAccOrElmOrID : null;
+ if (!acc) {
+ try {
+ acc = gAccService.getAccessibleFor(elm);
+ } catch (e) {}
+
+ if (!acc) {
+ if (!(aDoNotFailIf & DONOTFAIL_IF_NO_ACC)) {
+ ok(false, "Can't get accessible for " + prettyName(aAccOrElmOrID));
+ }
+
+ return null;
+ }
+ }
+
+ if (!aInterfaces) {
+ return acc;
+ }
+
+ if (!(aInterfaces instanceof Array)) {
+ aInterfaces = [aInterfaces];
+ }
+
+ for (var index = 0; index < aInterfaces.length; index++) {
+ if (acc instanceof aInterfaces[index]) {
+ continue;
+ }
+ try {
+ acc.QueryInterface(aInterfaces[index]);
+ } catch (e) {
+ if (!(aDoNotFailIf & DONOTFAIL_IF_NO_INTERFACE)) {
+ ok(
+ false,
+ "Can't query " + aInterfaces[index] + " for " + aAccOrElmOrID
+ );
+ }
+
+ return null;
+ }
+ }
+
+ return acc;
+}
+
+/**
+ * Return true if the given identifier has an accessible, or exposes the wanted
+ * interfaces.
+ */
+function isAccessible(aAccOrElmOrID, aInterfaces) {
+ return !!getAccessible(
+ aAccOrElmOrID,
+ aInterfaces,
+ null,
+ DONOTFAIL_IF_NO_ACC | DONOTFAIL_IF_NO_INTERFACE
+ );
+}
+
+/**
+ * Return an accessible that contains the DOM node for the given identifier.
+ */
+function getContainerAccessible(aAccOrElmOrID) {
+ var node = getNode(aAccOrElmOrID);
+ if (!node) {
+ return null;
+ }
+
+ // eslint-disable-next-line no-empty
+ while ((node = node.parentNode) && !isAccessible(node)) {}
+ return node ? getAccessible(node) : null;
+}
+
+/**
+ * Return root accessible for the given identifier.
+ */
+function getRootAccessible(aAccOrElmOrID) {
+ var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document);
+ return acc ? acc.rootDocument.QueryInterface(nsIAccessible) : null;
+}
+
+/**
+ * Return tab document accessible the given accessible is contained by.
+ */
+function getTabDocAccessible(aAccOrElmOrID) {
+ var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document);
+
+ var docAcc = acc.document.QueryInterface(nsIAccessible);
+ var containerDocAcc = docAcc.parent.document;
+
+ // Test is running is stand-alone mode.
+ if (acc.rootDocument == containerDocAcc) {
+ return docAcc;
+ }
+
+ // In the case of running all tests together.
+ return containerDocAcc.QueryInterface(nsIAccessible);
+}
+
+/**
+ * Return application accessible.
+ */
+function getApplicationAccessible() {
+ return gAccService
+ .getApplicationAccessible()
+ .QueryInterface(nsIAccessibleApplication);
+}
+
+/**
+ * A version of accessible tree testing, doesn't fail if tree is not complete.
+ */
+function testElm(aID, aTreeObj) {
+ testAccessibleTree(aID, aTreeObj, kSkipTreeFullCheck);
+}
+
+/**
+ * Flags used for testAccessibleTree
+ */
+const kSkipTreeFullCheck = 1;
+
+/**
+ * Compare expected and actual accessibles trees.
+ *
+ * @param aAccOrElmOrID [in] accessible identifier
+ * @param aAccTree [in] JS object, each field corresponds to property of
+ * accessible object. Additionally special properties
+ * are presented:
+ * children - an array of JS objects representing
+ * children of accessible
+ * states - an object having states and extraStates
+ * fields
+ * @param aFlags [in, optional] flags, see constants above
+ */
+// eslint-disable-next-line complexity
+function testAccessibleTree(aAccOrElmOrID, aAccTree, aFlags) {
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc) {
+ return;
+ }
+
+ var accTree = aAccTree;
+
+ // Support of simplified accessible tree object.
+ accTree = normalizeAccTreeObj(accTree);
+
+ // Test accessible properties.
+ for (var prop in accTree) {
+ var msg =
+ "Wrong value of property '" + prop + "' for " + prettyName(acc) + ".";
+
+ switch (prop) {
+ case "actions": {
+ testActionNames(acc, accTree.actions);
+ break;
+ }
+
+ case "attributes":
+ testAttrs(acc, accTree[prop], true);
+ break;
+
+ case "absentAttributes":
+ testAbsentAttrs(acc, accTree[prop]);
+ break;
+
+ case "interfaces": {
+ var ifaces =
+ accTree[prop] instanceof Array ? accTree[prop] : [accTree[prop]];
+ for (let i = 0; i < ifaces.length; i++) {
+ ok(
+ acc instanceof ifaces[i],
+ "No " + ifaces[i] + " interface on " + prettyName(acc)
+ );
+ }
+ break;
+ }
+
+ case "relations": {
+ for (var rel in accTree[prop]) {
+ testRelation(acc, window[rel], accTree[prop][rel]);
+ }
+ break;
+ }
+
+ case "role":
+ isRole(acc, accTree[prop], msg);
+ break;
+
+ case "states":
+ case "extraStates":
+ case "absentStates":
+ case "absentExtraStates": {
+ testStates(
+ acc,
+ accTree.states,
+ accTree.extraStates,
+ accTree.absentStates,
+ accTree.absentExtraStates
+ );
+ break;
+ }
+
+ case "tagName":
+ is(accTree[prop], acc.DOMNode.tagName, msg);
+ break;
+
+ case "textAttrs": {
+ var prevOffset = -1;
+ for (var offset in accTree[prop]) {
+ if (prevOffset != -1) {
+ let attrs = accTree[prop][prevOffset];
+ testTextAttrs(
+ acc,
+ prevOffset,
+ attrs,
+ {},
+ prevOffset,
+ +offset,
+ true
+ );
+ }
+ prevOffset = +offset;
+ }
+
+ if (prevOffset != -1) {
+ var charCount = getAccessible(acc, [
+ nsIAccessibleText,
+ ]).characterCount;
+ let attrs = accTree[prop][prevOffset];
+ testTextAttrs(
+ acc,
+ prevOffset,
+ attrs,
+ {},
+ prevOffset,
+ charCount,
+ true
+ );
+ }
+
+ break;
+ }
+
+ default:
+ if (prop.indexOf("todo_") == 0) {
+ todo(false, msg);
+ } else if (prop != "children") {
+ is(acc[prop], accTree[prop], msg);
+ }
+ }
+ }
+
+ // Test children.
+ if ("children" in accTree && accTree.children instanceof Array) {
+ var children = acc.children;
+ var childCount = children.length;
+
+ if (accTree.children.length != childCount) {
+ for (let i = 0; i < Math.max(accTree.children.length, childCount); i++) {
+ var accChild = null,
+ testChild = null;
+ try {
+ testChild = accTree.children[i];
+ accChild = children.queryElementAt(i, nsIAccessible);
+
+ if (!testChild) {
+ ok(
+ false,
+ prettyName(acc) +
+ " has an extra child at index " +
+ i +
+ " : " +
+ prettyName(accChild)
+ );
+ continue;
+ }
+
+ testChild = normalizeAccTreeObj(testChild);
+ if (accChild.role !== testChild.role) {
+ ok(
+ false,
+ prettyName(accTree) +
+ " and " +
+ prettyName(acc) +
+ " have different children at index " +
+ i +
+ " : " +
+ prettyName(testChild) +
+ ", " +
+ prettyName(accChild)
+ );
+ }
+ info(
+ "Matching " +
+ prettyName(accTree) +
+ " and " +
+ prettyName(acc) +
+ " child at index " +
+ i +
+ " : " +
+ prettyName(accChild)
+ );
+ } catch (e) {
+ ok(
+ false,
+ prettyName(accTree) +
+ " is expected to have a child at index " +
+ i +
+ " : " +
+ prettyName(testChild) +
+ ", original tested: " +
+ prettyName(aAccOrElmOrID) +
+ ", " +
+ e
+ );
+ }
+ }
+ } else {
+ if (aFlags & kSkipTreeFullCheck) {
+ for (let i = 0; i < childCount; i++) {
+ let child = children.queryElementAt(i, nsIAccessible);
+ testAccessibleTree(child, accTree.children[i], aFlags);
+ }
+ return;
+ }
+
+ // nsIAccessible::firstChild
+ var expectedFirstChild =
+ childCount > 0 ? children.queryElementAt(0, nsIAccessible) : null;
+ var firstChild = null;
+ try {
+ firstChild = acc.firstChild;
+ } catch (e) {}
+ is(
+ firstChild,
+ expectedFirstChild,
+ "Wrong first child of " + prettyName(acc)
+ );
+
+ // nsIAccessible::lastChild
+ var expectedLastChild =
+ childCount > 0
+ ? children.queryElementAt(childCount - 1, nsIAccessible)
+ : null;
+ var lastChild = null;
+ try {
+ lastChild = acc.lastChild;
+ } catch (e) {}
+ is(
+ lastChild,
+ expectedLastChild,
+ "Wrong last child of " + prettyName(acc)
+ );
+
+ for (var i = 0; i < childCount; i++) {
+ let child = children.queryElementAt(i, nsIAccessible);
+
+ // nsIAccessible::parent
+ var parent = null;
+ try {
+ parent = child.parent;
+ } catch (e) {}
+ is(parent, acc, "Wrong parent of " + prettyName(child));
+
+ // nsIAccessible::indexInParent
+ var indexInParent = -1;
+ try {
+ indexInParent = child.indexInParent;
+ } catch (e) {}
+ is(indexInParent, i, "Wrong index in parent of " + prettyName(child));
+
+ // nsIAccessible::nextSibling
+ var expectedNextSibling =
+ i < childCount - 1
+ ? children.queryElementAt(i + 1, nsIAccessible)
+ : null;
+ var nextSibling = null;
+ try {
+ nextSibling = child.nextSibling;
+ } catch (e) {}
+ is(
+ nextSibling,
+ expectedNextSibling,
+ "Wrong next sibling of " + prettyName(child)
+ );
+
+ // nsIAccessible::previousSibling
+ var expectedPrevSibling =
+ i > 0 ? children.queryElementAt(i - 1, nsIAccessible) : null;
+ var prevSibling = null;
+ try {
+ prevSibling = child.previousSibling;
+ } catch (e) {}
+ is(
+ prevSibling,
+ expectedPrevSibling,
+ "Wrong previous sibling of " + prettyName(child)
+ );
+
+ // Go down through subtree
+ testAccessibleTree(child, accTree.children[i], aFlags);
+ }
+ }
+ }
+}
+
+/**
+ * Return true if accessible for the given node is in cache.
+ */
+function isAccessibleInCache(aNodeOrId) {
+ var node = getNode(aNodeOrId);
+ return !!gAccService.getAccessibleFromCache(node);
+}
+
+/**
+ * Test accessible tree for defunct accessible.
+ *
+ * @param aAcc [in] the defunct accessible
+ * @param aNodeOrId [in] the DOM node identifier for the defunct accessible
+ */
+function testDefunctAccessible(aAcc, aNodeOrId) {
+ if (aNodeOrId) {
+ ok(
+ !isAccessible(aNodeOrId),
+ "Accessible for " + aNodeOrId + " wasn't properly shut down!"
+ );
+ }
+
+ var msg =
+ " doesn't fail for shut down accessible " + prettyName(aNodeOrId) + "!";
+
+ // firstChild
+ var success = false;
+ try {
+ aAcc.firstChild;
+ } catch (e) {
+ success = e.result == Cr.NS_ERROR_FAILURE;
+ }
+ ok(success, "firstChild" + msg);
+
+ // lastChild
+ success = false;
+ try {
+ aAcc.lastChild;
+ } catch (e) {
+ success = e.result == Cr.NS_ERROR_FAILURE;
+ }
+ ok(success, "lastChild" + msg);
+
+ // childCount
+ success = false;
+ try {
+ aAcc.childCount;
+ } catch (e) {
+ success = e.result == Cr.NS_ERROR_FAILURE;
+ }
+ ok(success, "childCount" + msg);
+
+ // children
+ success = false;
+ try {
+ aAcc.children;
+ } catch (e) {
+ success = e.result == Cr.NS_ERROR_FAILURE;
+ }
+ ok(success, "children" + msg);
+
+ // nextSibling
+ success = false;
+ try {
+ aAcc.nextSibling;
+ } catch (e) {
+ success = e.result == Cr.NS_ERROR_FAILURE;
+ }
+ ok(success, "nextSibling" + msg);
+
+ // previousSibling
+ success = false;
+ try {
+ aAcc.previousSibling;
+ } catch (e) {
+ success = e.result == Cr.NS_ERROR_FAILURE;
+ }
+ ok(success, "previousSibling" + msg);
+
+ // parent
+ success = false;
+ try {
+ aAcc.parent;
+ } catch (e) {
+ success = e.result == Cr.NS_ERROR_FAILURE;
+ }
+ ok(success, "parent" + msg);
+}
+
+/**
+ * Convert role to human readable string.
+ */
+function roleToString(aRole) {
+ return gAccService.getStringRole(aRole);
+}
+
+/**
+ * Convert states to human readable string.
+ */
+function statesToString(aStates, aExtraStates) {
+ var list = gAccService.getStringStates(aStates, aExtraStates);
+
+ var str = "";
+ for (var index = 0; index < list.length - 1; index++) {
+ str += list.item(index) + ", ";
+ }
+
+ if (list.length) {
+ str += list.item(index);
+ }
+
+ return str;
+}
+
+/**
+ * Convert event type to human readable string.
+ */
+function eventTypeToString(aEventType) {
+ return gAccService.getStringEventType(aEventType);
+}
+
+/**
+ * Convert relation type to human readable string.
+ */
+function relationTypeToString(aRelationType) {
+ return gAccService.getStringRelationType(aRelationType);
+}
+
+function getLoadContext() {
+ return window.docShell.QueryInterface(Ci.nsILoadContext);
+}
+
+/**
+ * Return text from clipboard.
+ */
+function getTextFromClipboard() {
+ var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+ Ci.nsITransferable
+ );
+ trans.init(getLoadContext());
+ if (!trans) {
+ return "";
+ }
+
+ trans.addDataFlavor("text/plain");
+ Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+
+ var str = {};
+ trans.getTransferData("text/plain", str);
+
+ if (str) {
+ str = str.value.QueryInterface(Ci.nsISupportsString);
+ }
+ if (str) {
+ return str.data;
+ }
+
+ return "";
+}
+
+/**
+ * Extract DOMNode id from an accessible. If e10s is enabled, DOMNode is not
+ * present in parent process but, if available, DOMNode id is attached to an
+ * accessible object.
+ * @param {nsIAccessible} accessible accessible
+ * @return {String?} DOMNode id if available
+ */
+function getAccessibleDOMNodeID(accessible) {
+ if (accessible instanceof nsIAccessibleDocument) {
+ // If accessible is a document, trying to find its document body id.
+ try {
+ return accessible.DOMNode.body.id;
+ } catch (e) {
+ /* This only works if accessible is not a proxy. */
+ }
+ }
+ try {
+ return accessible.DOMNode.id;
+ } catch (e) {
+ /* This will fail if DOMNode is in different process. */
+ }
+ try {
+ // When e10s is enabled, accessible will have an "id" property if its
+ // corresponding DOMNode has an id. If accessible is a document, its "id"
+ // property corresponds to the "id" of its body element.
+ return accessible.id;
+ } catch (e) {
+ /* This will fail if accessible is not a proxy. */
+ }
+ return null;
+}
+
+/**
+ * Return pretty name for identifier, it may be ID, DOM node or accessible.
+ */
+function prettyName(aIdentifier) {
+ if (aIdentifier instanceof Array) {
+ let msg = "";
+ for (var idx = 0; idx < aIdentifier.length; idx++) {
+ if (msg != "") {
+ msg += ", ";
+ }
+
+ msg += prettyName(aIdentifier[idx]);
+ }
+ return msg;
+ }
+
+ if (aIdentifier instanceof nsIAccessible) {
+ var acc = getAccessible(aIdentifier);
+ var domID = getAccessibleDOMNodeID(acc);
+ let msg = "[";
+ try {
+ if (Services.appinfo.browserTabsRemoteAutostart) {
+ if (domID) {
+ msg += `DOM node id: ${domID}, `;
+ }
+ } else {
+ msg += `${getNodePrettyName(acc.DOMNode)}, `;
+ }
+ msg += "role: " + roleToString(acc.role);
+ if (acc.name) {
+ msg += ", name: '" + shortenString(acc.name) + "'";
+ }
+ } catch (e) {
+ msg += "defunct";
+ }
+
+ if (acc) {
+ msg += ", address: " + getObjAddress(acc);
+ }
+ msg += "]";
+
+ return msg;
+ }
+
+ if (Node.isInstance(aIdentifier)) {
+ return "[ " + getNodePrettyName(aIdentifier) + " ]";
+ }
+
+ if (aIdentifier && typeof aIdentifier === "object") {
+ var treeObj = normalizeAccTreeObj(aIdentifier);
+ if ("role" in treeObj) {
+ function stringifyTree(aObj) {
+ var text = roleToString(aObj.role) + ": [ ";
+ if ("children" in aObj) {
+ for (var i = 0; i < aObj.children.length; i++) {
+ var c = normalizeAccTreeObj(aObj.children[i]);
+ text += stringifyTree(c);
+ if (i < aObj.children.length - 1) {
+ text += ", ";
+ }
+ }
+ }
+ return text + "] ";
+ }
+ return `{ ${stringifyTree(treeObj)} }`;
+ }
+ return JSON.stringify(aIdentifier);
+ }
+
+ return " '" + aIdentifier + "' ";
+}
+
+/**
+ * Shorten a long string if it exceeds MAX_TRIM_LENGTH.
+ * @param aString the string to shorten.
+ * @returns the shortened string.
+ */
+function shortenString(aString, aMaxLength) {
+ if (aString.length <= MAX_TRIM_LENGTH) {
+ return aString;
+ }
+
+ // Trim the string if its length is > MAX_TRIM_LENGTH characters.
+ var trimOffset = MAX_TRIM_LENGTH / 2;
+ return (
+ aString.substring(0, trimOffset - 1) +
+ "..." +
+ aString.substring(aString.length - trimOffset, aString.length)
+ );
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// General Utils
+// //////////////////////////////////////////////////////////////////////////////
+/**
+ * Return main chrome window (crosses chrome boundary)
+ */
+function getMainChromeWindow(aWindow) {
+ return aWindow.browsingContext.topChromeWindow;
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Private
+// //////////////////////////////////////////////////////////////////////////////
+
+// //////////////////////////////////////////////////////////////////////////////
+// Accessible general
+
+function getNodePrettyName(aNode) {
+ try {
+ var tag = "";
+ if (aNode.nodeType == Node.DOCUMENT_NODE) {
+ tag = "document";
+ } else {
+ tag = aNode.localName;
+ if (aNode.nodeType == Node.ELEMENT_NODE && aNode.hasAttribute("id")) {
+ tag += '@id="' + aNode.getAttribute("id") + '"';
+ }
+ }
+
+ return "'" + tag + " node', address: " + getObjAddress(aNode);
+ } catch (e) {
+ return "' no node info '";
+ }
+}
+
+function getObjAddress(aObj) {
+ var exp = /native\s*@\s*(0x[a-f0-9]+)/g;
+ var match = exp.exec(aObj.toString());
+ if (match) {
+ return match[1];
+ }
+
+ return aObj.toString();
+}
+
+function normalizeAccTreeObj(aObj) {
+ var key = Object.keys(aObj)[0];
+ var roleName = "ROLE_" + key;
+ if (roleName in nsIAccessibleRole) {
+ return {
+ role: nsIAccessibleRole[roleName],
+ children: aObj[key],
+ };
+ }
+ return aObj;
+}
diff --git a/accessible/tests/mochitest/dumbfile.zip b/accessible/tests/mochitest/dumbfile.zip
new file mode 100644
index 0000000000..15cb0ecb3e
--- /dev/null
+++ b/accessible/tests/mochitest/dumbfile.zip
Binary files differ
diff --git a/accessible/tests/mochitest/elm/a11y.ini b/accessible/tests/mochitest/elm/a11y.ini
new file mode 100644
index 0000000000..ceb296670d
--- /dev/null
+++ b/accessible/tests/mochitest/elm/a11y.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/moz.png
+ !/dom/media/test/bug461281.ogg
+ !/dom/security/test/csp/dummy.pdf
+
+[test_HTMLSpec.html]
+[test_figure.html]
+[test_listbox.xhtml]
+[test_MathMLSpec.html]
+[test_nsApplicationAcc.html]
+[test_shadowroot.html]
+support-files = test_shadowroot_subframe.html
diff --git a/accessible/tests/mochitest/elm/test_HTMLSpec.html b/accessible/tests/mochitest/elm/test_HTMLSpec.html
new file mode 100644
index 0000000000..a5cc175123
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_HTMLSpec.html
@@ -0,0 +1,2024 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML a11y spec tests</title>
+ <link id="link" rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+ const syntheticBrowsingContexts = SpecialPowers.getBoolPref("browser.opaqueResponseBlocking.syntheticBrowsingContext", false);
+
+ async function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:a@href
+
+ var obj = {
+ role: ROLE_LINK,
+ states: STATE_LINKED,
+ actions: "jump",
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText, nsIAccessibleHyperLink ],
+ children: [ // all kids inherits linked state and action
+ {
+ role: ROLE_TEXT_LEAF,
+ states: STATE_LINKED,
+ actions: "click ancestor",
+ },
+ ],
+ };
+ testElm("a_href", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:a no @href
+
+ obj = {
+ todo_role: ROLE_TEXT_CONTAINER,
+ absentStates: STATE_LINKED,
+ actions: null,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ absentStates: STATE_LINKED,
+ actions: null,
+ },
+ ],
+ };
+ testElm("a_nohref", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:abbr contained by HTML:td
+
+ obj = {
+ role: ROLE_CELL,
+ attributes: { abbr: "WWW" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [
+ {
+ role: ROLE_TEXT,
+ children: [ { role: ROLE_TEXT_LEAF } ],
+ },
+ ],
+ };
+ testElm("td_abbr", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:address
+
+ obj = {
+ role: ROLE_GROUPING,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("address", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:area@href
+
+ obj = {
+ role: ROLE_LINK,
+ states: STATE_LINKED,
+ actions: "jump",
+ interfaces: [ nsIAccessibleHyperLink ],
+ children: [],
+ };
+ testElm(getAccessible("imgmap").firstChild, obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:area no @href
+
+ obj = {
+ todo_role: "ROLE_SHAPE",
+ absentStates: STATE_LINKED,
+ children: [],
+ };
+ testElm(getAccessible("imgmap").lastChild, obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:article
+ obj = {
+ role: ROLE_ARTICLE,
+ states: STATE_READONLY,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("article", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:aside
+ obj = {
+ role: ROLE_LANDMARK,
+ attributes: { "xml-roles": "complementary" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("aside", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ obj = { // HTML:audio
+ role: ROLE_GROUPING,
+ };
+ testElm("audio", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ obj = { // HTML:b contained by paragraph
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-weight": kBoldFontWeight },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:b text
+ ],
+ };
+ testElm("b_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ obj = { // HTML:bdi contained by paragraph
+ role: ROLE_PARAGRAPH,
+ todo_textAttrs: {
+ 0: { },
+ 5: { "writing-mode": "rl" },
+ 8: { },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:bdi text
+ { role: ROLE_TEXT_LEAF }, // plain text
+ ],
+ };
+ testElm("bdi_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:bdo contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ todo_textAttrs: {
+ 0: { },
+ 6: { "writing-mode": "rl" },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ ],
+ };
+ testElm("bdo_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:blockquote
+
+ obj = {
+ role: ROLE_BLOCKQUOTE,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [ { role: ROLE_PARAGRAPH } ],
+ };
+ testElm("blockquote", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:br contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [
+ { role: ROLE_WHITESPACE },
+ { role: ROLE_WHITESPACE }
+ ]
+ };
+ testElm("br_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ obj = { // HTML:button
+ role: ROLE_PUSHBUTTON,
+ absentStates: STATE_DEFAULT,
+ actions: "press",
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("button", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:button@type="submit" (default button)
+
+ obj = {
+ role: ROLE_PUSHBUTTON,
+ states: STATE_DEFAULT,
+ actions: "press",
+ };
+ testElm("button_default", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:canvas
+
+ obj = {
+ role: ROLE_CANVAS,
+ };
+ testElm("canvas", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:caption under table
+
+ obj = {
+ role: ROLE_TABLE,
+ relations: {
+ RELATION_LABELLED_BY: "caption",
+ },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText, nsIAccessibleTable ],
+ children: [
+ {
+ role: ROLE_CAPTION,
+ relations: {
+ RELATION_LABEL_FOR: "table",
+ },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ },
+ { // td inside thead
+ role: ROLE_ROW,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [
+ {
+ role: ROLE_COLUMNHEADER,
+ interfaces: [ nsIAccessibleTableCell, nsIAccessibleText, nsIAccessibleHyperText ],
+ },
+ { role: ROLE_COLUMNHEADER },
+ ],
+ },
+ { // td inside tbody
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_ROWHEADER,
+ interfaces: [ nsIAccessibleTableCell, nsIAccessibleText, nsIAccessibleHyperText ],
+ },
+ {
+ role: ROLE_CELL,
+ interfaces: [ nsIAccessibleTableCell, nsIAccessibleText, nsIAccessibleHyperText ],
+ },
+ ],
+ },
+ { // td inside tfoot
+ role: ROLE_ROW,
+ },
+ ],
+ };
+ testElm("table", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:cite contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-style": "italic" },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:cite text
+ ],
+ };
+ testElm("cite_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:code contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-family": kMonospaceFontFamily },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:code text
+ ],
+ };
+ testElm("code_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:col and HTML:colgroup under table
+
+ obj =
+ { TABLE: [
+ { ROW: [
+ { role: ROLE_CELL },
+ { role: ROLE_CELL },
+ { role: ROLE_CELL },
+ ] },
+ ] };
+ testElm("colNcolgroup_table", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:data contained by paragraph
+
+ obj =
+ { PARAGRAPH: [
+ { TEXT_LEAF: [] }, // HTML:data text
+ ] };
+ testElm("data_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:datalist associated with input
+
+ todo(false, "datalist and summary tree hierarchy test missed");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:dd, HTML:dl, HTML:dd
+
+ obj = {
+ role: ROLE_DEFINITION_LIST,
+ states: STATE_READONLY,
+ children: [ // dl
+ {
+ role: ROLE_TERM,
+ states: STATE_READONLY,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [ // dt
+ { role: ROLE_TEXT_LEAF },
+ ],
+ },
+ {
+ role: ROLE_DEFINITION,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [ // dd
+ { role: ROLE_TEXT_LEAF },
+ ],
+ },
+ ],
+ };
+ testElm("dl", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:del contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_CONTENT_DELETION },
+ ],
+ };
+ testElm("del_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:details with open state
+
+ obj = {
+ role: ROLE_DETAILS,
+ children: [
+ {
+ role: ROLE_SUMMARY,
+ states: STATE_EXPANDED,
+ actions: "collapse",
+ },
+ { role: ROLE_PARAGRAPH },
+ ],
+ };
+ testElm("details", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:details with closed (default) state
+
+ obj = {
+ role: ROLE_DETAILS,
+ children: [
+ {
+ role: ROLE_SUMMARY,
+ states: STATE_COLLAPSED,
+ actions: "expand",
+ },
+ ],
+ };
+ testElm("details_closed", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:dfn contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { "font-style": "italic" },
+ 12: { },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // HTML:dfn text
+ { role: ROLE_TEXT_LEAF }, // plain text
+ ],
+ };
+ testElm("dfn_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:dialog
+
+ // XXX: Remove the pushing of the pref and just run the test once the
+ // dialog element is enabled by default.
+ await SpecialPowers.pushPrefEnv({ set: [["dom.dialog_element.enabled", true]] });
+ obj = {
+ role: ROLE_DIALOG,
+ children: [
+ { role: ROLE_TEXT_LEAF },
+ ],
+ };
+ testElm("dialog", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:div
+
+ obj = {
+ role: ROLE_SECTION,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ ],
+ };
+ testElm("div", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:em in a paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-style": "italic" },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:em text
+ ],
+ };
+ testElm("em_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:embed
+
+ if (syntheticBrowsingContexts) {
+ obj = {
+ INTERNAL_FRAME: [ {
+ DOCUMENT: [ {
+ role: ROLE_GRAPHIC,
+ interfaces: [ nsIAccessibleImage ],
+ } ],
+ } ],
+ };
+ } else {
+ obj = {
+ role: ROLE_GRAPHIC,
+ interfaces: [ nsIAccessibleImage ],
+ };
+ }
+ testElm("embed_png", obj);
+
+ obj = {
+ INTERNAL_FRAME: [ {
+ DOCUMENT: [ {
+ role: ROLE_PARAGRAPH,
+ } ],
+ } ],
+ };
+ testElm("embed_html", obj);
+
+ obj = {
+ INTERNAL_FRAME: [ {
+ DOCUMENT: [ {
+ } ],
+ } ],
+ };
+ testElm("embed_pdf", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:fieldset and HTML:legend
+
+ obj = {
+ role: ROLE_GROUPING,
+ name: "legend",
+ relations: {
+ RELATION_LABELLED_BY: "legend",
+ },
+ children: [
+ {
+ role: ROLE_LABEL,
+ name: "legend",
+ relations: {
+ RELATION_LABEL_FOR: "fieldset",
+ },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ },
+ {
+ role: ROLE_ENTRY,
+ },
+ ],
+ };
+ testElm("fieldset", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:figure and HTML:figcaption
+
+ obj = {
+ role: ROLE_FIGURE,
+ attributes: { "xml-roles": "figure" },
+ relations: {
+ RELATION_LABELLED_BY: "figcaption",
+ },
+ children: [
+ { role: ROLE_GRAPHIC },
+ {
+ role: ROLE_CAPTION,
+ relations: {
+ RELATION_LABEL_FOR: "figure",
+ },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ },
+ ],
+ };
+ testElm("figure", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:footer
+
+ obj = {
+ role: ROLE_LANDMARK,
+ attributes: { "xml-roles": "contentinfo" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("footer", obj);
+
+ obj = {
+ role: ROLE_SECTION,
+ absentAttributes: { "xml-roles": "contentinfo" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("footer_in_article", obj);
+ testElm("footer_in_aside", obj);
+ testElm("footer_in_main", obj);
+ testElm("footer_in_nav", obj);
+ testElm("footer_in_section", obj);
+ testElm("footer_in_blockquote", obj);
+ testElm("footer_in_details", obj);
+ testElm("footer_in_dialog", obj);
+ testElm("footer_in_fieldset", obj);
+ testElm("footer_in_figure", obj);
+ testElm("footer_in_td", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:form
+
+ obj = {
+ role: ROLE_FORM,
+ absentAttributes: { "xml-roles": "form" },
+ };
+ testElm("form", obj);
+
+ // HTML:form with an accessible name
+
+ obj = {
+ role: ROLE_FORM_LANDMARK,
+ attributes: { "xml-roles": "form" },
+ };
+ testElm("named_form", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // // HTML:frameset, HTML:frame and HTML:iframe
+
+ obj = {
+ INTERNAL_FRAME: [ { // HTML:iframe
+ DOCUMENT: [ {
+ INTERNAL_FRAME: [ { // HTML:frame
+ DOCUMENT: [ { role: ROLE_TEXT_LEAF} ],
+ } ],
+ } ],
+ } ],
+ };
+ testElm("frameset_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:h1, HTML:h2, HTML:h3, HTML:h4, HTML:h5, HTML:h6
+
+ function headingWithLevel(i) {
+ return {
+ role: ROLE_HEADING,
+ attributes: { "level": i.toString() },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ }
+
+ for (let level = 1; level <= 6; ++level) {
+ testElm("h" + level, headingWithLevel(level));
+ for (const ancestor of ["section", "article", "aside", "nav"]) {
+ testElm("h" + level + "_in_" + ancestor, headingWithLevel(level));
+ testElm("h" + level + "_in_" + ancestor + "_in_hgroup", headingWithLevel(level));
+ }
+ }
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:header
+
+ obj = {
+ role: ROLE_LANDMARK,
+ attributes: { "xml-roles": "banner" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("header", obj);
+
+ obj = {
+ role: ROLE_SECTION,
+ absentAttributes: { "xml-roles": "banner" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("header_in_article", obj);
+ testElm("header_in_aside", obj);
+ testElm("header_in_main", obj);
+ testElm("header_in_nav", obj);
+ testElm("header_in_section", obj);
+ testElm("header_in_blockquote", obj);
+ testElm("header_in_details", obj);
+ testElm("header_in_dialog", obj);
+ testElm("header_in_fieldset", obj);
+ testElm("header_in_figure", obj);
+ testElm("header_in_td", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:hr
+
+ obj = {
+ role: ROLE_SEPARATOR,
+ };
+ testElm("hr", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ obj = { // HTML:i contained by paragraph
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-style": "italic" },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:i text
+ ],
+ };
+ testElm("i_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:img
+
+ obj = {
+ role: ROLE_GRAPHIC,
+ interfaces: [ nsIAccessibleImage ],
+ };
+ testElm("img", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="button"
+
+ obj = {
+ role: ROLE_PUSHBUTTON,
+ absentStates: STATE_DEFAULT,
+ };
+ testElm("input_button", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="checkbox"
+
+ obj = {
+ role: ROLE_CHECKBUTTON,
+ states: STATE_CHECKABLE,
+ absentStates: STATE_CHECKED,
+ actions: "check",
+ };
+ testElm("input_checkbox", obj);
+
+ obj = {
+ role: ROLE_CHECKBUTTON,
+ states: STATE_CHECKABLE | STATE_CHECKED,
+ actions: "uncheck",
+ };
+ testElm("input_checkbox_true", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="file"
+
+ obj = {
+ GROUPING: [
+ { role: ROLE_PUSHBUTTON },
+ { role: ROLE_LABEL },
+ ],
+ };
+ testElm("input_file", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="image"
+
+ obj = {
+ role: ROLE_PUSHBUTTON,
+ absentStates: STATE_DEFAULT,
+ actions: "press",
+ };
+ testElm("input_image", obj);
+ testElm("input_image_display", obj);
+ testElm("input_submit", obj);
+
+ obj = {
+ role: ROLE_PUSHBUTTON,
+ actions: "press",
+ states: STATE_DEFAULT,
+ };
+ testElm("input_image_default", obj);
+ testElm("input_submit_default", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="number" and etc
+
+ obj = {
+ role: ROLE_SPINBUTTON,
+ interfaces: [ nsIAccessibleValue, nsIAccessibleText, nsIAccessibleEditableText ],
+ children: [
+ { role: ROLE_TEXT_LEAF },
+ ],
+ };
+ testElm("input_number", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="text" and etc
+
+ obj = {
+ role: ROLE_ENTRY,
+ extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE,
+ actions: "activate",
+ interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ],
+ children: [
+ { role: ROLE_TEXT_LEAF },
+ ],
+ };
+ testElm("input_email", obj);
+ testElm("input_search", obj);
+ testElm("input_tel", obj);
+ testElm("input_text", obj);
+ testElm("input_url", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // input @type="text" with placeholder attribute
+
+ // First: Label and placeholder, text is the same, no attribute.
+ obj = {
+ role: ROLE_ENTRY,
+ name: "Your name",
+ extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE,
+ actions: "activate",
+ absentAttributes: { placeholder: "Your name" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ],
+ children: [],
+ };
+ testElm("input_placeholder_same", obj);
+
+ // Second: Label and placeholder, text is different, attribute.
+ obj = {
+ role: ROLE_ENTRY,
+ name: "First name:",
+ extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE,
+ actions: "activate",
+ attributes: { placeholder: "Enter your first name" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ],
+ children: [],
+ };
+ testElm("input_placeholder_different", obj);
+
+ // Third: placeholder only, text is name, no attribute.
+ obj = {
+ role: ROLE_ENTRY,
+ name: "Date of birth",
+ extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE,
+ actions: "activate",
+ absentAttributes: { placeholder: "Date of birth" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ],
+ children: [],
+ };
+ testElm("input_placeholder_only", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="password"
+
+ obj = {
+ role: ROLE_PASSWORD_TEXT,
+ states: STATE_PROTECTED,
+ extraStates: EXT_STATE_EDITABLE,
+ actions: "activate",
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ },
+ ],
+ };
+ testElm("input_password", obj);
+ ok(getAccessible("input_password").firstChild.name != "44",
+ "text leaf for password shouldn't have its real value as its name!");
+
+ obj = {
+ role: ROLE_PASSWORD_TEXT,
+ states: STATE_PROTECTED | STATE_READONLY,
+ actions: "activate",
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ },
+ ],
+ };
+ testElm("input_password_readonly", obj);
+ ok(getAccessible("input_password_readonly").firstChild.name != "44",
+ "text leaf for password shouldn't have its real value as its name!");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="radio"
+
+ obj = {
+ role: ROLE_RADIOBUTTON,
+ states: STATE_CHECKABLE,
+ absentStates: STATE_CHECKED,
+ actions: "select",
+ };
+ testElm("input_radio", obj);
+
+ obj = {
+ role: ROLE_RADIOBUTTON,
+ states: STATE_CHECKABLE | STATE_CHECKED,
+ actions: "select",
+ };
+ testElm("input_radio_true", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="range"
+
+ obj = {
+ role: ROLE_SLIDER,
+ };
+ testElm("input_range", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="reset"
+
+ obj = {
+ role: ROLE_PUSHBUTTON,
+ actions: "press",
+ absentStates: STATE_DEFAULT,
+ };
+ testElm("input_reset", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="time"
+
+ obj = {
+ role: ROLE_TIME_EDITOR,
+ name: "time label",
+ attributes: { "text-input-type": "time" },
+ children: [
+ { role: ROLE_SPINBUTTON },
+ { role: ROLE_TEXT_LEAF },
+ { role: ROLE_SPINBUTTON },
+ { role: ROLE_TEXT_LEAF },
+ { role: ROLE_ENTRY },
+ ],
+ };
+ testElm("input_time", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="date"
+
+ obj = {
+ role: ROLE_DATE_EDITOR,
+ name: "date label",
+ attributes: { "text-input-type": "date" },
+ children: [
+ { role: ROLE_SPINBUTTON },
+ { role: ROLE_TEXT_LEAF },
+ { role: ROLE_SPINBUTTON },
+ { role: ROLE_TEXT_LEAF },
+ { role: ROLE_SPINBUTTON },
+ { role: ROLE_PUSHBUTTON },
+ ],
+ };
+ testElm("input_date", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="datetime-local"
+
+ obj = {
+ role: ROLE_DATE_EDITOR,
+ name: "datetime-local label",
+ attributes: { "text-input-type": "datetime-local" },
+ children: [
+ { role: ROLE_SPINBUTTON }, // Month
+ { role: ROLE_TEXT_LEAF }, // "/""
+ { role: ROLE_SPINBUTTON }, // Day
+ { role: ROLE_TEXT_LEAF }, // "/"
+ { role: ROLE_SPINBUTTON }, // Year
+ { role: ROLE_TEXT_LEAF }, // " "
+ { role: ROLE_SPINBUTTON }, // Hours
+ { role: ROLE_TEXT_LEAF }, // ":"
+ { role: ROLE_SPINBUTTON }, // Minutes
+ { role: ROLE_TEXT_LEAF }, // " "
+ { role: ROLE_ENTRY }, // "AM" or "PM"
+ { role: ROLE_PUSHBUTTON }, // Calendar
+ ],
+ };
+ testElm("input_datetime_local", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:ins contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_CONTENT_INSERTION },
+ ],
+ };
+ testElm("ins_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:kbd contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-family": kMonospaceFontFamily },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:kbd text
+ ],
+ };
+ testElm("kbd_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:label
+
+ obj = {
+ role: ROLE_LABEL,
+ todo_relations: {
+ RELATION_LABEL_FOR: "label_input",
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ {
+ role: ROLE_ENTRY,
+ relations: {
+ RELATION_LABELLED_BY: "label",
+ },
+ },
+ ],
+ };
+ testElm("label", obj);
+
+ obj = {
+ role: ROLE_LABEL,
+ relations: {
+ RELATION_LABEL_FOR: "label_for_input",
+ },
+ };
+ testElm("label_for", obj);
+
+ obj = {
+ role: ROLE_ENTRY,
+ relations: {
+ RELATION_LABELLED_BY: "label_for",
+ },
+ };
+ testElm("label_for_input", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:ul, HTML:ol, HTML:li
+
+ obj = { // ul or ol
+ role: ROLE_LIST,
+ states: STATE_READONLY,
+ children: [
+ { // li
+ role: ROLE_LISTITEM,
+ states: STATE_READONLY,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ },
+ ],
+ };
+ testElm("ul", obj);
+ testElm("ol", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:link
+
+ ok(!isAccessible("link"), "link element is not accessible");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:main
+
+ obj = {
+ role: ROLE_LANDMARK,
+ attributes: { "xml-roles": "main" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("main", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:map
+
+ ok(!isAccessible("map_imagemap"),
+ "map element is not accessible if used as an image map");
+
+ obj = {
+ role: ROLE_TEXT_CONTAINER,
+ };
+ testElm("map", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:mark contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_MARK, // HTML:mark text
+ attributes: { "xml-roles": "mark" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ textAttrs: {
+ 0: { },
+ }
+ }
+ ],
+ };
+ testElm("mark_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:math
+
+ obj = {
+ role: ROLE_MATHML_MATH,
+ };
+ testElm("math", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:menu
+
+ obj = {
+ role: ROLE_LIST, // menu
+ children: [
+ { role: ROLE_LISTITEM,
+ children: [ // home
+ { role: ROLE_LISTITEM_MARKER },
+ { role: ROLE_TEXT_LEAF }
+ ]
+ },
+ {
+ role: ROLE_LISTITEM,
+ children: [
+ { role: ROLE_LISTITEM_MARKER },
+ { role: ROLE_TEXT_LEAF }, // about
+ {
+ role: ROLE_LIST, // menu
+ children: [
+ { role: ROLE_LISTITEM,
+ children: [
+ { role: ROLE_LISTITEM_MARKER },
+ { role: ROLE_TEXT_LEAF } // our story
+ ]
+ },
+ ]
+ },
+ ]
+ },
+ ]
+ };
+
+ testElm("menu", obj);
+ obj = {
+ role: ROLE_LIST,
+ children: [
+ {
+ role: ROLE_LISTITEM,
+ children: [
+ { role: ROLE_LISTITEM_MARKER },
+ {
+ role: ROLE_PUSHBUTTON,
+ children: [
+ { role: ROLE_TEXT_LEAF }
+ ]
+ },
+ {
+ role: ROLE_LIST,
+ children: [
+ {
+ role: ROLE_LISTITEM,
+ children: [
+ { role: ROLE_LISTITEM_MARKER },
+ {
+ role: ROLE_PUSHBUTTON,
+ children: [
+ { role: ROLE_TEXT_LEAF }
+ ]
+ }
+ ]
+ },
+ ]
+ }
+ ]
+ }
+ ]
+ };
+ testElm("menu1", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:meter
+ obj = {
+ role: ROLE_METER
+ };
+ testElm("meter", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:nav
+
+ obj = {
+ role: ROLE_LANDMARK,
+ attributes: { "xml-roles": "navigation" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("nav", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:object and HTML:param
+
+ if (syntheticBrowsingContexts) {
+ obj = {
+ INTERNAL_FRAME: [ {
+ DOCUMENT: [ {
+ role: ROLE_GRAPHIC,
+ interfaces: [ nsIAccessibleImage ],
+ } ],
+ } ],
+ };
+ } else {
+ obj = {
+ role: ROLE_GRAPHIC,
+ interfaces: [ nsIAccessibleImage ],
+ };
+ }
+ testElm("object_png", obj);
+
+ obj = {
+ INTERNAL_FRAME: [ {
+ DOCUMENT: [ {
+ role: ROLE_PARAGRAPH,
+ } ],
+ } ],
+ };
+ testElm("object_html", obj);
+
+ obj = {
+ INTERNAL_FRAME: [ {
+ DOCUMENT: [ {
+ } ],
+ } ],
+ };
+ testElm("object_pdf", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:select, HTML:optgroup and HTML:option
+
+ obj = { // HMTL:select@size > 1
+ role: ROLE_LISTBOX,
+ states: STATE_FOCUSABLE,
+ absentStates: STATE_MULTISELECTABLE,
+ interfaces: [ nsIAccessibleSelectable ],
+ children: [
+ { GROUPING: [ // HTML:optgroup
+ { role: ROLE_STATICTEXT },
+ { role: ROLE_OPTION }, // HTML:option
+ { role: ROLE_OPTION },
+ ] },
+ {
+ role: ROLE_OPTION,
+ states: STATE_FOCUSABLE,
+ actions: "select",
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ },
+ ],
+ };
+ testElm("select_listbox", obj);
+
+ obj = { // HTML:select@multiple
+ role: ROLE_LISTBOX,
+ states: STATE_FOCUSABLE | STATE_MULTISELECTABLE,
+ children: [
+ { role: ROLE_OPTION },
+ { role: ROLE_OPTION },
+ { role: ROLE_OPTION },
+ ],
+ };
+ testElm("select_listbox_multiselectable", obj);
+
+ obj = { // HTML:select
+ role: ROLE_COMBOBOX,
+ states: STATE_FOCUSABLE,
+ children: [
+ {
+ role: ROLE_COMBOBOX_LIST,
+ children: [
+ { role: ROLE_COMBOBOX_OPTION },
+ { role: ROLE_COMBOBOX_OPTION },
+ { role: ROLE_COMBOBOX_OPTION },
+ ],
+ },
+ ],
+ };
+ testElm("select_combobox", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:output
+
+ obj = {
+ role: ROLE_STATUSBAR,
+ attributes: { "live": "polite" },
+ todo_relations: {
+ RELATION_CONTROLLED_BY: "output_input",
+ },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("output", obj);
+
+ obj = {
+ role: ROLE_ENTRY,
+ relations: {
+ RELATION_CONTROLLER_FOR: "output",
+ },
+ };
+ testElm("output_input", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:pre
+
+ obj = {
+ role: ROLE_TEXT_CONTAINER,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("pre", obj);
+
+ // /////////////////////////////////////////////////////////////////////////
+ // HTML:progress
+
+ obj = {
+ role: ROLE_PROGRESSBAR,
+ absentStates: STATE_MIXED,
+ interfaces: [ nsIAccessibleValue ],
+ };
+ testElm("progress", obj);
+
+ obj = {
+ role: ROLE_PROGRESSBAR,
+ states: STATE_MIXED,
+ };
+ testElm("progress_indeterminate", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:q
+
+ obj = {
+ role: ROLE_TEXT,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [
+ { role: ROLE_STATICTEXT }, // left quote
+ { role: ROLE_TEXT_LEAF }, // quoted text
+ { role: ROLE_STATICTEXT }, // right quote
+ ],
+ };
+ testElm("q", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:ruby
+
+ todo(isAccessible("ruby"), "ruby element is not accessible");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:s contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "text-line-through-style": "solid" },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:i text
+ ],
+ };
+ testElm("s_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:samp contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:samp text
+ ],
+ };
+ testElm("samp_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:section without an accessible name
+
+ obj = {
+ role: ROLE_SECTION,
+ absentAttributes: { "xml-roles": "region" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("section", obj);
+
+ // HTML:section with an accessible name
+
+ obj = {
+ role: ROLE_REGION,
+ attributes: { "xml-roles": "region" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("named_section", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:small contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-size": "10pt" },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:small text
+ ],
+ };
+ testElm("small_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:source
+
+ ok(!isAccessible("source"), "source element is not accessible");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:span
+
+ ok(!isAccessible("span"), "span element is not accessible");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // html:span with a title attribute, which should make it accessible.
+ obj = {
+ role: ROLE_TEXT,
+ };
+ testElm("span_explicit", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:strong contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:strong text
+ ],
+ };
+ testElm("strong_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:sub
+ obj = {
+ role: ROLE_SUBSCRIPT
+ };
+ testElm("sub", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:sup
+ obj = {
+ role: ROLE_SUPERSCRIPT
+ };
+ testElm("sup", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:sub contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ {
+ role: ROLE_SUBSCRIPT, // HTML:sub
+ children: [
+ { role: ROLE_TEXT_LEAF } // HTML:sub text
+ ]
+ }
+ ],
+ };
+ testElm("sub_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:sup contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ {
+ role: ROLE_SUPERSCRIPT, // HTML:sup
+ children: [
+ { role: ROLE_TEXT_LEAF } // HTML:sup text
+ ]
+ }
+ ],
+ };
+ testElm("sup_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:svg
+
+ obj = {
+ todo_role: ROLE_GRAPHIC,
+ };
+ testElm("svg", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:textarea
+
+ obj = {
+ role: ROLE_ENTRY,
+ extraStates: EXT_STATE_MULTI_LINE | EXT_STATE_EDITABLE,
+ actions: "activate",
+ interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ],
+ };
+ testElm("textarea", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:time
+
+ obj = {
+ role: ROLE_TEXT,
+ attributes: { "xml-roles": "time", "datetime": "2001-05-15 19:00" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("time", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:u contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "text-underline-style": "solid" },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:u text
+ ],
+ };
+ testElm("u_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:var contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:var text
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:var text
+ ],
+ };
+ testElm("var_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ obj = { // HTML:video
+ role: ROLE_GROUPING,
+ };
+ testElm("video", obj);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Implement figure and figcaption accessibility"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=658272">
+ Mozilla Bug 658272
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <a id="a_href" href="www.mozilla.com">mozilla site</a>
+ <a id="a_nohref">anchor</a>
+ <table>
+ <tr>
+ <td id="td_abbr"><abbr title="World Wide Web">WWW</abbr></td>
+ </tr>
+ </table>
+ <address id="address">
+ Mozilla Foundation<br>
+ 1981 Landings Drive<br>
+ Building K<br>
+ Mountain View, CA 94043-0801<br>
+ USA
+ </address>
+
+ <map name="atoz_map">
+ <area id="area_href"
+ href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ <area id="area_nohref"
+ coords="0,0,13,14" alt="a" shape="rect">
+ </map>
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="../letters.gif">
+
+ <article id="article">A document</article>
+ <audio id="audio" controls="true">
+ <source id="source" src="../bug461281.ogg" type="video/ogg">
+ </audio>
+
+ <aside id="aside">
+ <p>Some content related to an &lt;article&gt;</p>
+ </aside>
+
+ <p id="b_container">normal<b>bold</b></p>
+ <p id="bdi_container">User <bdi>إيان</bdi>: 90 points</p>
+ <p id="bdo_container"><bdo dir="rtl">This text will go right to left.</bdo></p>
+
+ <blockquote id="blockquote" cite="http://developer.mozilla.org">
+ <p>This is a quotation taken from the Mozilla Developer Center.</p>
+ </blockquote>
+
+ <!-- two BRs, both will be present -->
+ <p id="br_container"><br><br></p>
+
+ <button id="button">button</button>
+ <form>
+ <button id="button_default" type="submit">button</button>
+ </form>
+
+ <canvas id="canvas"></canvas>
+
+ <table id="table">
+ <caption id="caption">caption</caption>
+ <thead>
+ <tr>
+ <th>col1</th><th>col2</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th>col1</th><td>cell2</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td>cell5</td><td>cell6</td>
+ </tr>
+ </tfoot>
+ </table>
+
+ <p id="cite_container">normal<cite>cite</cite></p>
+ <p id="code_container">normal<code>code</code></p>
+
+ <table id="colNcolgroup_table">
+ <colgroup>
+ <col>
+ <col span="2">
+ </colgroup>
+ <tr>
+ <td>Lime</td>
+ <td>Lemon</td>
+ <td>Orange</td>
+ </tr>
+ </table>
+
+ <p id="data_container"><data value="8">Eight</data></p>
+
+ <datalist id="datalist">
+ <summary id="summary">details</summary>
+ <option>Paris</option>
+ <option>San Francisco</option>
+ </datalist>
+ <input id="autocomplete_datalist" list="datalist">
+
+ <dl id="dl">
+ <dt>item1</dt><dd>description</dd>
+ </dl>
+
+ <p id="del_container">normal<del>Removed</del></p>
+
+ <details id="details" open="open">
+ <summary>Information</summary>
+ <p>If your browser supports this element, it should allow you to expand and collapse these details.</p>
+ </details>
+
+ <details id="details_closed">
+ <summary>Information</summary>
+ <p>If your browser supports this element, it should allow you to expand and collapse these details.</p>
+ </details>
+
+ <p id="dfn_container"><dfn id="def-internet">The Internet</dfn> is a global
+ system of interconnected networks that use the Internet Protocol Suite (TCP/IP)
+ to serve billions of users worldwide.</p>
+
+ <dialog id="dialog" open="true">This is a dialog</dialog>
+
+ <div id="div">div</div>
+
+ <p id="em_container">normal<em>emphasis</em></p>
+
+ <embed id="embed_png" type="image/png" src="../moz.png"
+ width="300" height="300">
+ </embed>
+ <embed id="embed_html" type="text/html" src="../longdesc_src.html"
+ width="300" height="300">
+ </embed>
+ <embed id="embed_pdf" type="application/pdf" src="../dummy.pdf"
+ width="300" height="300">
+ </embed>
+
+ <fieldset id="fieldset">
+ <legend id="legend">legend</legend>
+ <input />
+ </fieldset>
+
+ <!-- Depending on whether or not the image is cached, layout may be able to
+ optimize away spaces between the figure, img and figcaption tags. As
+ such, we should keep everything on one line to get consistent results.
+ -->
+ <figure id="figure"><img src="../moz.png" alt="An awesome picture"><figcaption id="figcaption">Caption for the awesome picture</figcaption></figure>
+
+ <footer id="footer">Some copyright info</footer>
+ <article>
+ <footer id="footer_in_article">Some copyright info</footer>
+ </article>
+ <aside>
+ <footer id="footer_in_aside">Some copyright info</footer>
+ </aside>
+ <main>
+ <footer id="footer_in_main">Some copyright info</footer>
+ </main>
+ <nav>
+ <footer id="footer_in_nav">Some copyright info</footer>
+ </nav>
+ <section>
+ <footer id="footer_in_section">Some copyright info</footer>
+ </section>
+ <blockquote>
+ <footer id="footer_in_blockquote">Some copyright info</footer>
+ </blockquote>
+ <details open="true">
+ <footer id="footer_in_details">Some copyright info</footer>
+ </details>
+ <dialog open="true">
+ <footer id="footer_in_dialog">Some copyright info</footer>
+ </dialog>
+ <fieldset>
+ <footer id="footer_in_fieldset">Some copyright info</footer>
+ </fieldset>
+ <figure>
+ <footer id="footer_in_figure">Some copyright info</footer>
+ </figure>
+ <table><tr><td>
+ <footer id="footer_in_td">Some copyright info</footer>
+ </td></tr></table>
+
+ <form id="form"></form>
+ <form id="named_form" aria-label="New form"></form>
+
+ <iframe id="frameset_container"
+ src="data:text/html,<html><frameset><frame src='data:text/html,hi'></frame></frameset></html>">
+ </iframe>
+
+ <h1 id="h1">heading1</h1>
+ <h2 id="h2">heading2</h2>
+ <h3 id="h3">heading3</h3>
+ <h4 id="h4">heading4</h4>
+ <h5 id="h5">heading5</h5>
+ <h6 id="h6">heading6</h6>
+
+ <header id="header">A logo</header>
+ <article>
+ <header id="header_in_article">Not logo</header>
+ <h1 id="h1_in_article">heading1</h1>
+ <h2 id="h2_in_article">heading2</h2>
+ <h3 id="h3_in_article">heading3</h3>
+ <h4 id="h4_in_article">heading4</h4>
+ <h5 id="h5_in_article">heading5</h5>
+ <h6 id="h6_in_article">heading6</h6>
+ <hgroup>
+ <h1 id="h1_in_article_in_hgroup">heading1</h1>
+ <h2 id="h2_in_article_in_hgroup">heading2</h2>
+ <h3 id="h3_in_article_in_hgroup">heading3</h3>
+ <h4 id="h4_in_article_in_hgroup">heading4</h4>
+ <h5 id="h5_in_article_in_hgroup">heading5</h5>
+ <h6 id="h6_in_article_in_hgroup">heading6</h6>
+ </hgroup>
+ </article>
+ <aside>
+ <header id="header_in_aside">Not logo</header>
+ <h1 id="h1_in_aside">heading1</h1>
+ <h2 id="h2_in_aside">heading2</h2>
+ <h3 id="h3_in_aside">heading3</h3>
+ <h4 id="h4_in_aside">heading4</h4>
+ <h5 id="h5_in_aside">heading5</h5>
+ <h6 id="h6_in_aside">heading6</h6>
+ <hgroup>
+ <h1 id="h1_in_aside_in_hgroup">heading1</h1>
+ <h2 id="h2_in_aside_in_hgroup">heading2</h2>
+ <h3 id="h3_in_aside_in_hgroup">heading3</h3>
+ <h4 id="h4_in_aside_in_hgroup">heading4</h4>
+ <h5 id="h5_in_aside_in_hgroup">heading5</h5>
+ <h6 id="h6_in_aside_in_hgroup">heading6</h6>
+ </hgroup>
+ </aside>
+ <main>
+ <header id="header_in_main">Not logo</header>
+ </main>
+ <nav>
+ <header id="header_in_nav">Not logo</header>
+ <h1 id="h1_in_nav">heading1</h1>
+ <h2 id="h2_in_nav">heading2</h2>
+ <h3 id="h3_in_nav">heading3</h3>
+ <h4 id="h4_in_nav">heading4</h4>
+ <h5 id="h5_in_nav">heading5</h5>
+ <h6 id="h6_in_nav">heading6</h6>
+ <hgroup>
+ <h1 id="h1_in_nav_in_hgroup">heading1</h1>
+ <h2 id="h2_in_nav_in_hgroup">heading2</h2>
+ <h3 id="h3_in_nav_in_hgroup">heading3</h3>
+ <h4 id="h4_in_nav_in_hgroup">heading4</h4>
+ <h5 id="h5_in_nav_in_hgroup">heading5</h5>
+ <h6 id="h6_in_nav_in_hgroup">heading6</h6>
+ </hgroup>
+ </nav>
+ <section>
+ <header id="header_in_section">Not logo</header>
+ <h1 id="h1_in_section">heading1</h1>
+ <h2 id="h2_in_section">heading2</h2>
+ <h3 id="h3_in_section">heading3</h3>
+ <h4 id="h4_in_section">heading4</h4>
+ <h5 id="h5_in_section">heading5</h5>
+ <h6 id="h6_in_section">heading6</h6>
+ <hgroup>
+ <h1 id="h1_in_section_in_hgroup">heading1</h1>
+ <h2 id="h2_in_section_in_hgroup">heading2</h2>
+ <h3 id="h3_in_section_in_hgroup">heading3</h3>
+ <h4 id="h4_in_section_in_hgroup">heading4</h4>
+ <h5 id="h5_in_section_in_hgroup">heading5</h5>
+ <h6 id="h6_in_section_in_hgroup">heading6</h6>
+ </hgroup>
+ </section>
+ <blockquote>
+ <header id="header_in_blockquote">Not logo</header>
+ </blockquote>
+ <details open="true">
+ <header id="header_in_details">Not logo</header>
+ </details>
+ <dialog open="true">
+ <header id="header_in_dialog">Not logo</header>
+ </dialog>
+ <fieldset>
+ <header id="header_in_fieldset">Not logo</header>
+ </fieldset>
+ <figure>
+ <header id="header_in_figure">Not logo</header>
+ </figure>
+ <table><tr><td>
+ <header id="header_in_td">Not logo</header>
+ </td></tr></table>
+
+ <hr id="hr">
+ <p id="i_container">normal<i>italic</i></p>
+ <img id="img" src="../moz.png">
+
+ <input id="input_button" type="button" value="Button">
+ <input id="input_checkbox" type="checkbox">
+ <input id="input_checkbox_true" type="checkbox" checked>
+ <input id="input_file" type="file">
+ <input id="input_image" type="image">
+ <input id="input_image_display" type="image" style="display: block">
+ <form>
+ <input id="input_image_default" type="image">
+ </form>
+ <input id="input_submit" type="submit">
+ <form>
+ <input id="input_submit_default" type="submit">
+ </form>
+ <input id="input_number" type="number" value="44">
+ <input id="input_text" type="text" value="hi">
+ <form>
+ <label for="input_placeholder_same">Your name</label>
+ <input id="input_placeholder_same" placeholder="Your name"/>
+ <label for="input_placeholder_different">First name:</label>
+ <input id="input_placeholder_different" placeholder="Enter your first name"/>
+ <input id="input_placeholder_only" placeholder="Date of birth"/>
+ </form>
+ <input id="input_search" type="search" value="cats">
+ <input id="input_email" type="email" value="me@mozilla.com">
+ <input id="input_tel" type="tel" value="111.111.1111">
+ <input id="input_url" type="url" value="www.mozilla.com">
+ <input id="input_password" type="password" value="44">
+ <input id="input_password_readonly" type="password" value="44" readonly>
+ <input id="input_radio" type="radio">
+ <input id="input_radio_true" type="radio" checked>
+ <input id="input_range" type="range">
+ <form>
+ <input id="input_reset" type="reset">
+ </form>
+ <label>time label
+ <input id="input_time" type="time" value="23:23">
+ </label>
+ <label>date label
+ <input id="input_date" type="date" value="2017-11-10">
+ </label>
+ <label>datetime-local label
+ <input id="input_datetime_local" type="datetime-local" value="2017-11-10T23:23">
+ </label>
+
+ <p id="ins_container">normal<ins>Inserted</ins></p>
+ <p id="kbd_container">normal<kbd>cmd</kbd></p>
+
+ <label id="label">label<input id="label_input"></label>
+ <label id="label_for" for="label_for_input">label</label>
+ <input id="label_for_input">
+
+ <ul id="ul">
+ <li>item1</li>
+ </ul>
+ <ol id="ol">
+ <li>item1</li>
+ </ol>
+
+ <main id="main">main</main>
+
+ <map id="map_imagemap" name="atoz_map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,13,14" alt="a" shape="rect">
+ </map>
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="../letters.gif">
+
+ <map id="map" title="Navigation Bar" name="mapgroup">
+ <p>
+ [<a href="#how">Bypass navigation bar</a>]
+ [<a href="home.html">Home</a>]
+ </p>
+ </map>
+
+ <p id="mark_container">normal<mark>highlighted</mark></p>
+
+ <math id="math">
+ <mrow>
+ <mrow>
+ <msup>
+ <mi>a</mi>
+ <mn>2</mn>
+ </msup>
+ <mo>+</mo>
+ <msup>
+ <mi>b</mi>
+ <mn>2</mn>
+ </msup>
+ </mrow>
+ <mo>=</mo>
+ <msup>
+ <mi>c</mi>
+ <mn>2</mn>
+ </msup>
+ </mrow>
+ </math>
+
+ <menu id="menu">
+ <li>Home</li>
+ <li>About
+ <menu>
+ <li>Our Story</li>
+ </menu>
+ </li>
+ </menu>
+
+ <menu id="menu1">
+ <li>
+ <button>File</button>
+ <menu>
+ <li>
+ <button type="button" onclick="new()">New...</button>
+ </li>
+ </menu>
+ </li>
+ </menu>
+
+ <meter id="meter" min="0" max="1000" low="300" high="700" value="200">200 Euro</meter>
+
+ <nav id="nav">
+ <ul>
+ <li><a href="#">Home</a></li>
+ <li><a href="#">About</a></li>
+ <li><a href="#">Contact</a></li>
+ </ul>
+ </nav>
+
+ <object id="object_png" type="image/png" data="../moz.png"
+ width="300" height="300">
+ </object>
+ <object id="object_html" type="text/html" data="../longdesc_src.html"
+ width="300" height="300">
+ </object>
+ <object id="object_pdf" type="application/pdf" data="../dummy.pdf"
+ width="300" height="300">
+ </object>
+
+ <select id="select_listbox" size="4">
+ <optgroup label="Colors">
+ <option>Red</option>
+ <option>Blue</option>
+ </optgroup>
+ <option>Animal</option>
+ </select>
+
+ <select id="select_listbox_multiselectable" multiple>
+ <option>Red</option>
+ <option>Blue</option>
+ <option>Green</option>
+ </select>
+
+ <select id="select_combobox">
+ <option>Red</option>
+ <option>Blue</option>
+ <option>Green</option>
+ </select>
+
+ <input id="output_input">
+ <output id="output" for="output_input"></output>
+
+ <pre id="pre">pre</pre>
+
+ <progress id="progress" min="0" value="21" max="42"></progress>
+ <progress id="progress_indeterminate"></progress>
+
+ <q id="q" cite="http://en.wikipedia.org/wiki/Kenny_McCormick#Cultural_impact">
+ Oh my God, they killed Kenny!
+ </q>
+
+ <ruby id="ruby">
+ 漢 <rp>(</rp><rt>Kan</rt><rp>)</rp>
+ 字 <rp>(</rp><rt>ji</rt><rp>)</rp>
+ </ruby>
+
+ <p id="s_container">normal<s>striked</s></p>
+ <p id="samp_container">normal<samp>sample</samp></p>
+ <section id="section">section</section>
+ <section id="named_section" aria-label="foo">named section</section>
+ <p id="small_container">normal<small>small</small></p>
+ <span id="span"></span>
+ <span id="span_explicit" title="explicit"></span>
+ <p id="strong_container">normal<strong>strong</strong></p>
+ <sub id="sub"></sub>
+ <sup id="sup"></sup>
+ <p id="sub_container">normal<sub>sub</sub></p>
+ <p id="sup_container">normal<sup>sup</sup></p>
+
+ <svg id="svg"></svg>
+ <textarea id="textarea"></textarea>
+
+ <p>The concert took place on <time id="time" datetime="2001-05-15 19:00">May 15</time></p>
+ <p id="u_container">normal<u>underline</u></p>
+ <p id="var_container">An equation: <var>x</var> = <var>y</var></p>
+
+ <video id="video" controls="true">
+ <source id="source" src="../bug461281.ogg" type="video/ogg">
+ </video>
+
+</video>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/elm/test_MathMLSpec.html b/accessible/tests/mochitest/elm/test_MathMLSpec.html
new file mode 100644
index 0000000000..a55c77668a
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_MathMLSpec.html
@@ -0,0 +1,616 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML a11y spec tests</title>
+ <link id="link" rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // math
+
+ let obj = {
+ role: ROLE_MATHML_MATH,
+ };
+ testElm("math", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mi
+
+ obj = {
+ role: ROLE_MATHML_IDENTIFIER,
+ };
+ testElm("mi", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mn
+
+ obj = {
+ role: ROLE_MATHML_NUMBER,
+ };
+ testElm("mn", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mo
+
+ obj = {
+ role: ROLE_MATHML_OPERATOR,
+ attributes: { accent: "true", largeop: "true" },
+ };
+ testElm("mo", obj);
+
+ obj = {
+ role: ROLE_MATHML_OPERATOR,
+ attributes: { fence: "true" },
+ };
+ testElm("mo_fence", obj);
+
+ obj = {
+ role: ROLE_MATHML_OPERATOR,
+ attributes: { separator: "true" },
+ };
+ testElm("mo_separator", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mtext
+
+ obj = {
+ role: ROLE_MATHML_TEXT,
+ };
+ testElm("mtext", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ms
+
+ obj = {
+ role: ROLE_MATHML_STRING_LITERAL,
+ };
+ testElm("ms", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mglyph
+
+ obj = {
+ role: ROLE_MATHML_GLYPH,
+ };
+ testElm("mglyph", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mrow
+
+ obj = {
+ role: ROLE_MATHML_ROW,
+ };
+ testElm("mrow", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mfrac
+
+ obj = {
+ role: ROLE_MATHML_FRACTION,
+ attributes: { bevelled: "true", linethickness: "thick" },
+ };
+ testElm("mfrac", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // msqrt
+
+ obj = {
+ role: ROLE_MATHML_SQUARE_ROOT,
+ };
+ testElm("msqrt", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mroot
+
+ obj = {
+ role: ROLE_MATHML_ROOT,
+ relations: {
+ RELATION_NODE_PARENT_OF: ["mroot_index", "mroot_base"],
+ },
+ children: [
+ {
+ role: ROLE_MATHML_IDENTIFIER,
+ relations: { RELATION_NODE_CHILD_OF: "mroot" },
+ },
+ {
+ role: ROLE_MATHML_NUMBER,
+ relations: { RELATION_NODE_CHILD_OF: "mroot" },
+ },
+ ],
+ };
+ testElm("mroot", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Deprecated mfenced element (treated as an mrow).
+
+ obj = {
+ role: ROLE_MATHML_ROW,
+ };
+ testElm("mfenced", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // menclose
+
+ obj = {
+ role: ROLE_MATHML_ENCLOSED,
+ attributes: { notation: "circle" },
+ };
+ testElm("menclose", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mstyle, mpadded, mphantom
+
+ obj = {
+ role: ROLE_MATHML_STYLE,
+ };
+ testElm("mstyle", obj);
+
+ ok(!isAccessible("mpadded"), "mpadded should not have accessible");
+ ok(!isAccessible("mphantom"), "mphantom should not have accessible");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // msub
+
+ obj = {
+ role: ROLE_MATHML_SUB,
+ };
+ testElm("msub", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // msup
+
+ obj = {
+ role: ROLE_MATHML_SUP,
+ };
+ testElm("msup", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // msubsup
+
+ obj = {
+ role: ROLE_MATHML_SUB_SUP,
+ };
+ testElm("msubsup", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // munder
+
+ obj = {
+ role: ROLE_MATHML_UNDER,
+ attributes: { accentunder: "true", align: "center" },
+ };
+ testElm("munder", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mover
+
+ obj = {
+ role: ROLE_MATHML_OVER,
+ attributes: { accent: "true", align: "center" },
+ };
+ testElm("mover", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // munderover
+
+ obj = {
+ role: ROLE_MATHML_UNDER_OVER,
+ attributes: { accent: "true", accentunder: "true", align: "center" },
+ };
+ testElm("munderover", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mmultiscripts
+
+ obj = {
+ role: ROLE_MATHML_MULTISCRIPTS,
+ };
+ testElm("mmultiscripts", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mtable
+
+ obj = {
+ role: ROLE_MATHML_TABLE,
+ attributes: { align: "center", columnlines: "solid", rowlines: "solid" },
+ };
+ testElm("mtable", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mlabeledtr
+
+ obj = {
+ role: ROLE_MATHML_LABELED_ROW,
+ };
+ testElm("mlabeledtr", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mtr
+
+ obj = {
+ role: ROLE_MATHML_TABLE_ROW,
+ };
+ testElm("mtr", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mtd
+
+ obj = {
+ role: ROLE_MATHML_CELL,
+ };
+ testElm("mtd", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // maction
+
+ obj = {
+ role: ROLE_MATHML_ACTION,
+ attributes: { actiontype: "toggle", selection: "1" },
+ };
+ testElm("maction", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // merror
+
+ obj = {
+ role: ROLE_MATHML_ERROR,
+ };
+ testElm("merror", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // semantics, annotation, annotation-xml
+ ok(!isAccessible("semantics"), "semantics should not have accessible");
+ ok(!isAccessible("annotation"), "annotation should not have accessible");
+ ok(!isAccessible("annotation-xml"), "annotation-xml should not have accessible");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mstack
+
+ obj = {
+ role: ROLE_MATHML_STACK,
+ attributes: { align: "center" },
+ };
+ testElm("mstack", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mlongdiv
+
+ obj = {
+ role: ROLE_MATHML_LONG_DIVISION,
+ attributes: { longdivstyle: "stackedrightright" },
+ };
+ testElm("mlongdiv", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // msgroup
+
+ obj = {
+ role: ROLE_MATHML_STACK_GROUP,
+ attributes: { position: "2", shift: "-1" },
+ };
+ testElm("msgroup", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // msrow
+
+ obj = {
+ role: ROLE_MATHML_STACK_ROW,
+ attributes: { position: "1" },
+ };
+ testElm("msrow", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mscarries
+
+ obj = {
+ role: ROLE_MATHML_STACK_CARRIES,
+ attributes: { location: "nw", position: "1" },
+ };
+ testElm("mscarries", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mscarry
+
+ obj = {
+ role: ROLE_MATHML_STACK_CARRY,
+ attributes: { crossout: "updiagonalstrike" },
+ };
+ testElm("mscarry", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // msline
+
+ obj = {
+ role: ROLE_MATHML_STACK_LINE,
+ attributes: { position: "1" },
+ };
+ testElm("msline", obj);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Implement figure and figcaption accessibility"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=658272">
+ Mozilla Bug 658272
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <math id="math">
+ <mrow id="mrow">
+ <mrow>
+ <msup id="msup">
+ <mi id="mi">a</mi>
+ <mn id="mn">2</mn>
+ </msup>
+ <mo id="mo" accent="true" largeop="true">+</mo>
+ <msqrt id="msqrt">
+ <mn>2</mn>
+ </msqrt>
+ </mrow>
+ <mo>=</mo>
+ <msub id="msub">
+ <mi>c</mi>
+ <mn>2</mn>
+ </msub>
+ </mrow>
+ <mspace id="mspace" width="1em"/>
+ <mtext id="mtext">Arbitrary text</mtext>
+ <mspace width="1em"/>
+ <ms id="ms">InterpretedStringLiteral</ms>
+ <mi>
+ <mglyph id="mglyph" src="../letters.gif" alt="letters"/>
+ </mi>
+ <mfrac id="mfrac" bevelled="true" linethickness="thick">
+ <mi>x</mi>
+ <mn>2</mn>
+ </mfrac>
+ <mroot id="mroot">
+ <mi id="mroot_base">x</mi>
+ <mn id="mroot_index">5</mn>
+ </mroot>
+ <mspace width="1em"/>
+ <mfenced id="mfenced" close="[" open="]" separators=".">
+ <mrow>
+ <mi>x</mi>
+ <mi>y</mi>
+ </mrow>
+ </mfenced>
+ <mrow>
+ <mo id="mo_fence" fence="true">[</mo>
+ <mrow>
+ X
+ <mo id="mo_separator" separator="true">,</mo>
+ Y
+ </mrow>
+ <mo fence="true"> closing-fence </mo>
+ </mrow>
+ <mspace width="1em"/>
+ <menclose id="menclose" notation="circle">
+ <mi>a</mi>
+ <mo>+</mo>
+ <mi>b</mi>
+ </menclose>
+ <mstyle id="mstyle" dir="rtl" mathcolor="blue">
+ <mpadded id="mpadded" height="100px" width="200px">
+ <mi>x</mi>
+ <mphantom id="mphantom">
+ <mo>+</mo>
+ <mi>y</mi>
+ </mphantom>
+ </mpadded>
+ </mstyle>
+
+ <msubsup id="msubsup">
+ <mi>b</mi>
+ <mn>1</mn>
+ <mn>2</mn>
+ </msubsup>
+ <munder id="munder" accentunder="true" align="center">
+ <mrow>
+ <mi> x </mi>
+ <mo> + </mo>
+ <mi> y </mi>
+ <mo> + </mo>
+ <mi> z </mi>
+ </mrow>
+ <mo> &#x23DF;<!--BOTTOM CURLY BRACKET--> </mo>
+ </munder>
+ <mspace width="1em"/>
+ <mover id="mover" accent="true" align="center">
+ <mi> x </mi>
+ <mo> &#x5E;<!--CIRCUMFLEX ACCENT--> </mo>
+ </mover>
+ <munderover id="munderover" accentunder="true" accent="true" align="center">
+ <mo> &#x222B;<!--INTEGRAL--> </mo>
+ <mn> 0 </mn>
+ <mi> &#x221E;<!--INFINITY--> </mi>
+ </munderover>
+ <mmultiscripts id="mmultiscripts">
+ <mi> R </mi>
+ <mi> i </mi>
+ <none/>
+ <none/>
+ <mi> j </mi>
+ <mi> k </mi>
+ <none/>
+ <mi> l </mi>
+ <none/>
+ </mmultiscripts>
+
+ <mtable id="mtable" align="center" columnlines="solid" rowlines="solid">
+ <mlabeledtr id="mlabeledtr">
+ <mtd>
+ <mtext> (2.1) </mtext>
+ </mtd>
+ <mtd>
+ <mrow>
+ <mi>E</mi>
+ <mo>=</mo>
+ <mrow>
+ <mi>m</mi>
+ <mo>&#x2062;<!--INVISIBLE TIMES--></mo>
+ <msup>
+ <mi>c</mi>
+ <mn>2</mn>
+ </msup>
+ </mrow>
+ </mrow>
+ </mtd>
+ </mlabeledtr>
+ </mtable>
+ <mrow>
+ <mo> ( </mo>
+ <mtable>
+ <mtr id="mtr">
+ <mtd id="mtd"> <mn>1</mn> </mtd>
+ <mtd> <mn>0</mn> </mtd>
+ <mtd> <mn>0</mn> </mtd>
+ </mtr>
+ <mtr>
+ <mtd> <mn>0</mn> </mtd>
+ <mtd> <mn>1</mn> </mtd>
+ <mtd> <mn>0</mn> </mtd>
+ </mtr>
+ <mtr>
+ <mtd> <mn>0</mn> </mtd>
+ <mtd> <mn>0</mn> </mtd>
+ <mtd> <mn>1</mn> </mtd>
+ </mtr>
+ </mtable>
+ <mo> ) </mo>
+ </mrow>
+
+ <maction id="maction" actiontype="toggle" selection="1">
+ <mfrac>
+ <mn>6</mn>
+ <mn>8</mn>
+ </mfrac>
+ <mfrac>
+ <mrow>
+ <mn>3</mn>
+ <mo>⋅</mo>
+ <mn>2</mn>
+ </mrow>
+ <mrow>
+ <mn>4</mn>
+ <mo>⋅</mo>
+ <mn>2</mn>
+ </mrow>
+ </mfrac>
+ <mfrac>
+ <mn>3</mn>
+ <mn>4</mn>
+ </mfrac>
+ </maction>
+
+ <merror id="merror">
+ <mrow>
+ <mtext>Division by zero: </mtext>
+ <mfrac>
+ <mn>1</mn>
+ <mn>0</mn>
+ </mfrac>
+ </mrow>
+ </merror>
+
+ <semantics id="semantics">
+ <!-- Presentation MathML -->
+ <mrow>
+ <msup>
+ <mi>x</mi>
+ <mn>2</mn>
+ </msup>
+ <mo>+</mo>
+ <mi>y</mi>
+ </mrow>
+ <!-- Content MathML -->
+ <annotation-xml id="annotation-xml" encoding="MathML-Content">
+ <apply>
+ <plus/>
+ <apply>
+ <power/>
+ <ci>x</ci>
+ <cn type="integer">2</cn>
+ </apply>
+ <ci>y</ci>
+ </apply>
+ </annotation-xml>
+ <!-- annotate TeX -->
+ <annotation id="annotation" encoding="application/x-tex">
+ x^{2} + y
+ </annotation>
+ </semantics>
+
+ <mstack id="mstack" align="center">
+ <mscarries id="mscarries" location="nw" position="1">
+ <none/>
+ <mscarry id="mscarry" crossout="updiagonalstrike">
+ <mn>1</mn>
+ </mscarry>
+ <mscarry location="w">
+ <mn>1</mn>
+ </mscarry>
+ </mscarries>
+ <mn>523</mn>
+ <msrow id="msrow" position="1">
+ <mo>-</mo>
+ <none/>
+ <mn>15</mn>
+ </msrow>
+ <msline id="msline" position="1"/>
+ <mn>508</mn>
+ </mstack>
+ <mspace width="1em"/>
+ <mlongdiv id="mlongdiv" longdivstyle="stackedrightright">
+ <mn>5</mn>
+ <mn>1</mn>
+ <mn>5</mn>
+ </mlongdiv>
+
+ <mstack>
+ <msgroup id="msgroup" position="2" shift="-1">
+ <mn>123</mn>
+ <msrow><mo>&#xD7;<!--MULTIPLICATION SIGN--></mo><mn>321</mn></msrow>
+ </msgroup>
+ <msline/>
+ <msgroup shift="1">
+ <mn>123</mn>
+ <mn>246</mn>
+ <mn>369</mn>
+ </msgroup>
+ <msline/>
+ </mstack>
+ </math>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/elm/test_figure.html b/accessible/tests/mochitest/elm/test_figure.html
new file mode 100644
index 0000000000..82ac961e36
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_figure.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 figure/figcaption tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ testRole("figure", ROLE_FIGURE);
+ testRole("figcaption", ROLE_CAPTION);
+
+ todo(false, "figure name gets extra whitespace in the end!");
+ testName("figure", "figure caption");
+ testName("figcaption", null);
+
+ testRelation("figure", RELATION_LABELLED_BY, "figcaption");
+ testRelation("figcaption", RELATION_LABEL_FOR, "figure");
+
+ testAttrs("figure", {"xml-roles": "figure"}, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Implement figure and figcaption accessibility"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=658272">
+ Mozilla Bug 658272
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <figure id="figure">
+ <figcaption id="figcaption">figure caption</figcaption>
+ </figure>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/elm/test_listbox.xhtml b/accessible/tests/mochitest/elm/test_listbox.xhtml
new file mode 100644
index 0000000000..2315959e3a
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_listbox.xhtml
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL listbox element test.">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ var id = "";
+ var acc = null;
+
+ //////////////////////////////////////////////////////////////////////////
+ // Simple listbox. There is no nsIAccessibleTable interface.
+
+ id = "listbox1";
+ acc = getAccessible(id);
+
+ // query nsIAccessibleTable
+ try {
+ acc.QueryInterface(nsIAccessibleTable);
+ ok(false,
+ id + " shouldn't implement nsIAccessibleTable interface.");
+ } catch(e) {
+ ok(true, id + " doesn't implement nsIAccessibleTable interface.");
+ }
+
+ // role
+ testRole(id, ROLE_LISTBOX);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=418371"
+ title="implement the rest of methods of nsIAccessibleTable on xul:listbox">
+ Mozilla Bug 418371
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <label control="listbox1" value="listbox: "/>
+ <richlistbox id="listbox1">
+ <richlistitem id="item1"><label value="item1"/></richlistitem>
+ <richlistitem id="item1"><label value="item2"/></richlistitem>
+ </richlistbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/elm/test_nsApplicationAcc.html b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html
new file mode 100644
index 0000000000..2e7aabf882
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html
@@ -0,0 +1,67 @@
+<html>
+
+<head>
+ <title>application accessible name</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var accessible = getApplicationAccessible();
+ if (!accessible) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var brandBundle =
+ Services.strings.createBundle("chrome://branding/locale/brand.properties");
+
+ // nsIAccessible::name
+ var applicationName = "";
+ if (LINUX || SOLARIS) {
+ applicationName = Services.appinfo.name;
+ } else {
+ try {
+ applicationName = brandBundle.GetStringFromName("brandShortName");
+ } catch (e) {
+ }
+
+ if (applicationName == "")
+ applicationName = "Gecko based application";
+ }
+ is(accessible.name, applicationName, "wrong application accessible name");
+
+ // nsIAccessibleApplication
+ is(accessible.appName, Services.appinfo.name, "Wrong application name");
+ is(accessible.appVersion, Services.appinfo.version, "Wrong application version");
+ is(accessible.platformName, "Gecko", "Wrong platform name");
+ is(accessible.platformVersion, Services.appinfo.platformVersion,
+ "Wrong platform version");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+ </head>
+ <body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=456121"
+ title="nsApplicationAccessible::GetName does not return a default value when brand.properties does not exist">
+ Mozilla Bug 454211
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/elm/test_shadowroot.html b/accessible/tests/mochitest/elm/test_shadowroot.html
new file mode 100644
index 0000000000..bc221090b4
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_shadowroot.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>ShadowRoot tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ title="Ensure accessible objects are created for shadow root"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1026125">
+ Mozilla Bug 1026125
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ window.onload = () => {
+ var iframe = document.createElement("iframe");
+ iframe.src = "test_shadowroot_subframe.html";
+ document.body.appendChild(iframe);
+ };
+
+ </script>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/elm/test_shadowroot_subframe.html b/accessible/tests/mochitest/elm/test_shadowroot_subframe.html
new file mode 100644
index 0000000000..20e2baf681
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_shadowroot_subframe.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>ShadowRoot tests</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../role.js"></script>
+
+ <script type="application/javascript">
+ let SimpleTest = window.parent.SimpleTest;
+ let ok = window.parent.ok;
+ let is = window.parent.is;
+
+ function doTest() {
+ testElm("component", {
+ role: ROLE_GROUPING,
+ children: [
+ {
+ role: ROLE_PUSHBUTTON,
+ },
+ {
+ role: ROLE_LINK,
+ },
+ ],
+ });
+
+ // Shadow root boundary between table and row
+ testElm("table", {
+ role: ROLE_TABLE,
+ children: [
+ {
+ role: ROLE_ROW,
+ },
+ ],
+ });
+
+ SimpleTest.finish();
+ }
+
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+<body>
+ <div role="group" id="component"></div>
+ <div id="table" role="table" style="display: table;"></div>
+
+ <script>
+ var component = document.getElementById("component");
+ var shadow = component.attachShadow({mode: "open"});
+
+ var button = document.createElement("button");
+ button.append("Hello");
+
+ var a = document.createElement("a");
+ a.setAttribute("href", "#");
+ a.append(" World");
+
+ shadow.appendChild(button);
+ shadow.appendChild(a);
+
+ var table = document.getElementById("table");
+ shadow = table.attachShadow({mode: "open"});
+ shadow.innerHTML = "<div style='display: table-row' role='row'>" +
+ "<div style='display: table-cell' role='cell'>hi</div>" +
+ "</div>";
+ </script>
+</body>
diff --git a/accessible/tests/mochitest/events.js b/accessible/tests/mochitest/events.js
new file mode 100644
index 0000000000..a6c216e01d
--- /dev/null
+++ b/accessible/tests/mochitest/events.js
@@ -0,0 +1,2660 @@
+/* import-globals-from common.js */
+/* import-globals-from states.js */
+/* import-globals-from text.js */
+
+// XXX Bug 1425371 - enable no-redeclare and fix the issues with the tests.
+/* eslint-disable no-redeclare */
+
+// //////////////////////////////////////////////////////////////////////////////
+// Constants
+
+const EVENT_ALERT = nsIAccessibleEvent.EVENT_ALERT;
+const EVENT_ANNOUNCEMENT = nsIAccessibleEvent.EVENT_ANNOUNCEMENT;
+const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE;
+const EVENT_DOCUMENT_LOAD_COMPLETE =
+ nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
+const EVENT_DOCUMENT_RELOAD = nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD;
+const EVENT_DOCUMENT_LOAD_STOPPED =
+ nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_STOPPED;
+const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
+const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
+const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
+const EVENT_MENU_START = nsIAccessibleEvent.EVENT_MENU_START;
+const EVENT_MENU_END = nsIAccessibleEvent.EVENT_MENU_END;
+const EVENT_MENUPOPUP_START = nsIAccessibleEvent.EVENT_MENUPOPUP_START;
+const EVENT_MENUPOPUP_END = nsIAccessibleEvent.EVENT_MENUPOPUP_END;
+const EVENT_OBJECT_ATTRIBUTE_CHANGED =
+ nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED;
+const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
+const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START;
+const EVENT_SELECTION = nsIAccessibleEvent.EVENT_SELECTION;
+const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD;
+const EVENT_SELECTION_REMOVE = nsIAccessibleEvent.EVENT_SELECTION_REMOVE;
+const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN;
+const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
+const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
+const EVENT_TEXT_ATTRIBUTE_CHANGED =
+ nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED;
+const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
+const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
+const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
+const EVENT_TEXT_SELECTION_CHANGED =
+ nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED;
+const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
+const EVENT_TEXT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_TEXT_VALUE_CHANGE;
+const EVENT_VIRTUALCURSOR_CHANGED =
+ nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED;
+
+const kNotFromUserInput = 0;
+const kFromUserInput = 1;
+
+// //////////////////////////////////////////////////////////////////////////////
+// General
+
+/**
+ * Set up this variable to dump events into DOM.
+ */
+var gA11yEventDumpID = "";
+
+/**
+ * Set up this variable to dump event processing into console.
+ */
+var gA11yEventDumpToConsole = false;
+
+/**
+ * Set up this variable to dump event processing into error console.
+ */
+var gA11yEventDumpToAppConsole = false;
+
+/**
+ * Semicolon separated set of logging features.
+ */
+var gA11yEventDumpFeature = "";
+
+/**
+ * Function to detect HTML elements when given a node.
+ */
+function isHTMLElement(aNode) {
+ return (
+ aNode.nodeType == aNode.ELEMENT_NODE &&
+ aNode.namespaceURI == "http://www.w3.org/1999/xhtml"
+ );
+}
+
+function isXULElement(aNode) {
+ return (
+ aNode.nodeType == aNode.ELEMENT_NODE &&
+ aNode.namespaceURI ==
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ );
+}
+
+/**
+ * Executes the function when requested event is handled.
+ *
+ * @param aEventType [in] event type
+ * @param aTarget [in] event target
+ * @param aFunc [in] function to call when event is handled
+ * @param aContext [in, optional] object in which context the function is
+ * called
+ * @param aArg1 [in, optional] argument passed into the function
+ * @param aArg2 [in, optional] argument passed into the function
+ */
+function waitForEvent(
+ aEventType,
+ aTargetOrFunc,
+ aFunc,
+ aContext,
+ aArg1,
+ aArg2
+) {
+ var handler = {
+ handleEvent: function handleEvent(aEvent) {
+ var target = aTargetOrFunc;
+ if (typeof aTargetOrFunc == "function") {
+ target = aTargetOrFunc.call();
+ }
+
+ if (target) {
+ if (target instanceof nsIAccessible && target != aEvent.accessible) {
+ return;
+ }
+
+ if (Node.isInstance(target) && target != aEvent.DOMNode) {
+ return;
+ }
+ }
+
+ unregisterA11yEventListener(aEventType, this);
+
+ window.setTimeout(function () {
+ aFunc.call(aContext, aArg1, aArg2);
+ }, 0);
+ },
+ };
+
+ registerA11yEventListener(aEventType, handler);
+}
+
+/**
+ * Generate mouse move over image map what creates image map accessible (async).
+ * See waitForImageMap() function.
+ */
+function waveOverImageMap(aImageMapID) {
+ var imageMapNode = getNode(aImageMapID);
+ synthesizeMouse(
+ imageMapNode,
+ 10,
+ 10,
+ { type: "mousemove" },
+ imageMapNode.ownerGlobal
+ );
+}
+
+/**
+ * Call the given function when the tree of the given image map is built.
+ */
+function waitForImageMap(aImageMapID, aTestFunc) {
+ waveOverImageMap(aImageMapID);
+
+ var imageMapAcc = getAccessible(aImageMapID);
+ if (imageMapAcc.firstChild) {
+ aTestFunc();
+ return;
+ }
+
+ waitForEvent(EVENT_REORDER, imageMapAcc, aTestFunc);
+}
+
+/**
+ * Register accessibility event listener.
+ *
+ * @param aEventType the accessible event type (see nsIAccessibleEvent for
+ * available constants).
+ * @param aEventHandler event listener object, when accessible event of the
+ * given type is handled then 'handleEvent' method of
+ * this object is invoked with nsIAccessibleEvent object
+ * as the first argument.
+ */
+function registerA11yEventListener(aEventType, aEventHandler) {
+ listenA11yEvents(true);
+ addA11yEventListener(aEventType, aEventHandler);
+}
+
+/**
+ * Unregister accessibility event listener. Must be called for every registered
+ * event listener (see registerA11yEventListener() function) when the listener
+ * is not needed.
+ */
+function unregisterA11yEventListener(aEventType, aEventHandler) {
+ removeA11yEventListener(aEventType, aEventHandler);
+ listenA11yEvents(false);
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Event queue
+
+/**
+ * Return value of invoke method of invoker object. Indicates invoker was unable
+ * to prepare action.
+ */
+const INVOKER_ACTION_FAILED = 1;
+
+/**
+ * Return value of eventQueue.onFinish. Indicates eventQueue should not finish
+ * tests.
+ */
+const DO_NOT_FINISH_TEST = 1;
+
+/**
+ * Creates event queue for the given event type. The queue consists of invoker
+ * objects, each of them generates the event of the event type. When queue is
+ * started then every invoker object is asked to generate event after timeout.
+ * When event is caught then current invoker object is asked to check whether
+ * event was handled correctly.
+ *
+ * Invoker interface is:
+ *
+ * var invoker = {
+ * // Generates accessible event or event sequence. If returns
+ * // INVOKER_ACTION_FAILED constant then stop tests.
+ * invoke: function(){},
+ *
+ * // [optional] Invoker's check of handled event for correctness.
+ * check: function(aEvent){},
+ *
+ * // [optional] Invoker's check before the next invoker is proceeded.
+ * finalCheck: function(aEvent){},
+ *
+ * // [optional] Is called when event of any registered type is handled.
+ * debugCheck: function(aEvent){},
+ *
+ * // [ignored if 'eventSeq' is defined] DOM node event is generated for
+ * // (used in the case when invoker expects single event).
+ * DOMNode getter: function() {},
+ *
+ * // [optional] if true then event sequences are ignored (no failure if
+ * // sequences are empty). Use you need to invoke an action, do some check
+ * // after timeout and proceed a next invoker.
+ * noEventsOnAction getter: function() {},
+ *
+ * // Array of checker objects defining expected events on invoker's action.
+ * //
+ * // Checker object interface:
+ * //
+ * // var checker = {
+ * // * DOM or a11y event type. *
+ * // type getter: function() {},
+ * //
+ * // * DOM node or accessible. *
+ * // target getter: function() {},
+ * //
+ * // * DOM event phase (false - bubbling). *
+ * // phase getter: function() {},
+ * //
+ * // * Callback, called to match handled event. *
+ * // match : function(aEvent) {},
+ * //
+ * // * Callback, called when event is handled
+ * // check: function(aEvent) {},
+ * //
+ * // * Checker ID *
+ * // getID: function() {},
+ * //
+ * // * Event that don't have predefined order relative other events. *
+ * // async getter: function() {},
+ * //
+ * // * Event that is not expected. *
+ * // unexpected getter: function() {},
+ * //
+ * // * No other event of the same type is not allowed. *
+ * // unique getter: function() {}
+ * // };
+ * eventSeq getter() {},
+ *
+ * // Array of checker objects defining unexpected events on invoker's
+ * // action.
+ * unexpectedEventSeq getter() {},
+ *
+ * // The ID of invoker.
+ * getID: function(){} // returns invoker ID
+ * };
+ *
+ * // Used to add a possible scenario of expected/unexpected events on
+ * // invoker's action.
+ * defineScenario(aInvokerObj, aEventSeq, aUnexpectedEventSeq)
+ *
+ *
+ * @param aEventType [in, optional] the default event type (isn't used if
+ * invoker defines eventSeq property).
+ */
+function eventQueue(aEventType) {
+ // public
+
+ /**
+ * Add invoker object into queue.
+ */
+ this.push = function eventQueue_push(aEventInvoker) {
+ this.mInvokers.push(aEventInvoker);
+ };
+
+ /**
+ * Start the queue processing.
+ */
+ this.invoke = function eventQueue_invoke() {
+ listenA11yEvents(true);
+
+ // XXX: Intermittent test_events_caretmove.html fails withouth timeout,
+ // see bug 474952.
+ this.processNextInvokerInTimeout(true);
+ };
+
+ /**
+ * This function is called when all events in the queue were handled.
+ * Override it if you need to be notified of this.
+ */
+ this.onFinish = function eventQueue_finish() {};
+
+ // private
+
+ /**
+ * Process next invoker.
+ */
+ // eslint-disable-next-line complexity
+ this.processNextInvoker = function eventQueue_processNextInvoker() {
+ // Some scenario was matched, we wait on next invoker processing.
+ if (this.mNextInvokerStatus == kInvokerCanceled) {
+ this.setInvokerStatus(
+ kInvokerNotScheduled,
+ "scenario was matched, wait for next invoker activation"
+ );
+ return;
+ }
+
+ this.setInvokerStatus(
+ kInvokerNotScheduled,
+ "the next invoker is processed now"
+ );
+
+ // Finish processing of the current invoker if any.
+ var testFailed = false;
+
+ var invoker = this.getInvoker();
+ if (invoker) {
+ if ("finalCheck" in invoker) {
+ invoker.finalCheck();
+ }
+
+ if (this.mScenarios && this.mScenarios.length) {
+ var matchIdx = -1;
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+ if (!this.areExpectedEventsLeft(eventSeq)) {
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ var checker = eventSeq[idx];
+ if (
+ (checker.unexpected && checker.wasCaught) ||
+ (!checker.unexpected && checker.wasCaught != 1)
+ ) {
+ break;
+ }
+ }
+
+ // Ok, we have matched scenario. Report it was completed ok. In
+ // case of empty scenario guess it was matched but if later we
+ // find out that non empty scenario was matched then it will be
+ // a final match.
+ if (idx == eventSeq.length) {
+ if (
+ matchIdx != -1 &&
+ !!eventSeq.length &&
+ this.mScenarios[matchIdx].length
+ ) {
+ ok(
+ false,
+ "We have a matched scenario at index " +
+ matchIdx +
+ " already."
+ );
+ }
+
+ if (matchIdx == -1 || eventSeq.length) {
+ matchIdx = scnIdx;
+ }
+
+ // Report everything is ok.
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ var checker = eventSeq[idx];
+
+ var typeStr = eventQueue.getEventTypeAsString(checker);
+ var msg =
+ "Test with ID = '" + this.getEventID(checker) + "' succeed. ";
+
+ if (checker.unexpected) {
+ ok(true, msg + `There's no unexpected '${typeStr}' event.`);
+ } else if (checker.todo) {
+ todo(false, `Todo event '${typeStr}' was caught`);
+ } else {
+ ok(true, `${msg} Event '${typeStr}' was handled.`);
+ }
+ }
+ }
+ }
+ }
+
+ // We don't have completely matched scenario. Report each failure/success
+ // for every scenario.
+ if (matchIdx == -1) {
+ testFailed = true;
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ var checker = eventSeq[idx];
+
+ var typeStr = eventQueue.getEventTypeAsString(checker);
+ var msg =
+ "Scenario #" +
+ scnIdx +
+ " of test with ID = '" +
+ this.getEventID(checker) +
+ "' failed. ";
+
+ if (checker.wasCaught > 1) {
+ ok(false, msg + "Dupe " + typeStr + " event.");
+ }
+
+ if (checker.unexpected) {
+ if (checker.wasCaught) {
+ ok(false, msg + "There's unexpected " + typeStr + " event.");
+ }
+ } else if (!checker.wasCaught) {
+ var rf = checker.todo ? todo : ok;
+ rf(false, `${msg} '${typeStr} event is missed.`);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ this.clearEventHandler();
+
+ // Check if need to stop the test.
+ if (testFailed || this.mIndex == this.mInvokers.length - 1) {
+ listenA11yEvents(false);
+
+ var res = this.onFinish();
+ if (res != DO_NOT_FINISH_TEST) {
+ SimpleTest.executeSoon(SimpleTest.finish);
+ }
+
+ return;
+ }
+
+ // Start processing of next invoker.
+ invoker = this.getNextInvoker();
+
+ // Set up event listeners. Process a next invoker if no events were added.
+ if (!this.setEventHandler(invoker)) {
+ this.processNextInvoker();
+ return;
+ }
+
+ if (gLogger.isEnabled()) {
+ gLogger.logToConsole("Event queue: \n invoke: " + invoker.getID());
+ gLogger.logToDOM("EQ: invoke: " + invoker.getID(), true);
+ }
+
+ var infoText = "Invoke the '" + invoker.getID() + "' test { ";
+ var scnCount = this.mScenarios ? this.mScenarios.length : 0;
+ for (var scnIdx = 0; scnIdx < scnCount; scnIdx++) {
+ infoText += "scenario #" + scnIdx + ": ";
+ var eventSeq = this.mScenarios[scnIdx];
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ infoText += eventSeq[idx].unexpected
+ ? "un"
+ : "" +
+ "expected '" +
+ eventQueue.getEventTypeAsString(eventSeq[idx]) +
+ "' event; ";
+ }
+ }
+ infoText += " }";
+ info(infoText);
+
+ if (invoker.invoke() == INVOKER_ACTION_FAILED) {
+ // Invoker failed to prepare action, fail and finish tests.
+ this.processNextInvoker();
+ return;
+ }
+
+ if (this.hasUnexpectedEventsScenario()) {
+ this.processNextInvokerInTimeout(true);
+ }
+ };
+
+ this.processNextInvokerInTimeout =
+ function eventQueue_processNextInvokerInTimeout(aUncondProcess) {
+ this.setInvokerStatus(kInvokerPending, "Process next invoker in timeout");
+
+ // No need to wait extra timeout when a) we know we don't need to do that
+ // and b) there's no any single unexpected event.
+ if (!aUncondProcess && this.areAllEventsExpected()) {
+ // We need delay to avoid events coalesce from different invokers.
+ var queue = this;
+ SimpleTest.executeSoon(function () {
+ queue.processNextInvoker();
+ });
+ return;
+ }
+
+ // Check in timeout invoker didn't fire registered events.
+ window.setTimeout(
+ function (aQueue) {
+ aQueue.processNextInvoker();
+ },
+ 300,
+ this
+ );
+ };
+
+ /**
+ * Handle events for the current invoker.
+ */
+ // eslint-disable-next-line complexity
+ this.handleEvent = function eventQueue_handleEvent(aEvent) {
+ var invoker = this.getInvoker();
+ if (!invoker) {
+ // skip events before test was started
+ return;
+ }
+
+ if (!this.mScenarios) {
+ // Bad invoker object, error will be reported before processing of next
+ // invoker in the queue.
+ this.processNextInvoker();
+ return;
+ }
+
+ if ("debugCheck" in invoker) {
+ invoker.debugCheck(aEvent);
+ }
+
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ var checker = eventSeq[idx];
+
+ // Search through handled expected events to report error if one of them
+ // is handled for a second time.
+ if (
+ !checker.unexpected &&
+ checker.wasCaught > 0 &&
+ eventQueue.isSameEvent(checker, aEvent)
+ ) {
+ checker.wasCaught++;
+ continue;
+ }
+
+ // Search through unexpected events, any match results in error report
+ // after this invoker processing (in case of matched scenario only).
+ if (checker.unexpected && eventQueue.compareEvents(checker, aEvent)) {
+ checker.wasCaught++;
+ continue;
+ }
+
+ // Report an error if we handled not expected event of unique type
+ // (i.e. event types are matched, targets differs).
+ if (
+ !checker.unexpected &&
+ checker.unique &&
+ eventQueue.compareEventTypes(checker, aEvent)
+ ) {
+ var isExpected = false;
+ for (var jdx = 0; jdx < eventSeq.length; jdx++) {
+ isExpected = eventQueue.compareEvents(eventSeq[jdx], aEvent);
+ if (isExpected) {
+ break;
+ }
+ }
+
+ if (!isExpected) {
+ ok(
+ false,
+ "Unique type " +
+ eventQueue.getEventTypeAsString(checker) +
+ " event was handled."
+ );
+ }
+ }
+ }
+ }
+
+ var hasMatchedCheckers = false;
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+
+ // Check if handled event matches expected sync event.
+ var nextChecker = this.getNextExpectedEvent(eventSeq);
+ if (nextChecker) {
+ if (eventQueue.compareEvents(nextChecker, aEvent)) {
+ this.processMatchedChecker(aEvent, nextChecker, scnIdx, eventSeq.idx);
+ hasMatchedCheckers = true;
+ continue;
+ }
+ }
+
+ // Check if handled event matches any expected async events.
+ var haveUnmatchedAsync = false;
+ for (idx = 0; idx < eventSeq.length; idx++) {
+ if (eventSeq[idx] instanceof orderChecker && haveUnmatchedAsync) {
+ break;
+ }
+
+ if (!eventSeq[idx].wasCaught) {
+ haveUnmatchedAsync = true;
+ }
+
+ if (!eventSeq[idx].unexpected && eventSeq[idx].async) {
+ if (eventQueue.compareEvents(eventSeq[idx], aEvent)) {
+ this.processMatchedChecker(aEvent, eventSeq[idx], scnIdx, idx);
+ hasMatchedCheckers = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (hasMatchedCheckers) {
+ var invoker = this.getInvoker();
+ if ("check" in invoker) {
+ invoker.check(aEvent);
+ }
+ }
+
+ for (idx = 0; idx < eventSeq.length; idx++) {
+ if (!eventSeq[idx].wasCaught) {
+ if (eventSeq[idx] instanceof orderChecker) {
+ eventSeq[idx].wasCaught++;
+ } else {
+ break;
+ }
+ }
+ }
+
+ // If we don't have more events to wait then schedule next invoker.
+ if (this.hasMatchedScenario()) {
+ if (this.mNextInvokerStatus == kInvokerNotScheduled) {
+ this.processNextInvokerInTimeout();
+ } else if (this.mNextInvokerStatus == kInvokerCanceled) {
+ this.setInvokerStatus(
+ kInvokerPending,
+ "Full match. Void the cancelation of next invoker processing"
+ );
+ }
+ return;
+ }
+
+ // If we have scheduled a next invoker then cancel in case of match.
+ if (this.mNextInvokerStatus == kInvokerPending && hasMatchedCheckers) {
+ this.setInvokerStatus(
+ kInvokerCanceled,
+ "Cancel the scheduled invoker in case of match"
+ );
+ }
+ };
+
+ // Helpers
+ this.processMatchedChecker = function eventQueue_function(
+ aEvent,
+ aMatchedChecker,
+ aScenarioIdx,
+ aEventIdx
+ ) {
+ aMatchedChecker.wasCaught++;
+
+ if ("check" in aMatchedChecker) {
+ aMatchedChecker.check(aEvent);
+ }
+
+ eventQueue.logEvent(
+ aEvent,
+ aMatchedChecker,
+ aScenarioIdx,
+ aEventIdx,
+ this.areExpectedEventsLeft(),
+ this.mNextInvokerStatus
+ );
+ };
+
+ this.getNextExpectedEvent = function eventQueue_getNextExpectedEvent(
+ aEventSeq
+ ) {
+ if (!("idx" in aEventSeq)) {
+ aEventSeq.idx = 0;
+ }
+
+ while (
+ aEventSeq.idx < aEventSeq.length &&
+ (aEventSeq[aEventSeq.idx].unexpected ||
+ aEventSeq[aEventSeq.idx].todo ||
+ aEventSeq[aEventSeq.idx].async ||
+ aEventSeq[aEventSeq.idx] instanceof orderChecker ||
+ aEventSeq[aEventSeq.idx].wasCaught > 0)
+ ) {
+ aEventSeq.idx++;
+ }
+
+ return aEventSeq.idx != aEventSeq.length ? aEventSeq[aEventSeq.idx] : null;
+ };
+
+ this.areExpectedEventsLeft = function eventQueue_areExpectedEventsLeft(
+ aScenario
+ ) {
+ function scenarioHasUnhandledExpectedEvent(aEventSeq) {
+ // Check if we have unhandled async (can be anywhere in the sequance) or
+ // sync expcected events yet.
+ for (var idx = 0; idx < aEventSeq.length; idx++) {
+ if (
+ !aEventSeq[idx].unexpected &&
+ !aEventSeq[idx].todo &&
+ !aEventSeq[idx].wasCaught &&
+ !(aEventSeq[idx] instanceof orderChecker)
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (aScenario) {
+ return scenarioHasUnhandledExpectedEvent(aScenario);
+ }
+
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+ if (scenarioHasUnhandledExpectedEvent(eventSeq)) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ this.areAllEventsExpected = function eventQueue_areAllEventsExpected() {
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ if (eventSeq[idx].unexpected || eventSeq[idx].todo) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ };
+
+ this.isUnexpectedEventScenario =
+ function eventQueue_isUnexpectedEventsScenario(aScenario) {
+ for (var idx = 0; idx < aScenario.length; idx++) {
+ if (!aScenario[idx].unexpected && !aScenario[idx].todo) {
+ break;
+ }
+ }
+
+ return idx == aScenario.length;
+ };
+
+ this.hasUnexpectedEventsScenario =
+ function eventQueue_hasUnexpectedEventsScenario() {
+ if (this.getInvoker().noEventsOnAction) {
+ return true;
+ }
+
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ if (this.isUnexpectedEventScenario(this.mScenarios[scnIdx])) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ this.hasMatchedScenario = function eventQueue_hasMatchedScenario() {
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var scn = this.mScenarios[scnIdx];
+ if (
+ !this.isUnexpectedEventScenario(scn) &&
+ !this.areExpectedEventsLeft(scn)
+ ) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ this.getInvoker = function eventQueue_getInvoker() {
+ return this.mInvokers[this.mIndex];
+ };
+
+ this.getNextInvoker = function eventQueue_getNextInvoker() {
+ return this.mInvokers[++this.mIndex];
+ };
+
+ this.setEventHandler = function eventQueue_setEventHandler(aInvoker) {
+ if (!("scenarios" in aInvoker) || !aInvoker.scenarios.length) {
+ var eventSeq = aInvoker.eventSeq;
+ var unexpectedEventSeq = aInvoker.unexpectedEventSeq;
+ if (!eventSeq && !unexpectedEventSeq && this.mDefEventType) {
+ eventSeq = [new invokerChecker(this.mDefEventType, aInvoker.DOMNode)];
+ }
+
+ if (eventSeq || unexpectedEventSeq) {
+ defineScenario(aInvoker, eventSeq, unexpectedEventSeq);
+ }
+ }
+
+ if (aInvoker.noEventsOnAction) {
+ return true;
+ }
+
+ this.mScenarios = aInvoker.scenarios;
+ if (!this.mScenarios || !this.mScenarios.length) {
+ ok(false, "Broken invoker '" + aInvoker.getID() + "'");
+ return false;
+ }
+
+ // Register event listeners.
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+
+ if (gLogger.isEnabled()) {
+ var msg =
+ "scenario #" +
+ scnIdx +
+ ", registered events number: " +
+ eventSeq.length;
+ gLogger.logToConsole(msg);
+ gLogger.logToDOM(msg, true);
+ }
+
+ // Do not warn about empty event sequances when more than one scenario
+ // was registered.
+ if (this.mScenarios.length == 1 && !eventSeq.length) {
+ ok(
+ false,
+ "Broken scenario #" +
+ scnIdx +
+ " of invoker '" +
+ aInvoker.getID() +
+ "'. No registered events"
+ );
+ return false;
+ }
+
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ eventSeq[idx].wasCaught = 0;
+ }
+
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ if (gLogger.isEnabled()) {
+ var msg = "registered";
+ if (eventSeq[idx].unexpected) {
+ msg += " unexpected";
+ }
+ if (eventSeq[idx].async) {
+ msg += " async";
+ }
+
+ msg +=
+ ": event type: " +
+ eventQueue.getEventTypeAsString(eventSeq[idx]) +
+ ", target: " +
+ eventQueue.getEventTargetDescr(eventSeq[idx], true);
+
+ gLogger.logToConsole(msg);
+ gLogger.logToDOM(msg, true);
+ }
+
+ var eventType = eventSeq[idx].type;
+ if (typeof eventType == "string") {
+ // DOM event
+ var target = eventQueue.getEventTarget(eventSeq[idx]);
+ if (!target) {
+ ok(false, "no target for DOM event!");
+ return false;
+ }
+ var phase = eventQueue.getEventPhase(eventSeq[idx]);
+ target.addEventListener(eventType, this, phase);
+ } else {
+ // A11y event
+ addA11yEventListener(eventType, this);
+ }
+ }
+ }
+
+ return true;
+ };
+
+ this.clearEventHandler = function eventQueue_clearEventHandler() {
+ if (!this.mScenarios) {
+ return;
+ }
+
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ var eventType = eventSeq[idx].type;
+ if (typeof eventType == "string") {
+ // DOM event
+ var target = eventQueue.getEventTarget(eventSeq[idx]);
+ var phase = eventQueue.getEventPhase(eventSeq[idx]);
+ target.removeEventListener(eventType, this, phase);
+ } else {
+ // A11y event
+ removeA11yEventListener(eventType, this);
+ }
+ }
+ }
+ this.mScenarios = null;
+ };
+
+ this.getEventID = function eventQueue_getEventID(aChecker) {
+ if ("getID" in aChecker) {
+ return aChecker.getID();
+ }
+
+ var invoker = this.getInvoker();
+ return invoker.getID();
+ };
+
+ this.setInvokerStatus = function eventQueue_setInvokerStatus(
+ aStatus,
+ aLogMsg
+ ) {
+ this.mNextInvokerStatus = aStatus;
+
+ // Uncomment it to debug invoker processing logic.
+ // gLogger.log(eventQueue.invokerStatusToMsg(aStatus, aLogMsg));
+ };
+
+ this.mDefEventType = aEventType;
+
+ this.mInvokers = [];
+ this.mIndex = -1;
+ this.mScenarios = null;
+
+ this.mNextInvokerStatus = kInvokerNotScheduled;
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// eventQueue static members and constants
+
+const kInvokerNotScheduled = 0;
+const kInvokerPending = 1;
+const kInvokerCanceled = 2;
+
+eventQueue.getEventTypeAsString = function eventQueue_getEventTypeAsString(
+ aEventOrChecker
+) {
+ if (Event.isInstance(aEventOrChecker)) {
+ return aEventOrChecker.type;
+ }
+
+ if (aEventOrChecker instanceof nsIAccessibleEvent) {
+ return eventTypeToString(aEventOrChecker.eventType);
+ }
+
+ return typeof aEventOrChecker.type == "string"
+ ? aEventOrChecker.type
+ : eventTypeToString(aEventOrChecker.type);
+};
+
+eventQueue.getEventTargetDescr = function eventQueue_getEventTargetDescr(
+ aEventOrChecker,
+ aDontForceTarget
+) {
+ if (Event.isInstance(aEventOrChecker)) {
+ return prettyName(aEventOrChecker.originalTarget);
+ }
+
+ // XXXbz this block doesn't seem to be reachable...
+ if (Event.isInstance(aEventOrChecker)) {
+ return prettyName(aEventOrChecker.accessible);
+ }
+
+ var descr = aEventOrChecker.targetDescr;
+ if (descr) {
+ return descr;
+ }
+
+ if (aDontForceTarget) {
+ return "no target description";
+ }
+
+ var target = "target" in aEventOrChecker ? aEventOrChecker.target : null;
+ return prettyName(target);
+};
+
+eventQueue.getEventPhase = function eventQueue_getEventPhase(aChecker) {
+ return "phase" in aChecker ? aChecker.phase : true;
+};
+
+eventQueue.getEventTarget = function eventQueue_getEventTarget(aChecker) {
+ if ("eventTarget" in aChecker) {
+ switch (aChecker.eventTarget) {
+ case "element":
+ return aChecker.target;
+ case "document":
+ default:
+ return aChecker.target.ownerDocument;
+ }
+ }
+ return aChecker.target.ownerDocument;
+};
+
+eventQueue.compareEventTypes = function eventQueue_compareEventTypes(
+ aChecker,
+ aEvent
+) {
+ var eventType = Event.isInstance(aEvent) ? aEvent.type : aEvent.eventType;
+ return aChecker.type == eventType;
+};
+
+eventQueue.compareEvents = function eventQueue_compareEvents(aChecker, aEvent) {
+ if (!eventQueue.compareEventTypes(aChecker, aEvent)) {
+ return false;
+ }
+
+ // If checker provides "match" function then allow the checker to decide
+ // whether event is matched.
+ if ("match" in aChecker) {
+ return aChecker.match(aEvent);
+ }
+
+ var target1 = aChecker.target;
+ if (target1 instanceof nsIAccessible) {
+ var target2 = Event.isInstance(aEvent)
+ ? getAccessible(aEvent.target)
+ : aEvent.accessible;
+
+ return target1 == target2;
+ }
+
+ // If original target isn't suitable then extend interface to support target
+ // (original target is used in test_elm_media.html).
+ var target2 = Event.isInstance(aEvent)
+ ? aEvent.originalTarget
+ : aEvent.DOMNode;
+ return target1 == target2;
+};
+
+eventQueue.isSameEvent = function eventQueue_isSameEvent(aChecker, aEvent) {
+ // We don't have stored info about handled event other than its type and
+ // target, thus we should filter text change and state change events since
+ // they may occur on the same element because of complex changes.
+ return (
+ this.compareEvents(aChecker, aEvent) &&
+ !(aEvent instanceof nsIAccessibleTextChangeEvent) &&
+ !(aEvent instanceof nsIAccessibleStateChangeEvent)
+ );
+};
+
+eventQueue.invokerStatusToMsg = function eventQueue_invokerStatusToMsg(
+ aInvokerStatus,
+ aMsg
+) {
+ var msg = "invoker status: ";
+ switch (aInvokerStatus) {
+ case kInvokerNotScheduled:
+ msg += "not scheduled";
+ break;
+ case kInvokerPending:
+ msg += "pending";
+ break;
+ case kInvokerCanceled:
+ msg += "canceled";
+ break;
+ }
+
+ if (aMsg) {
+ msg += " (" + aMsg + ")";
+ }
+
+ return msg;
+};
+
+eventQueue.logEvent = function eventQueue_logEvent(
+ aOrigEvent,
+ aMatchedChecker,
+ aScenarioIdx,
+ aEventIdx,
+ aAreExpectedEventsLeft,
+ aInvokerStatus
+) {
+ // Dump DOM event information. Skip a11y event since it is dumped by
+ // gA11yEventObserver.
+ if (Event.isInstance(aOrigEvent)) {
+ var info = "Event type: " + eventQueue.getEventTypeAsString(aOrigEvent);
+ info += ". Target: " + eventQueue.getEventTargetDescr(aOrigEvent);
+ gLogger.logToDOM(info);
+ }
+
+ var infoMsg =
+ "unhandled expected events: " +
+ aAreExpectedEventsLeft +
+ ", " +
+ eventQueue.invokerStatusToMsg(aInvokerStatus);
+
+ var currType = eventQueue.getEventTypeAsString(aMatchedChecker);
+ var currTargetDescr = eventQueue.getEventTargetDescr(aMatchedChecker);
+ var consoleMsg =
+ "*****\nScenario " +
+ aScenarioIdx +
+ ", event " +
+ aEventIdx +
+ " matched: " +
+ currType +
+ "\n" +
+ infoMsg +
+ "\n*****";
+ gLogger.logToConsole(consoleMsg);
+
+ var emphText = "matched ";
+ var msg =
+ "EQ event, type: " +
+ currType +
+ ", target: " +
+ currTargetDescr +
+ ", " +
+ infoMsg;
+ gLogger.logToDOM(msg, true, emphText);
+};
+
+// //////////////////////////////////////////////////////////////////////////////
+// Action sequence
+
+/**
+ * Deal with action sequence. Used when you need to execute couple of actions
+ * each after other one.
+ */
+function sequence() {
+ /**
+ * Append new sequence item.
+ *
+ * @param aProcessor [in] object implementing interface
+ * {
+ * // execute item action
+ * process: function() {},
+ * // callback, is called when item was processed
+ * onProcessed: function() {}
+ * };
+ * @param aEventType [in] event type of expected event on item action
+ * @param aTarget [in] event target of expected event on item action
+ * @param aItemID [in] identifier of item
+ */
+ this.append = function sequence_append(
+ aProcessor,
+ aEventType,
+ aTarget,
+ aItemID
+ ) {
+ var item = new sequenceItem(aProcessor, aEventType, aTarget, aItemID);
+ this.items.push(item);
+ };
+
+ /**
+ * Process next sequence item.
+ */
+ this.processNext = function sequence_processNext() {
+ this.idx++;
+ if (this.idx >= this.items.length) {
+ ok(false, "End of sequence: nothing to process!");
+ SimpleTest.finish();
+ return;
+ }
+
+ this.items[this.idx].startProcess();
+ };
+
+ this.items = [];
+ this.idx = -1;
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Event queue invokers
+
+/**
+ * Defines a scenario of expected/unexpected events. Each invoker can have
+ * one or more scenarios of events. Only one scenario must be completed.
+ */
+function defineScenario(aInvoker, aEventSeq, aUnexpectedEventSeq) {
+ if (!("scenarios" in aInvoker)) {
+ aInvoker.scenarios = [];
+ }
+
+ // Create unified event sequence concatenating expected and unexpected
+ // events.
+ if (!aEventSeq) {
+ aEventSeq = [];
+ }
+
+ for (var idx = 0; idx < aEventSeq.length; idx++) {
+ aEventSeq[idx].unexpected |= false;
+ aEventSeq[idx].async |= false;
+ }
+
+ if (aUnexpectedEventSeq) {
+ for (var idx = 0; idx < aUnexpectedEventSeq.length; idx++) {
+ aUnexpectedEventSeq[idx].unexpected = true;
+ aUnexpectedEventSeq[idx].async = false;
+ }
+
+ aEventSeq = aEventSeq.concat(aUnexpectedEventSeq);
+ }
+
+ aInvoker.scenarios.push(aEventSeq);
+}
+
+/**
+ * Invokers defined below take a checker object (or array of checker objects).
+ * An invoker listens for default event type registered in event queue object
+ * until its checker is provided.
+ *
+ * Note, checker object or array of checker objects is optional.
+ */
+
+/**
+ * Click invoker.
+ */
+function synthClick(aNodeOrID, aCheckerOrEventSeq, aArgs) {
+ this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
+
+ this.invoke = function synthClick_invoke() {
+ var targetNode = this.DOMNode;
+ if (targetNode.nodeType == targetNode.DOCUMENT_NODE) {
+ targetNode = this.DOMNode.body
+ ? this.DOMNode.body
+ : this.DOMNode.documentElement;
+ }
+
+ // Scroll the node into view, otherwise synth click may fail.
+ if (isHTMLElement(targetNode)) {
+ targetNode.scrollIntoView(true);
+ } else if (isXULElement(targetNode)) {
+ var targetAcc = getAccessible(targetNode);
+ targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE);
+ }
+
+ var x = 1,
+ y = 1;
+ if (aArgs && "where" in aArgs) {
+ if (aArgs.where == "right") {
+ if (isHTMLElement(targetNode)) {
+ x = targetNode.offsetWidth - 1;
+ } else if (isXULElement(targetNode)) {
+ x = targetNode.getBoundingClientRect().width - 1;
+ }
+ } else if (aArgs.where == "center") {
+ if (isHTMLElement(targetNode)) {
+ x = targetNode.offsetWidth / 2;
+ y = targetNode.offsetHeight / 2;
+ } else if (isXULElement(targetNode)) {
+ x = targetNode.getBoundingClientRect().width / 2;
+ y = targetNode.getBoundingClientRect().height / 2;
+ }
+ }
+ }
+ synthesizeMouse(targetNode, x, y, aArgs ? aArgs : {});
+ };
+
+ this.finalCheck = function synthClick_finalCheck() {
+ // Scroll top window back.
+ window.top.scrollTo(0, 0);
+ };
+
+ this.getID = function synthClick_getID() {
+ return prettyName(aNodeOrID) + " click";
+ };
+}
+
+/**
+ * Scrolls the node into view.
+ */
+function scrollIntoView(aNodeOrID, aCheckerOrEventSeq) {
+ this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
+
+ this.invoke = function scrollIntoView_invoke() {
+ var targetNode = this.DOMNode;
+ if (isHTMLElement(targetNode)) {
+ targetNode.scrollIntoView(true);
+ } else if (isXULElement(targetNode)) {
+ var targetAcc = getAccessible(targetNode);
+ targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE);
+ }
+ };
+
+ this.getID = function scrollIntoView_getID() {
+ return prettyName(aNodeOrID) + " scrollIntoView";
+ };
+}
+
+/**
+ * Mouse move invoker.
+ */
+function synthMouseMove(aID, aCheckerOrEventSeq) {
+ this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
+
+ this.invoke = function synthMouseMove_invoke() {
+ synthesizeMouse(this.DOMNode, 5, 5, { type: "mousemove" });
+ synthesizeMouse(this.DOMNode, 6, 6, { type: "mousemove" });
+ };
+
+ this.getID = function synthMouseMove_getID() {
+ return prettyName(aID) + " mouse move";
+ };
+}
+
+/**
+ * General key press invoker.
+ */
+function synthKey(aNodeOrID, aKey, aArgs, aCheckerOrEventSeq) {
+ this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
+
+ this.invoke = function synthKey_invoke() {
+ synthesizeKey(this.mKey, this.mArgs, this.mWindow);
+ };
+
+ this.getID = function synthKey_getID() {
+ var key = this.mKey;
+ switch (this.mKey) {
+ case "VK_TAB":
+ key = "tab";
+ break;
+ case "VK_DOWN":
+ key = "down";
+ break;
+ case "VK_UP":
+ key = "up";
+ break;
+ case "VK_LEFT":
+ key = "left";
+ break;
+ case "VK_RIGHT":
+ key = "right";
+ break;
+ case "VK_HOME":
+ key = "home";
+ break;
+ case "VK_END":
+ key = "end";
+ break;
+ case "VK_ESCAPE":
+ key = "escape";
+ break;
+ case "VK_RETURN":
+ key = "enter";
+ break;
+ }
+ if (aArgs) {
+ if (aArgs.shiftKey) {
+ key += " shift";
+ }
+ if (aArgs.ctrlKey) {
+ key += " ctrl";
+ }
+ if (aArgs.altKey) {
+ key += " alt";
+ }
+ }
+ return prettyName(aNodeOrID) + " '" + key + " ' key";
+ };
+
+ this.mKey = aKey;
+ this.mArgs = aArgs ? aArgs : {};
+ this.mWindow = aArgs ? aArgs.window : null;
+}
+
+/**
+ * Tab key invoker.
+ */
+function synthTab(aNodeOrID, aCheckerOrEventSeq, aWindow) {
+ this.__proto__ = new synthKey(
+ aNodeOrID,
+ "VK_TAB",
+ { shiftKey: false, window: aWindow },
+ aCheckerOrEventSeq
+ );
+}
+
+/**
+ * Shift tab key invoker.
+ */
+function synthShiftTab(aNodeOrID, aCheckerOrEventSeq) {
+ this.__proto__ = new synthKey(
+ aNodeOrID,
+ "VK_TAB",
+ { shiftKey: true },
+ aCheckerOrEventSeq
+ );
+}
+
+/**
+ * Escape key invoker.
+ */
+function synthEscapeKey(aNodeOrID, aCheckerOrEventSeq) {
+ this.__proto__ = new synthKey(
+ aNodeOrID,
+ "VK_ESCAPE",
+ null,
+ aCheckerOrEventSeq
+ );
+}
+
+/**
+ * Down arrow key invoker.
+ */
+function synthDownKey(aNodeOrID, aCheckerOrEventSeq, aArgs) {
+ this.__proto__ = new synthKey(
+ aNodeOrID,
+ "VK_DOWN",
+ aArgs,
+ aCheckerOrEventSeq
+ );
+}
+
+/**
+ * Up arrow key invoker.
+ */
+function synthUpKey(aNodeOrID, aCheckerOrEventSeq, aArgs) {
+ this.__proto__ = new synthKey(aNodeOrID, "VK_UP", aArgs, aCheckerOrEventSeq);
+}
+
+/**
+ * Left arrow key invoker.
+ */
+function synthLeftKey(aNodeOrID, aCheckerOrEventSeq, aArgs) {
+ this.__proto__ = new synthKey(
+ aNodeOrID,
+ "VK_LEFT",
+ aArgs,
+ aCheckerOrEventSeq
+ );
+}
+
+/**
+ * Right arrow key invoker.
+ */
+function synthRightKey(aNodeOrID, aCheckerOrEventSeq, aArgs) {
+ this.__proto__ = new synthKey(
+ aNodeOrID,
+ "VK_RIGHT",
+ aArgs,
+ aCheckerOrEventSeq
+ );
+}
+
+/**
+ * Home key invoker.
+ */
+function synthHomeKey(aNodeOrID, aCheckerOrEventSeq) {
+ this.__proto__ = new synthKey(aNodeOrID, "VK_HOME", null, aCheckerOrEventSeq);
+}
+
+/**
+ * End key invoker.
+ */
+function synthEndKey(aNodeOrID, aCheckerOrEventSeq) {
+ this.__proto__ = new synthKey(aNodeOrID, "VK_END", null, aCheckerOrEventSeq);
+}
+
+/**
+ * Enter key invoker
+ */
+function synthEnterKey(aID, aCheckerOrEventSeq) {
+ this.__proto__ = new synthKey(aID, "VK_RETURN", null, aCheckerOrEventSeq);
+}
+
+/**
+ * Synth alt + down arrow to open combobox.
+ */
+function synthOpenComboboxKey(aID, aCheckerOrEventSeq) {
+ this.__proto__ = new synthDownKey(aID, aCheckerOrEventSeq, { altKey: true });
+
+ this.getID = function synthOpenComboboxKey_getID() {
+ return "open combobox (alt + down arrow) " + prettyName(aID);
+ };
+}
+
+/**
+ * Focus invoker.
+ */
+function synthFocus(aNodeOrID, aCheckerOrEventSeq) {
+ var checkerOfEventSeq = aCheckerOrEventSeq
+ ? aCheckerOrEventSeq
+ : new focusChecker(aNodeOrID);
+ this.__proto__ = new synthAction(aNodeOrID, checkerOfEventSeq);
+
+ this.invoke = function synthFocus_invoke() {
+ if (this.DOMNode.editor) {
+ this.DOMNode.selectionStart = this.DOMNode.selectionEnd =
+ this.DOMNode.value.length;
+ }
+ this.DOMNode.focus();
+ };
+
+ this.getID = function synthFocus_getID() {
+ return prettyName(aNodeOrID) + " focus";
+ };
+}
+
+/**
+ * Focus invoker. Focus the HTML body of content document of iframe.
+ */
+function synthFocusOnFrame(aNodeOrID, aCheckerOrEventSeq) {
+ var frameDoc = getNode(aNodeOrID).contentDocument;
+ var checkerOrEventSeq = aCheckerOrEventSeq
+ ? aCheckerOrEventSeq
+ : new focusChecker(frameDoc);
+ this.__proto__ = new synthAction(frameDoc, checkerOrEventSeq);
+
+ this.invoke = function synthFocus_invoke() {
+ this.DOMNode.body.focus();
+ };
+
+ this.getID = function synthFocus_getID() {
+ return prettyName(aNodeOrID) + " frame document focus";
+ };
+}
+
+/**
+ * Change the current item when the widget doesn't have a focus.
+ */
+function changeCurrentItem(aID, aItemID) {
+ this.eventSeq = [new nofocusChecker()];
+
+ this.invoke = function changeCurrentItem_invoke() {
+ var controlNode = getNode(aID);
+ var itemNode = getNode(aItemID);
+
+ // HTML
+ if (controlNode.localName == "input") {
+ if (controlNode.checked) {
+ this.reportError();
+ }
+
+ controlNode.checked = true;
+ return;
+ }
+
+ if (controlNode.localName == "select") {
+ if (controlNode.selectedIndex == itemNode.index) {
+ this.reportError();
+ }
+
+ controlNode.selectedIndex = itemNode.index;
+ return;
+ }
+
+ // XUL
+ if (controlNode.localName == "tree") {
+ if (controlNode.currentIndex == aItemID) {
+ this.reportError();
+ }
+
+ controlNode.currentIndex = aItemID;
+ return;
+ }
+
+ if (controlNode.localName == "menulist") {
+ if (controlNode.selectedItem == itemNode) {
+ this.reportError();
+ }
+
+ controlNode.selectedItem = itemNode;
+ return;
+ }
+
+ if (controlNode.currentItem == itemNode) {
+ ok(
+ false,
+ "Error in test: proposed current item is already current" +
+ prettyName(aID)
+ );
+ }
+
+ controlNode.currentItem = itemNode;
+ };
+
+ this.getID = function changeCurrentItem_getID() {
+ return "current item change for " + prettyName(aID);
+ };
+
+ this.reportError = function changeCurrentItem_reportError() {
+ ok(
+ false,
+ "Error in test: proposed current item '" +
+ aItemID +
+ "' is already current"
+ );
+ };
+}
+
+/**
+ * Toggle top menu invoker.
+ */
+function toggleTopMenu(aID, aCheckerOrEventSeq) {
+ this.__proto__ = new synthKey(aID, "VK_ALT", null, aCheckerOrEventSeq);
+
+ this.getID = function toggleTopMenu_getID() {
+ return "toggle top menu on " + prettyName(aID);
+ };
+}
+
+/**
+ * Context menu invoker.
+ */
+function synthContextMenu(aID, aCheckerOrEventSeq) {
+ this.__proto__ = new synthClick(aID, aCheckerOrEventSeq, {
+ button: 0,
+ type: "contextmenu",
+ });
+
+ this.getID = function synthContextMenu_getID() {
+ return "context menu on " + prettyName(aID);
+ };
+}
+
+/**
+ * Open combobox, autocomplete and etc popup, check expandable states.
+ */
+function openCombobox(aComboboxID) {
+ this.eventSeq = [
+ new stateChangeChecker(STATE_EXPANDED, false, true, aComboboxID),
+ ];
+
+ this.invoke = function openCombobox_invoke() {
+ getNode(aComboboxID).focus();
+ synthesizeKey("VK_DOWN", { altKey: true });
+ };
+
+ this.getID = function openCombobox_getID() {
+ return "open combobox " + prettyName(aComboboxID);
+ };
+}
+
+/**
+ * Close combobox, autocomplete and etc popup, check expandable states.
+ */
+function closeCombobox(aComboboxID) {
+ this.eventSeq = [
+ new stateChangeChecker(STATE_EXPANDED, false, false, aComboboxID),
+ ];
+
+ this.invoke = function closeCombobox_invoke() {
+ synthesizeKey("KEY_Escape");
+ };
+
+ this.getID = function closeCombobox_getID() {
+ return "close combobox " + prettyName(aComboboxID);
+ };
+}
+
+/**
+ * Select all invoker.
+ */
+function synthSelectAll(aNodeOrID, aCheckerOrEventSeq) {
+ this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
+
+ this.invoke = function synthSelectAll_invoke() {
+ if (ChromeUtils.getClassName(this.DOMNode) === "HTMLInputElement") {
+ this.DOMNode.select();
+ } else {
+ window.getSelection().selectAllChildren(this.DOMNode);
+ }
+ };
+
+ this.getID = function synthSelectAll_getID() {
+ return aNodeOrID + " selectall";
+ };
+}
+
+/**
+ * Move the caret to the end of line.
+ */
+function moveToLineEnd(aID, aCaretOffset) {
+ if (MAC) {
+ this.__proto__ = new synthKey(
+ aID,
+ "VK_RIGHT",
+ { metaKey: true },
+ new caretMoveChecker(aCaretOffset, true, aID)
+ );
+ } else {
+ this.__proto__ = new synthEndKey(
+ aID,
+ new caretMoveChecker(aCaretOffset, true, aID)
+ );
+ }
+
+ this.getID = function moveToLineEnd_getID() {
+ return "move to line end in " + prettyName(aID);
+ };
+}
+
+/**
+ * Move the caret to the end of previous line if any.
+ */
+function moveToPrevLineEnd(aID, aCaretOffset) {
+ this.__proto__ = new synthAction(
+ aID,
+ new caretMoveChecker(aCaretOffset, true, aID)
+ );
+
+ this.invoke = function moveToPrevLineEnd_invoke() {
+ synthesizeKey("KEY_ArrowUp");
+
+ if (MAC) {
+ synthesizeKey("Key_ArrowRight", { metaKey: true });
+ } else {
+ synthesizeKey("KEY_End");
+ }
+ };
+
+ this.getID = function moveToPrevLineEnd_getID() {
+ return "move to previous line end in " + prettyName(aID);
+ };
+}
+
+/**
+ * Move the caret to begining of the line.
+ */
+function moveToLineStart(aID, aCaretOffset) {
+ if (MAC) {
+ this.__proto__ = new synthKey(
+ aID,
+ "VK_LEFT",
+ { metaKey: true },
+ new caretMoveChecker(aCaretOffset, true, aID)
+ );
+ } else {
+ this.__proto__ = new synthHomeKey(
+ aID,
+ new caretMoveChecker(aCaretOffset, true, aID)
+ );
+ }
+
+ this.getID = function moveToLineEnd_getID() {
+ return "move to line start in " + prettyName(aID);
+ };
+}
+
+/**
+ * Move the caret to begining of the text.
+ */
+function moveToTextStart(aID) {
+ if (MAC) {
+ this.__proto__ = new synthKey(
+ aID,
+ "VK_UP",
+ { metaKey: true },
+ new caretMoveChecker(0, true, aID)
+ );
+ } else {
+ this.__proto__ = new synthKey(
+ aID,
+ "VK_HOME",
+ { ctrlKey: true },
+ new caretMoveChecker(0, true, aID)
+ );
+ }
+
+ this.getID = function moveToTextStart_getID() {
+ return "move to text start in " + prettyName(aID);
+ };
+}
+
+/**
+ * Move the caret in text accessible.
+ */
+function moveCaretToDOMPoint(
+ aID,
+ aDOMPointNodeID,
+ aDOMPointOffset,
+ aExpectedOffset,
+ aFocusTargetID,
+ aCheckFunc
+) {
+ this.target = getAccessible(aID, [nsIAccessibleText]);
+ this.DOMPointNode = getNode(aDOMPointNodeID);
+ this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null;
+ this.focusNode = this.focus ? this.focus.DOMNode : null;
+
+ this.invoke = function moveCaretToDOMPoint_invoke() {
+ if (this.focusNode) {
+ this.focusNode.focus();
+ }
+
+ var selection = this.DOMPointNode.ownerGlobal.getSelection();
+ var selRange = selection.getRangeAt(0);
+ selRange.setStart(this.DOMPointNode, aDOMPointOffset);
+ selRange.collapse(true);
+
+ selection.removeRange(selRange);
+ selection.addRange(selRange);
+ };
+
+ this.getID = function moveCaretToDOMPoint_getID() {
+ return (
+ "Set caret on " +
+ prettyName(aID) +
+ " at point: " +
+ prettyName(aDOMPointNodeID) +
+ " node with offset " +
+ aDOMPointOffset
+ );
+ };
+
+ this.finalCheck = function moveCaretToDOMPoint_finalCheck() {
+ if (aCheckFunc) {
+ aCheckFunc.call();
+ }
+ };
+
+ this.eventSeq = [new caretMoveChecker(aExpectedOffset, true, this.target)];
+
+ if (this.focus) {
+ this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
+ }
+}
+
+/**
+ * Set caret offset in text accessible.
+ */
+function setCaretOffset(aID, aOffset, aFocusTargetID) {
+ this.target = getAccessible(aID, [nsIAccessibleText]);
+ this.offset = aOffset == -1 ? this.target.characterCount : aOffset;
+ this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null;
+
+ this.invoke = function setCaretOffset_invoke() {
+ this.target.caretOffset = this.offset;
+ };
+
+ this.getID = function setCaretOffset_getID() {
+ return "Set caretOffset on " + prettyName(aID) + " at " + this.offset;
+ };
+
+ this.eventSeq = [new caretMoveChecker(this.offset, true, this.target)];
+
+ if (this.focus) {
+ this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
+ }
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Event queue checkers
+
+/**
+ * Common invoker checker (see eventSeq of eventQueue).
+ */
+function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg, aIsAsync) {
+ this.type = aEventType;
+ this.async = aIsAsync;
+
+ this.__defineGetter__("target", invokerChecker_targetGetter);
+ this.__defineSetter__("target", invokerChecker_targetSetter);
+
+ // implementation details
+ function invokerChecker_targetGetter() {
+ if (typeof this.mTarget == "function") {
+ return this.mTarget.call(null, this.mTargetFuncArg);
+ }
+ if (typeof this.mTarget == "string") {
+ return getNode(this.mTarget);
+ }
+
+ return this.mTarget;
+ }
+
+ function invokerChecker_targetSetter(aValue) {
+ this.mTarget = aValue;
+ return this.mTarget;
+ }
+
+ this.__defineGetter__("targetDescr", invokerChecker_targetDescrGetter);
+
+ function invokerChecker_targetDescrGetter() {
+ if (typeof this.mTarget == "function") {
+ return this.mTarget.name + ", arg: " + this.mTargetFuncArg;
+ }
+
+ return prettyName(this.mTarget);
+ }
+
+ this.mTarget = aTargetOrFunc;
+ this.mTargetFuncArg = aTargetFuncArg;
+}
+
+/**
+ * event checker that forces preceeding async events to happen before this
+ * checker.
+ */
+function orderChecker() {
+ // XXX it doesn't actually work to inherit from invokerChecker, but maybe we
+ // should fix that?
+ // this.__proto__ = new invokerChecker(null, null, null, false);
+}
+
+/**
+ * Generic invoker checker for todo events.
+ */
+function todo_invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg) {
+ this.__proto__ = new invokerChecker(
+ aEventType,
+ aTargetOrFunc,
+ aTargetFuncArg,
+ true
+ );
+ this.todo = true;
+}
+
+/**
+ * Generic invoker checker for unexpected events.
+ */
+function unexpectedInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg) {
+ this.__proto__ = new invokerChecker(
+ aEventType,
+ aTargetOrFunc,
+ aTargetFuncArg,
+ true
+ );
+
+ this.unexpected = true;
+}
+
+/**
+ * Common invoker checker for async events.
+ */
+function asyncInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg) {
+ this.__proto__ = new invokerChecker(
+ aEventType,
+ aTargetOrFunc,
+ aTargetFuncArg,
+ true
+ );
+}
+
+function focusChecker(aTargetOrFunc, aTargetFuncArg) {
+ this.__proto__ = new invokerChecker(
+ EVENT_FOCUS,
+ aTargetOrFunc,
+ aTargetFuncArg,
+ false
+ );
+
+ this.unique = true; // focus event must be unique for invoker action
+
+ this.check = function focusChecker_check(aEvent) {
+ testStates(aEvent.accessible, STATE_FOCUSED);
+ };
+}
+
+function nofocusChecker(aID) {
+ this.__proto__ = new focusChecker(aID);
+ this.unexpected = true;
+}
+
+/**
+ * Text inserted/removed events checker.
+ * @param aFromUser [in, optional] kNotFromUserInput or kFromUserInput
+ */
+function textChangeChecker(
+ aID,
+ aStart,
+ aEnd,
+ aTextOrFunc,
+ aIsInserted,
+ aFromUser,
+ aAsync
+) {
+ this.target = getNode(aID);
+ this.type = aIsInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
+ this.startOffset = aStart;
+ this.endOffset = aEnd;
+ this.textOrFunc = aTextOrFunc;
+ this.async = aAsync;
+
+ this.match = function stextChangeChecker_match(aEvent) {
+ if (
+ !(aEvent instanceof nsIAccessibleTextChangeEvent) ||
+ aEvent.accessible !== getAccessible(this.target)
+ ) {
+ return false;
+ }
+
+ let tcEvent = aEvent.QueryInterface(nsIAccessibleTextChangeEvent);
+ let modifiedText =
+ typeof this.textOrFunc === "function"
+ ? this.textOrFunc()
+ : this.textOrFunc;
+ return modifiedText === tcEvent.modifiedText;
+ };
+
+ this.check = function textChangeChecker_check(aEvent) {
+ aEvent.QueryInterface(nsIAccessibleTextChangeEvent);
+
+ var modifiedText =
+ typeof this.textOrFunc == "function"
+ ? this.textOrFunc()
+ : this.textOrFunc;
+ var modifiedTextLen =
+ this.endOffset == -1 ? modifiedText.length : aEnd - aStart;
+
+ is(
+ aEvent.start,
+ this.startOffset,
+ "Wrong start offset for " + prettyName(aID)
+ );
+ is(aEvent.length, modifiedTextLen, "Wrong length for " + prettyName(aID));
+ var changeInfo = aIsInserted ? "inserted" : "removed";
+ is(
+ aEvent.isInserted,
+ aIsInserted,
+ "Text was " + changeInfo + " for " + prettyName(aID)
+ );
+ is(
+ aEvent.modifiedText,
+ modifiedText,
+ "Wrong " + changeInfo + " text for " + prettyName(aID)
+ );
+ if (typeof aFromUser != "undefined") {
+ is(
+ aEvent.isFromUserInput,
+ aFromUser,
+ "wrong value of isFromUserInput() for " + prettyName(aID)
+ );
+ }
+ };
+}
+
+/**
+ * Caret move events checker.
+ */
+function caretMoveChecker(
+ aCaretOffset,
+ aIsSelectionCollapsed,
+ aTargetOrFunc,
+ aTargetFuncArg,
+ aIsAsync
+) {
+ this.__proto__ = new invokerChecker(
+ EVENT_TEXT_CARET_MOVED,
+ aTargetOrFunc,
+ aTargetFuncArg,
+ aIsAsync
+ );
+
+ this.check = function caretMoveChecker_check(aEvent) {
+ let evt = aEvent.QueryInterface(nsIAccessibleCaretMoveEvent);
+ is(
+ evt.caretOffset,
+ aCaretOffset,
+ "Wrong caret offset for " + prettyName(aEvent.accessible)
+ );
+ is(
+ evt.isSelectionCollapsed,
+ aIsSelectionCollapsed,
+ "wrong collapsed value for " + prettyName(aEvent.accessible)
+ );
+ };
+}
+
+function asyncCaretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg) {
+ this.__proto__ = new caretMoveChecker(
+ aCaretOffset,
+ true, // Caret is collapsed
+ aTargetOrFunc,
+ aTargetFuncArg,
+ true
+ );
+}
+
+/**
+ * Text selection change checker.
+ */
+function textSelectionChecker(
+ aID,
+ aStartOffset,
+ aEndOffset,
+ aRangeStartContainer,
+ aRangeStartOffset,
+ aRangeEndContainer,
+ aRangeEndOffset
+) {
+ this.__proto__ = new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID);
+
+ this.check = function textSelectionChecker_check(aEvent) {
+ if (aStartOffset == aEndOffset) {
+ ok(true, "Collapsed selection triggered text selection change event.");
+ } else {
+ testTextGetSelection(aID, aStartOffset, aEndOffset, 0);
+
+ // Test selection test range
+ let selectionRanges = aEvent.QueryInterface(
+ nsIAccessibleTextSelectionChangeEvent
+ ).selectionRanges;
+ let range = selectionRanges.queryElementAt(0, nsIAccessibleTextRange);
+ is(
+ range.startContainer,
+ getAccessible(aRangeStartContainer),
+ "correct range start container"
+ );
+ is(range.startOffset, aRangeStartOffset, "correct range start offset");
+ is(range.endOffset, aRangeEndOffset, "correct range end offset");
+ is(
+ range.endContainer,
+ getAccessible(aRangeEndContainer),
+ "correct range end container"
+ );
+ }
+ };
+}
+
+/**
+ * Object attribute changed checker
+ */
+function objAttrChangedChecker(aID, aAttr) {
+ this.__proto__ = new invokerChecker(EVENT_OBJECT_ATTRIBUTE_CHANGED, aID);
+
+ this.check = function objAttrChangedChecker_check(aEvent) {
+ var event = null;
+ try {
+ var event = aEvent.QueryInterface(
+ nsIAccessibleObjectAttributeChangedEvent
+ );
+ } catch (e) {
+ ok(false, "Object attribute changed event was expected");
+ }
+
+ if (!event) {
+ return;
+ }
+
+ is(
+ event.changedAttribute,
+ aAttr,
+ "Wrong attribute name of the object attribute changed event."
+ );
+ };
+
+ this.match = function objAttrChangedChecker_match(aEvent) {
+ if (aEvent instanceof nsIAccessibleObjectAttributeChangedEvent) {
+ var scEvent = aEvent.QueryInterface(
+ nsIAccessibleObjectAttributeChangedEvent
+ );
+ return (
+ aEvent.accessible == getAccessible(this.target) &&
+ scEvent.changedAttribute == aAttr
+ );
+ }
+ return false;
+ };
+}
+
+/**
+ * State change checker.
+ */
+function stateChangeChecker(
+ aState,
+ aIsExtraState,
+ aIsEnabled,
+ aTargetOrFunc,
+ aTargetFuncArg,
+ aIsAsync,
+ aSkipCurrentStateCheck
+) {
+ this.__proto__ = new invokerChecker(
+ EVENT_STATE_CHANGE,
+ aTargetOrFunc,
+ aTargetFuncArg,
+ aIsAsync
+ );
+
+ this.check = function stateChangeChecker_check(aEvent) {
+ var event = null;
+ try {
+ var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
+ } catch (e) {
+ ok(false, "State change event was expected");
+ }
+
+ if (!event) {
+ return;
+ }
+
+ is(
+ event.isExtraState,
+ aIsExtraState,
+ "Wrong extra state bit of the statechange event."
+ );
+ isState(
+ event.state,
+ aState,
+ aIsExtraState,
+ "Wrong state of the statechange event."
+ );
+ is(event.isEnabled, aIsEnabled, "Wrong state of statechange event state");
+
+ if (aSkipCurrentStateCheck) {
+ todo(false, "State checking was skipped!");
+ return;
+ }
+
+ var state = aIsEnabled ? (aIsExtraState ? 0 : aState) : 0;
+ var extraState = aIsEnabled ? (aIsExtraState ? aState : 0) : 0;
+ var unxpdState = aIsEnabled ? 0 : aIsExtraState ? 0 : aState;
+ var unxpdExtraState = aIsEnabled ? 0 : aIsExtraState ? aState : 0;
+ testStates(
+ event.accessible,
+ state,
+ extraState,
+ unxpdState,
+ unxpdExtraState
+ );
+ };
+
+ this.match = function stateChangeChecker_match(aEvent) {
+ if (aEvent instanceof nsIAccessibleStateChangeEvent) {
+ var scEvent = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
+ return (
+ aEvent.accessible == getAccessible(this.target) &&
+ scEvent.state == aState
+ );
+ }
+ return false;
+ };
+}
+
+function asyncStateChangeChecker(
+ aState,
+ aIsExtraState,
+ aIsEnabled,
+ aTargetOrFunc,
+ aTargetFuncArg
+) {
+ this.__proto__ = new stateChangeChecker(
+ aState,
+ aIsExtraState,
+ aIsEnabled,
+ aTargetOrFunc,
+ aTargetFuncArg,
+ true
+ );
+}
+
+/**
+ * Expanded state change checker.
+ */
+function expandedStateChecker(aIsEnabled, aTargetOrFunc, aTargetFuncArg) {
+ this.__proto__ = new invokerChecker(
+ EVENT_STATE_CHANGE,
+ aTargetOrFunc,
+ aTargetFuncArg
+ );
+
+ this.check = function expandedStateChecker_check(aEvent) {
+ var event = null;
+ try {
+ var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
+ } catch (e) {
+ ok(false, "State change event was expected");
+ }
+
+ if (!event) {
+ return;
+ }
+
+ is(event.state, STATE_EXPANDED, "Wrong state of the statechange event.");
+ is(
+ event.isExtraState,
+ false,
+ "Wrong extra state bit of the statechange event."
+ );
+ is(event.isEnabled, aIsEnabled, "Wrong state of statechange event state");
+
+ testStates(event.accessible, aIsEnabled ? STATE_EXPANDED : STATE_COLLAPSED);
+ };
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Event sequances (array of predefined checkers)
+
+/**
+ * Event seq for single selection change.
+ */
+function selChangeSeq(aUnselectedID, aSelectedID) {
+ if (!aUnselectedID) {
+ return [
+ new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
+ new invokerChecker(EVENT_SELECTION, aSelectedID),
+ ];
+ }
+
+ // Return two possible scenarios: depending on widget type when selection is
+ // moved the the order of items that get selected and unselected may vary.
+ return [
+ [
+ new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
+ new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
+ new invokerChecker(EVENT_SELECTION, aSelectedID),
+ ],
+ [
+ new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
+ new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
+ new invokerChecker(EVENT_SELECTION, aSelectedID),
+ ],
+ ];
+}
+
+/**
+ * Event seq for item removed form the selection.
+ */
+function selRemoveSeq(aUnselectedID) {
+ return [
+ new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
+ new invokerChecker(EVENT_SELECTION_REMOVE, aUnselectedID),
+ ];
+}
+
+/**
+ * Event seq for item added to the selection.
+ */
+function selAddSeq(aSelectedID) {
+ return [
+ new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
+ new invokerChecker(EVENT_SELECTION_ADD, aSelectedID),
+ ];
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Private implementation details.
+// //////////////////////////////////////////////////////////////////////////////
+
+// //////////////////////////////////////////////////////////////////////////////
+// General
+
+var gA11yEventListeners = {};
+var gA11yEventApplicantsCount = 0;
+
+var gA11yEventObserver = {
+ // eslint-disable-next-line complexity
+ observe: function observe(aSubject, aTopic, aData) {
+ if (aTopic != "accessible-event") {
+ return;
+ }
+
+ var event;
+ try {
+ event = aSubject.QueryInterface(nsIAccessibleEvent);
+ } catch (ex) {
+ // After a test is aborted (i.e. timed out by the harness), this exception is soon triggered.
+ // Remove the leftover observer, otherwise it "leaks" to all the following tests.
+ Services.obs.removeObserver(this, "accessible-event");
+ // Forward the exception, with added explanation.
+ throw new Error(
+ "[accessible/events.js, gA11yEventObserver.observe] This is expected " +
+ `if a previous test has been aborted... Initial exception was: [ ${ex} ]`
+ );
+ }
+ var listenersArray = gA11yEventListeners[event.eventType];
+
+ var eventFromDumpArea = false;
+ if (gLogger.isEnabled()) {
+ // debug stuff
+ eventFromDumpArea = true;
+
+ var target = event.DOMNode;
+ var dumpElm = gA11yEventDumpID
+ ? document.getElementById(gA11yEventDumpID)
+ : null;
+
+ if (dumpElm) {
+ var parent = target;
+ while (parent && parent != dumpElm) {
+ parent = parent.parentNode;
+ }
+ }
+
+ if (!dumpElm || parent != dumpElm) {
+ var type = eventTypeToString(event.eventType);
+ var info = "Event type: " + type;
+
+ if (event instanceof nsIAccessibleStateChangeEvent) {
+ var stateStr = statesToString(
+ event.isExtraState ? 0 : event.state,
+ event.isExtraState ? event.state : 0
+ );
+ info += ", state: " + stateStr + ", is enabled: " + event.isEnabled;
+ } else if (event instanceof nsIAccessibleTextChangeEvent) {
+ info +=
+ ", start: " +
+ event.start +
+ ", length: " +
+ event.length +
+ ", " +
+ (event.isInserted ? "inserted" : "removed") +
+ " text: " +
+ event.modifiedText;
+ }
+
+ info += ". Target: " + prettyName(event.accessible);
+
+ if (listenersArray) {
+ info += ". Listeners count: " + listenersArray.length;
+ }
+
+ if (gLogger.hasFeature("parentchain:" + type)) {
+ info += "\nParent chain:\n";
+ var acc = event.accessible;
+ while (acc) {
+ info += " " + prettyName(acc) + "\n";
+ acc = acc.parent;
+ }
+ }
+
+ eventFromDumpArea = false;
+ gLogger.log(info);
+ }
+ }
+
+ // Do not notify listeners if event is result of event log changes.
+ if (!listenersArray || eventFromDumpArea) {
+ return;
+ }
+
+ for (var index = 0; index < listenersArray.length; index++) {
+ listenersArray[index].handleEvent(event);
+ }
+ },
+};
+
+function listenA11yEvents(aStartToListen) {
+ if (aStartToListen) {
+ // Add observer when adding the first applicant only.
+ if (!gA11yEventApplicantsCount++) {
+ Services.obs.addObserver(gA11yEventObserver, "accessible-event");
+ }
+ } else {
+ // Remove observer when there are no more applicants only.
+ // '< 0' case should not happen, but just in case: removeObserver() will throw.
+ // eslint-disable-next-line no-lonely-if
+ if (--gA11yEventApplicantsCount <= 0) {
+ Services.obs.removeObserver(gA11yEventObserver, "accessible-event");
+ }
+ }
+}
+
+function addA11yEventListener(aEventType, aEventHandler) {
+ if (!(aEventType in gA11yEventListeners)) {
+ gA11yEventListeners[aEventType] = [];
+ }
+
+ var listenersArray = gA11yEventListeners[aEventType];
+ var index = listenersArray.indexOf(aEventHandler);
+ if (index == -1) {
+ listenersArray.push(aEventHandler);
+ }
+}
+
+function removeA11yEventListener(aEventType, aEventHandler) {
+ var listenersArray = gA11yEventListeners[aEventType];
+ if (!listenersArray) {
+ return false;
+ }
+
+ var index = listenersArray.indexOf(aEventHandler);
+ if (index == -1) {
+ return false;
+ }
+
+ listenersArray.splice(index, 1);
+
+ if (!listenersArray.length) {
+ gA11yEventListeners[aEventType] = null;
+ delete gA11yEventListeners[aEventType];
+ }
+
+ return true;
+}
+
+/**
+ * Used to dump debug information.
+ */
+var gLogger = {
+ /**
+ * Return true if dump is enabled.
+ */
+ isEnabled: function debugOutput_isEnabled() {
+ return (
+ gA11yEventDumpID || gA11yEventDumpToConsole || gA11yEventDumpToAppConsole
+ );
+ },
+
+ /**
+ * Dump information into DOM and console if applicable.
+ */
+ log: function logger_log(aMsg) {
+ this.logToConsole(aMsg);
+ this.logToAppConsole(aMsg);
+ this.logToDOM(aMsg);
+ },
+
+ /**
+ * Log message to DOM.
+ *
+ * @param aMsg [in] the primary message
+ * @param aHasIndent [in, optional] if specified the message has an indent
+ * @param aPreEmphText [in, optional] the text is colored and appended prior
+ * primary message
+ */
+ logToDOM: function logger_logToDOM(aMsg, aHasIndent, aPreEmphText) {
+ if (gA11yEventDumpID == "") {
+ return;
+ }
+
+ var dumpElm = document.getElementById(gA11yEventDumpID);
+ if (!dumpElm) {
+ ok(
+ false,
+ "No dump element '" + gA11yEventDumpID + "' within the document!"
+ );
+ return;
+ }
+
+ var containerTagName =
+ ChromeUtils.getClassName(document) == "HTMLDocument"
+ ? "div"
+ : "description";
+
+ var container = document.createElement(containerTagName);
+ if (aHasIndent) {
+ container.setAttribute("style", "padding-left: 10px;");
+ }
+
+ if (aPreEmphText) {
+ var inlineTagName =
+ ChromeUtils.getClassName(document) == "HTMLDocument"
+ ? "span"
+ : "description";
+ var emphElm = document.createElement(inlineTagName);
+ emphElm.setAttribute("style", "color: blue;");
+ emphElm.textContent = aPreEmphText;
+
+ container.appendChild(emphElm);
+ }
+
+ var textNode = document.createTextNode(aMsg);
+ container.appendChild(textNode);
+
+ dumpElm.appendChild(container);
+ },
+
+ /**
+ * Log message to console.
+ */
+ logToConsole: function logger_logToConsole(aMsg) {
+ if (gA11yEventDumpToConsole) {
+ dump("\n" + aMsg + "\n");
+ }
+ },
+
+ /**
+ * Log message to error console.
+ */
+ logToAppConsole: function logger_logToAppConsole(aMsg) {
+ if (gA11yEventDumpToAppConsole) {
+ Services.console.logStringMessage("events: " + aMsg);
+ }
+ },
+
+ /**
+ * Return true if logging feature is enabled.
+ */
+ hasFeature: function logger_hasFeature(aFeature) {
+ var startIdx = gA11yEventDumpFeature.indexOf(aFeature);
+ if (startIdx == -1) {
+ return false;
+ }
+
+ var endIdx = startIdx + aFeature.length;
+ return (
+ endIdx == gA11yEventDumpFeature.length ||
+ gA11yEventDumpFeature[endIdx] == ";"
+ );
+ },
+};
+
+// //////////////////////////////////////////////////////////////////////////////
+// Sequence
+
+/**
+ * Base class of sequence item.
+ */
+function sequenceItem(aProcessor, aEventType, aTarget, aItemID) {
+ // private
+
+ this.startProcess = function sequenceItem_startProcess() {
+ this.queue.invoke();
+ };
+
+ this.queue = new eventQueue();
+ this.queue.onFinish = function () {
+ aProcessor.onProcessed();
+ return DO_NOT_FINISH_TEST;
+ };
+
+ var invoker = {
+ invoke: function invoker_invoke() {
+ return aProcessor.process();
+ },
+ getID: function invoker_getID() {
+ return aItemID;
+ },
+ eventSeq: [new invokerChecker(aEventType, aTarget)],
+ };
+
+ this.queue.push(invoker);
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Event queue invokers
+
+/**
+ * Invoker base class for prepare an action.
+ */
+function synthAction(aNodeOrID, aEventsObj) {
+ this.DOMNode = getNode(aNodeOrID);
+
+ if (aEventsObj) {
+ var scenarios = null;
+ if (aEventsObj instanceof Array) {
+ if (aEventsObj[0] instanceof Array) {
+ scenarios = aEventsObj;
+ }
+ // scenarios
+ else {
+ scenarios = [aEventsObj];
+ } // event sequance
+ } else {
+ scenarios = [[aEventsObj]]; // a single checker object
+ }
+
+ for (var i = 0; i < scenarios.length; i++) {
+ defineScenario(this, scenarios[i]);
+ }
+ }
+
+ this.getID = function synthAction_getID() {
+ return prettyName(aNodeOrID) + " action";
+ };
+}
diff --git a/accessible/tests/mochitest/events/a11y.ini b/accessible/tests/mochitest/events/a11y.ini
new file mode 100644
index 0000000000..40ef776175
--- /dev/null
+++ b/accessible/tests/mochitest/events/a11y.ini
@@ -0,0 +1,68 @@
+[DEFAULT]
+support-files =
+ focus.html
+ scroll.html
+ slow_image.sjs
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+ !/image/test/mochitest/animated-gif-finalframe.gif
+ !/image/test/mochitest/animated-gif.gif
+
+[test_announcement.html]
+[test_aria_alert.html]
+[test_aria_menu.html]
+[test_aria_objattr.html]
+[test_aria_owns.html]
+[test_aria_statechange.html]
+[test_attrs.html]
+[test_attrchange.html]
+[test_bug1322593.html]
+[test_bug1322593-2.html]
+[test_caretmove.html]
+[test_coalescence.html]
+[test_contextmenu.html]
+[test_descrchange.html]
+[test_dragndrop.html]
+[test_flush.html]
+[test_focusable_statechange.html]
+[test_focus_aria_activedescendant.html]
+[test_focus_autocomplete.html]
+[test_focus_autocomplete.xhtml]
+# Disabled on Linux and Windows due to frequent failures - bug 695019, bug 890795
+skip-if = os == 'win' || os == 'linux'
+[test_focus_canvas.html]
+[test_focus_contextmenu.xhtml]
+[test_focus_controls.html]
+[test_focus_doc.html]
+[test_focus_general.html]
+[test_focus_general.xhtml]
+[test_focus_listcontrols.xhtml]
+[test_focus_menu.xhtml]
+[test_focus_name.html]
+[test_focus_removal.html]
+[test_focus_selects.html]
+[test_focus_tabbox.xhtml]
+skip-if = true
+[test_focus_tree.xhtml]
+[test_fromUserInput.html]
+[test_label.xhtml]
+[test_menu.xhtml]
+[test_mutation.html]
+[test_namechange.xhtml]
+[test_namechange.html]
+[test_scroll.xhtml]
+[test_scroll_caret.xhtml]
+[test_selection.html]
+skip-if = os == 'mac'
+[test_selection.xhtml]
+skip-if = os == 'mac'
+[test_selection_aria.html]
+[test_statechange.html]
+[test_statechange.xhtml]
+[test_text.html]
+[test_text_alg.html]
+[test_textattrchange.html]
+[test_textselchange.html]
+[test_tree.xhtml]
+[test_valuechange.html]
+skip-if = os == 'mac'
diff --git a/accessible/tests/mochitest/events/docload/a11y.ini b/accessible/tests/mochitest/events/docload/a11y.ini
new file mode 100644
index 0000000000..6e014d511c
--- /dev/null
+++ b/accessible/tests/mochitest/events/docload/a11y.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+support-files =
+ docload_wnd.html
+ !/accessible/tests/mochitest/*.js
+
+[test_docload_aria.html]
+[test_docload_busy.html]
+[test_docload_embedded.html]
+[test_docload_iframe.html]
+[test_docload_root.html]
+skip-if = os == 'mac' # bug 1456997
+[test_docload_shutdown.html]
+skip-if = os == 'mac' # bug 1456997
diff --git a/accessible/tests/mochitest/events/docload/docload_wnd.html b/accessible/tests/mochitest/events/docload/docload_wnd.html
new file mode 100644
index 0000000000..93df1e86d4
--- /dev/null
+++ b/accessible/tests/mochitest/events/docload/docload_wnd.html
@@ -0,0 +1,37 @@
+<html>
+<head>
+ <title>Accessible events testing for document</title>
+ <script>
+ const STATE_BUSY = Ci.nsIAccessibleStates.STATE_BUSY;
+
+ var gService = null;
+ function waitForDocLoad() {
+ if (!gService) {
+ gService = Cc["@mozilla.org/accessibilityService;1"].
+ getService(Ci.nsIAccessibilityService);
+ }
+
+ var accDoc = gService.getAccessibleFor(document);
+
+ var state = {};
+ accDoc.getState(state, {});
+ if (state.value & STATE_BUSY) {
+ window.setTimeout(waitForDocLoad, 0);
+ return;
+ }
+
+ hideIFrame();
+ }
+
+ function hideIFrame() {
+ var iframe = document.getElementById("iframe");
+ gService.getAccessibleFor(iframe.contentDocument);
+ iframe.style.display = "none";
+ }
+ </script>
+</head>
+
+<body onload="waitForDocLoad();">
+ <iframe id="iframe"></iframe>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/docload/test_docload_aria.html b/accessible/tests/mochitest/events/docload/test_docload_aria.html
new file mode 100644
index 0000000000..c5fc099918
--- /dev/null
+++ b/accessible/tests/mochitest/events/docload/test_docload_aria.html
@@ -0,0 +1,75 @@
+<html>
+
+<head>
+ <title>Accessible events testing for ARIA document</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../../common.js"></script>
+ <script type="application/javascript"
+ src="../../role.js"></script>
+ <script type="application/javascript"
+ src="../../states.js"></script>
+ <script type="application/javascript"
+ src="../../events.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function showARIADialog(aID) {
+ this.dialogNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, this.dialogNode),
+ ];
+
+ this.invoke = function showARIADialog_invoke() {
+ this.dialogNode.style.display = "block";
+ };
+
+ this.getID = function showARIADialog_getID() {
+ return "show ARIA dialog";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ var gQueue = null;
+
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new showARIADialog("dialog"));
+ gQueue.push(new showARIADialog("document"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=759833"
+ title="ARIA documents should fire document loading events">
+ Mozilla Bug 759833
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="dialog" id="dialog" style="display: none;">It's a dialog</div>
+ <div role="document" id="document" style="display: none;">It's a document</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/docload/test_docload_busy.html b/accessible/tests/mochitest/events/docload/test_docload_busy.html
new file mode 100644
index 0000000000..37caf306bb
--- /dev/null
+++ b/accessible/tests/mochitest/events/docload/test_docload_busy.html
@@ -0,0 +1,83 @@
+<html>
+
+<head>
+ <title>Accessible events testing for document</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../../common.js"></script>
+ <script type="application/javascript"
+ src="../../role.js"></script>
+ <script type="application/javascript"
+ src="../../states.js"></script>
+ <script type="application/javascript"
+ src="../../events.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function makeIFrameVisible(aID) {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.DOMNode.parentNode),
+ {
+ type: EVENT_STATE_CHANGE,
+ get target() {
+ return getAccessible("iframe").firstChild;
+ },
+ match(aEvent) {
+ // The document shouldn't have busy state (the DOM document was
+ // loaded before its accessible was created). Do this test lately to
+ // make sure the content of document accessible was created
+ // initially, prior to this the document accessible keeps busy
+ // state. The initial creation happens asynchronously after document
+ // creation, there are no events we could use to catch it.
+ let { state, isEnabled } = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
+ return state & STATE_BUSY && !isEnabled;
+ },
+ },
+ ];
+
+ this.invoke = () => (this.DOMNode.style.visibility = "visible");
+
+ this.getID = () =>
+ "The accessible for DOM document loaded before it's shown shouldn't have busy state.";
+ }
+
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ function doTests() {
+ const gQueue = new eventQueue();
+ gQueue.push(new makeIFrameVisible("iframe"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=658185"
+ title="The DOM document loaded before it's shown shouldn't have busy state">
+ Mozilla Bug 658185
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="testContainer"><iframe id="iframe" src="about:mozilla" style="visibility: hidden;"></iframe></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/docload/test_docload_embedded.html b/accessible/tests/mochitest/events/docload/test_docload_embedded.html
new file mode 100644
index 0000000000..18873dc904
--- /dev/null
+++ b/accessible/tests/mochitest/events/docload/test_docload_embedded.html
@@ -0,0 +1,85 @@
+<html>
+
+<head>
+ <title>Accessible events testing for document</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../../common.js"></script>
+ <script type="application/javascript"
+ src="../../role.js"></script>
+ <script type="application/javascript"
+ src="../../events.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function changeIframeSrc(aIdentifier, aURL, aTitle) {
+ this.DOMNode = getNode(aIdentifier);
+
+ function getIframeDoc() {
+ return getAccessible(getNode(aIdentifier).contentDocument);
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getAccessible(this.DOMNode)),
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getIframeDoc),
+ ];
+
+ this.invoke = () => (this.DOMNode.src = aURL);
+
+ this.finalCheck = () =>
+ testAccessibleTree(this.DOMNode, {
+ role: ROLE_INTERNAL_FRAME,
+ children: [
+ {
+ role: ROLE_DOCUMENT,
+ name: aTitle,
+ },
+ ],
+ });
+
+ this.getID = () => `change iframe src on ${aURL}`;
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ function doTests() {
+ const gQueue = new eventQueue();
+ gQueue.push(new changeIframeSrc("iframe", "about:license", "Licenses"));
+ gQueue.push(new changeIframeSrc("iframe", "about:buildconfig", "Build Configuration"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=420845"
+ title="Fire event_reorder on any embedded frames/iframes whos document has just loaded">
+ Mozilla Bug 420845
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=754165"
+ title="Fire document load events on iframes too">
+ Mozilla Bug 754165
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="testContainer"><iframe id="iframe"></iframe></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/docload/test_docload_iframe.html b/accessible/tests/mochitest/events/docload/test_docload_iframe.html
new file mode 100644
index 0000000000..d410ebb7e2
--- /dev/null
+++ b/accessible/tests/mochitest/events/docload/test_docload_iframe.html
@@ -0,0 +1,99 @@
+<html>
+
+<head>
+ <title>Accessible events testing for document</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../../common.js"></script>
+ <script type="application/javascript"
+ src="../../role.js"></script>
+ <script type="application/javascript"
+ src="../../states.js"></script>
+ <script type="application/javascript"
+ src="../../events.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ const kHide = 1;
+ const kShow = 2;
+ const kRemove = 3;
+
+ function morphIFrame(aIdentifier, aAction) {
+ this.DOMNode = getNode(aIdentifier);
+ this.IFrameContainerDOMNode = this.DOMNode.parentNode;
+
+ this.eventSeq = [
+ new invokerChecker(aAction === kShow ? EVENT_SHOW : EVENT_HIDE, this.DOMNode),
+ new invokerChecker(EVENT_REORDER, this.IFrameContainerDOMNode),
+ ];
+
+ this.invoke = () => {
+ if (aAction === kRemove) {
+ this.IFrameContainerDOMNode.removeChild(this.DOMNode);
+ } else {
+ this.DOMNode.style.display = aAction === kHide ? "none" : "block";
+ }
+ };
+
+ this.finalCheck = () =>
+ testAccessibleTree(this.IFrameContainerDOMNode, {
+ role: ROLE_SECTION,
+ children: (aAction == kHide || aAction == kRemove) ? [ ] :
+ [
+ {
+ role: ROLE_INTERNAL_FRAME,
+ children: [
+ { role: ROLE_DOCUMENT },
+ ],
+ },
+ ],
+ });
+
+ this.getID = () => {
+ if (aAction === kRemove) {
+ return "remove iframe";
+ }
+
+ return `change display style of iframe to ${aAction === kHide ? "none" : "block"}`;
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ function doTests() {
+ const gQueue = new eventQueue(EVENT_REORDER);
+ gQueue.push(new morphIFrame("iframe", kHide));
+ gQueue.push(new morphIFrame("iframe", kShow));
+ gQueue.push(new morphIFrame("iframe", kRemove));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=566103"
+ title="Reorganize accessible document handling">
+ Mozilla Bug 566103
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="testContainer"><iframe id="iframe"></iframe></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/docload/test_docload_root.html b/accessible/tests/mochitest/events/docload/test_docload_root.html
new file mode 100644
index 0000000000..91ce3a10ee
--- /dev/null
+++ b/accessible/tests/mochitest/events/docload/test_docload_root.html
@@ -0,0 +1,125 @@
+<html>
+
+<head>
+ <title>Accessible events testing for document</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../../common.js"></script>
+ <script type="application/javascript"
+ src="../../role.js"></script>
+ <script type="application/javascript"
+ src="../../events.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ let gDialog;
+ let gDialogDoc;
+ let gRootAcc;
+
+ function openDialogWnd(aURL) {
+ // Get application root accessible.
+ let docAcc = getAccessible(document);
+ while (docAcc) {
+ gRootAcc = docAcc;
+ try {
+ docAcc = docAcc.parent;
+ } catch (e) {
+ ok(false, `Can't get parent for ${prettyName(docAcc)}`);
+ throw e;
+ }
+ }
+
+ this.eventSeq = [
+ new asyncInvokerChecker(EVENT_REORDER, gRootAcc),
+ // We use a function here to get the target because gDialog isn't set
+ // yet, but it will be when the function is called.
+ new invokerChecker(EVENT_FOCUS, () => gDialog.document)
+ ];
+
+ this.invoke = () => (gDialog = window.browsingContext.topChromeWindow.openDialog(aURL));
+
+ this.finalCheck = () => {
+ const accTree = {
+ role: ROLE_APP_ROOT,
+ children: [
+ {
+ role: ROLE_CHROME_WINDOW,
+ },
+ {
+ role: ROLE_CHROME_WINDOW,
+ },
+ ],
+ };
+
+ testAccessibleTree(gRootAcc, accTree);
+
+ gDialogDoc = gDialog.document;
+ ok(isAccessibleInCache(gDialogDoc),
+ `The document accessible for '${aURL}' is not in cache!`);
+ };
+
+ this.getID = () => `open dialog '${aURL}'`;
+ }
+
+ function closeDialogWnd() {
+ this.eventSeq = [ new invokerChecker(EVENT_FOCUS, getAccessible(document)) ];
+
+ this.invoke = () => {
+ gDialog.close();
+ window.focus();
+ };
+
+ this.finalCheck = () => {
+ ok(!isAccessibleInCache(gDialogDoc),
+ `The document accessible for dialog is in cache still!`);
+
+ gDialog = gDialogDoc = gRootAcc = null;
+ };
+
+ this.getID = () => "close dialog";
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ function doTests() {
+ // Front end stuff sometimes likes to stuff things in the hidden window(s)
+ // in which case we should repress all accessibles for those.
+
+ // Try to create an accessible for the hidden window's document.
+ let doc = Services.appShell.hiddenDOMWindow.document;
+ let hiddenDocAcc = gAccService.getAccessibleFor(doc);
+ ok(!hiddenDocAcc, "hiddenDOMWindow should not have an accessible.");
+
+ const gQueue = new eventQueue();
+ gQueue.push(new openDialogWnd("about:about"));
+ gQueue.push(new closeDialogWnd());
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=506206"
+ title="Fire event_reorder application root accessible">
+ Mozilla Bug 506206
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/docload/test_docload_shutdown.html b/accessible/tests/mochitest/events/docload/test_docload_shutdown.html
new file mode 100644
index 0000000000..a111d9e43b
--- /dev/null
+++ b/accessible/tests/mochitest/events/docload/test_docload_shutdown.html
@@ -0,0 +1,142 @@
+<html>
+
+<head>
+ <title>Accessible events testing for document</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../../common.js"></script>
+ <script type="application/javascript"
+ src="../../role.js"></script>
+ <script type="application/javascript"
+ src="../../events.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ let gDialog;
+ let gDialogDoc;
+ let gRootAcc;
+ let gIframeDoc;
+
+ function openWndShutdownDoc(aURL) {
+ // Get application root accessible.
+ let docAcc = getAccessible(document);
+ while (docAcc) {
+ gRootAcc = docAcc;
+ try {
+ docAcc = docAcc.parent;
+ } catch (e) {
+ ok(false, `Can't get parent for ${prettyName(docAcc)}`);
+ throw e;
+ }
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, gRootAcc),
+ {
+ type: EVENT_HIDE,
+ get target() {
+ gDialogDoc = gDialog.document;
+ const iframe = gDialogDoc.getElementById("iframe");
+ gIframeDoc = iframe.contentDocument;
+ return iframe;
+ },
+ get targetDescr() {
+ return "inner iframe of docload_wnd.html document";
+ },
+ },
+ ];
+
+
+ this.invoke = () => gDialog = window.browsingContext.topChromeWindow.openDialog(aURL);
+
+ this.finalCheck = () => {
+ const accTree = {
+ role: ROLE_APP_ROOT,
+ children: [
+ {
+ role: ROLE_CHROME_WINDOW,
+ },
+ {
+ role: ROLE_CHROME_WINDOW,
+ },
+ ],
+ };
+
+ testAccessibleTree(gRootAcc, accTree);
+ // After timeout after event hide for iframe was handled the document
+ // accessible for iframe's document should no longer be in cache.
+ ok(!isAccessibleInCache(gIframeDoc),
+ "The document accessible for iframe is in cache still after iframe hide!");
+ ok(isAccessibleInCache(gDialogDoc),
+ `The document accessible for '${aURL}' is not in cache!`);
+ };
+
+ this.getID = () => `open dialog '${aURL}'`;
+ }
+
+ function closeWndShutdownDoc() {
+ this.eventSeq = [ new invokerChecker(EVENT_FOCUS, getAccessible(document)) ];
+
+ this.invoke = () => {
+ gDialog.close();
+ window.focus();
+ };
+
+ this.finalCheck = () => {
+ ok(!isAccessibleInCache(gDialogDoc),
+ "The document accessible for dialog is in cache still!");
+ // After the window is closed all alive subdocument accessibles should
+ // be shut down.
+ ok(!isAccessibleInCache(gIframeDoc),
+ "The document accessible for iframe is in cache still!");
+
+ gDialog = gDialogDoc = gRootAcc = gIframeDoc = null;
+ };
+
+ this.getID = () => "close dialog";
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ function doTests() {
+ // Front end stuff sometimes likes to stuff things in the hidden window(s)
+ // in which case we should repress all accessibles for those.
+
+ // Try to create an accessible for the hidden window's document.
+ let doc = Services.appShell.hiddenDOMWindow.document;
+ let hiddenDocAcc = gAccService.getAccessibleFor(doc);
+ ok(!hiddenDocAcc, "hiddenDOMWindow should not have an accessible.");
+
+ const gQueue = new eventQueue();
+ gQueue.push(new openWndShutdownDoc("../../events/docload/docload_wnd.html"));
+ gQueue.push(new closeWndShutdownDoc());
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=571459"
+ title="Shutdown document accessible when presshell goes away">
+ Mozilla Bug 571459
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/focus.html b/accessible/tests/mochitest/events/focus.html
new file mode 100644
index 0000000000..ab055df82c
--- /dev/null
+++ b/accessible/tests/mochitest/events/focus.html
@@ -0,0 +1,10 @@
+<html>
+
+<head>
+ <title>editable document</title>
+</head>
+
+<body contentEditable="true">
+ editable document
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/scroll.html b/accessible/tests/mochitest/events/scroll.html
new file mode 100644
index 0000000000..562e0a3825
--- /dev/null
+++ b/accessible/tests/mochitest/events/scroll.html
@@ -0,0 +1,181 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing for anchors</title>
+</head>
+
+<body>
+ <p>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ </p>
+ <a name="link1">link1</a>
+
+ <p style="color: blue">
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ </p>
+
+ <h1 id="heading_1">heading 1</h1>
+ <p style="color: blue">
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ </p>
+</body>
+<html>
diff --git a/accessible/tests/mochitest/events/slow_image.sjs b/accessible/tests/mochitest/events/slow_image.sjs
new file mode 100644
index 0000000000..f322568be6
--- /dev/null
+++ b/accessible/tests/mochitest/events/slow_image.sjs
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+// small red image
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
+);
+
+// stolen from file_blocked_script.sjs
+function setGlobalState(data, key) {
+ let x = {
+ data,
+ QueryInterface: ChromeUtils.generateQI([]),
+ };
+ x.wrappedJSObject = x;
+ setObjectState(key, x);
+}
+
+function getGlobalState(key) {
+ var data;
+ getObjectState(key, function (x) {
+ data = x && x.wrappedJSObject.data;
+ });
+ return data;
+}
+
+function handleRequest(request, response) {
+ if (request.queryString == "complete") {
+ // Unblock the previous request.
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "application/json", false);
+ response.write("true"); // the payload doesn't matter.
+
+ let blockedResponse = getGlobalState("a11y-image");
+ if (blockedResponse) {
+ blockedResponse.setStatusLine(request.httpVersion, 200, "OK");
+ blockedResponse.setHeader("Cache-Control", "no-cache", false);
+ blockedResponse.setHeader("Content-Type", "image/png", false);
+ blockedResponse.write(IMG_BYTES);
+ blockedResponse.finish();
+
+ setGlobalState(undefined, "a11y-image");
+ }
+ } else {
+ // Getting the image
+ response.processAsync();
+ // Store the response in the global state
+ setGlobalState(response, "a11y-image");
+ }
+}
diff --git a/accessible/tests/mochitest/events/test_announcement.html b/accessible/tests/mochitest/events/test_announcement.html
new file mode 100644
index 0000000000..eb303e4aa9
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_announcement.html
@@ -0,0 +1,61 @@
+<html>
+
+<head>
+ <title>Announcement event and method testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+ async function doTests() {
+ let acc = getAccessible("display");
+
+ let onAnnounce = waitForEvent(EVENT_ANNOUNCEMENT, acc);
+ acc.announce("please", nsIAccessibleAnnouncementEvent.POLITE);
+ let evt = await onAnnounce;
+ evt.QueryInterface(nsIAccessibleAnnouncementEvent);
+ is(evt.announcement, "please", "announcement matches.");
+ is(evt.priority, nsIAccessibleAnnouncementEvent.POLITE, "priority matches");
+
+ onAnnounce = waitForEvent(EVENT_ANNOUNCEMENT, acc);
+ acc.announce("do it", nsIAccessibleAnnouncementEvent.ASSERTIVE);
+ evt = await onAnnounce;
+ evt.QueryInterface(nsIAccessibleAnnouncementEvent);
+ is(evt.announcement, "do it", "announcement matches.");
+ is(evt.priority, nsIAccessibleAnnouncementEvent.ASSERTIVE,
+ "priority matches");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1525980"
+ title="Introduce announcement event and method">
+ Mozilla Bug 1525980
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_aria_alert.html b/accessible/tests/mochitest/events/test_aria_alert.html
new file mode 100644
index 0000000000..48f4197b50
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_alert.html
@@ -0,0 +1,84 @@
+<html>
+
+<head>
+ <title>ARIA alert event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function showAlert(aID) {
+ this.DOMNode = document.createElement("div");
+
+ this.invoke = function showAlert_invoke() {
+ this.DOMNode.setAttribute("role", "alert");
+ this.DOMNode.setAttribute("id", aID);
+ var text = document.createTextNode("alert");
+ this.DOMNode.appendChild(text);
+ document.body.appendChild(this.DOMNode);
+ };
+
+ this.getID = function showAlert_getID() {
+ return "Show ARIA alert " + aID;
+ };
+ }
+
+ function changeAlert(aID) {
+ this.__defineGetter__("DOMNode", function() { return getNode(aID); });
+
+ this.invoke = function changeAlert_invoke() {
+ this.DOMNode.textContent = "new alert";
+ };
+
+ this.getID = function showAlert_getID() {
+ return "Change ARIA alert " + aID;
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ // gA11yEventDumpToConsole = true; // debuging
+ // enableLogging("tree,events,verbose");
+
+ var gQueue = null;
+ function doTests() {
+ gQueue = new eventQueue(nsIAccessibleEvent.EVENT_ALERT);
+
+ gQueue.push(new showAlert("alert"));
+ gQueue.push(new changeAlert("alert"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=591199"
+ title="mochitest for bug 334386: fire alert event when ARIA alert is shown or new its children are inserted">
+ Mozilla Bug 591199
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_aria_menu.html b/accessible/tests/mochitest/events/test_aria_menu.html
new file mode 100644
index 0000000000..b240090cb9
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_menu.html
@@ -0,0 +1,267 @@
+<html>
+
+<head>
+ <title>ARIA menu events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ const kViaDisplayStyle = 0;
+ const kViaVisibilityStyle = 1;
+
+ function focusMenu(aMenuBarID, aMenuID, aActiveMenuBarID) {
+ this.eventSeq = [];
+
+ if (aActiveMenuBarID) {
+ this.eventSeq.push(new invokerChecker(EVENT_MENU_END,
+ getNode(aActiveMenuBarID)));
+ }
+
+ this.eventSeq.push(new invokerChecker(EVENT_MENU_START, getNode(aMenuBarID)));
+ this.eventSeq.push(new invokerChecker(EVENT_FOCUS, getNode(aMenuID)));
+
+ this.invoke = function focusMenu_invoke() {
+ getNode(aMenuID).focus();
+ };
+
+ this.getID = function focusMenu_getID() {
+ return "focus menu '" + aMenuID + "'";
+ };
+ }
+
+ function showMenu(aMenuID, aParentMenuID, aHow) {
+ this.menuNode = getNode(aMenuID);
+
+ // Because of aria-owns processing we may have menupopup start fired before
+ // related show event.
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.menuNode),
+ new invokerChecker(EVENT_REORDER, getNode(aParentMenuID)),
+ new invokerChecker(EVENT_MENUPOPUP_START, this.menuNode),
+ ];
+
+ this.invoke = function showMenu_invoke() {
+ if (aHow == kViaDisplayStyle)
+ this.menuNode.style.display = "block";
+ else
+ this.menuNode.style.visibility = "visible";
+ };
+
+ this.getID = function showMenu_getID() {
+ return "Show ARIA menu '" + aMenuID + "' by " +
+ (aHow == kViaDisplayStyle ? "display" : "visibility") +
+ " style tricks";
+ };
+ }
+
+ function closeMenu(aMenuID, aParentMenuID, aHow) {
+ this.menuNode = getNode(aMenuID);
+ this.menu = null;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getMenu, this),
+ new invokerChecker(EVENT_MENUPOPUP_END, getMenu, this),
+ new invokerChecker(EVENT_REORDER, getNode(aParentMenuID)),
+ ];
+
+ this.invoke = function closeMenu_invoke() {
+ // Store menu accessible reference while menu is still open.
+ this.menu = getAccessible(this.menuNode);
+
+ // Hide menu.
+ if (aHow == kViaDisplayStyle)
+ this.menuNode.style.display = "none";
+ else
+ this.menuNode.style.visibility = "hidden";
+ };
+
+ this.getID = function closeMenu_getID() {
+ return "Close ARIA menu " + aMenuID + " by " +
+ (aHow == kViaDisplayStyle ? "display" : "visibility") +
+ " style tricks";
+ };
+
+ function getMenu(aThisObj) {
+ return aThisObj.menu;
+ }
+ }
+
+ function focusInsideMenu(aMenuID, aMenuBarID) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getNode(aMenuID)),
+ ];
+
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_MENU_END, getNode(aMenuBarID)),
+ ];
+
+ this.invoke = function focusInsideMenu_invoke() {
+ getNode(aMenuID).focus();
+ };
+
+ this.getID = function focusInsideMenu_getID() {
+ return "focus menu '" + aMenuID + "'";
+ };
+ }
+
+ function blurMenu(aMenuBarID) {
+ var eventSeq = [
+ new invokerChecker(EVENT_MENU_END, getNode(aMenuBarID)),
+ new invokerChecker(EVENT_FOCUS, getNode("outsidemenu")),
+ ];
+
+ this.__proto__ = new synthClick("outsidemenu", eventSeq);
+
+ this.getID = function blurMenu_getID() {
+ return "blur menu";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ // gA11yEventDumpToConsole = true; // debuging
+ // enableLogging("tree,events,verbose");
+
+ var gQueue = null;
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new focusMenu("menubar2", "menu-help"));
+ gQueue.push(new focusMenu("menubar", "menu-file", "menubar2"));
+ gQueue.push(new showMenu("menupopup-file", "menu-file", kViaDisplayStyle));
+ gQueue.push(new closeMenu("menupopup-file", "menu-file", kViaDisplayStyle));
+ gQueue.push(new showMenu("menupopup-edit", "menu-edit", kViaVisibilityStyle));
+ gQueue.push(new closeMenu("menupopup-edit", "menu-edit", kViaVisibilityStyle));
+ gQueue.push(new focusInsideMenu("menu-edit", "menubar"));
+ gQueue.push(new blurMenu("menubar"));
+
+ gQueue.push(new focusMenu("menubar3", "mb3-mi-outside"));
+ gQueue.push(new showMenu("mb4-menu", document, kViaDisplayStyle));
+ gQueue.push(new focusMenu("menubar4", "mb4-item1"));
+ gQueue.push(new focusMenu("menubar5", "mb5-mi"));
+
+ gQueue.push(new synthFocus("mi6"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=606207"
+ title="Dojo dropdown buttons are broken">
+ Bug 606207
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=614829"
+ title="Menupopup end event isn't fired for ARIA menus">
+ Bug 614829
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=615189"
+ title="Clean up FireAccessibleFocusEvent">
+ Bug 615189
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Bug 673958
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=933322"
+ title="menustart/end events are missing when aria-owns makes a menu hierarchy">
+ Bug 933322
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=934460"
+ title="menustart/end events may be missed when top level menuitem is focused">
+ Bug 934460
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=970005"
+ title="infinite long loop in a11y:FocusManager::ProcessFocusEvent">
+ Bug 970005
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="menubar" role="menubar">
+ <div id="menu-file" role="menuitem" tabindex="0">
+ File
+ <div id="menupopup-file" role="menu" style="display: none;">
+ <div id="menuitem-newtab" role="menuitem" tabindex="0">New Tab</div>
+ <div id="menuitem-newwindow" role="menuitem" tabindex="0">New Window</div>
+ </div>
+ </div>
+ <div id="menu-edit" role="menuitem" tabindex="0">
+ Edit
+ <div id="menupopup-edit" role="menu" style="visibility: hidden;">
+ <div id="menuitem-undo" role="menuitem" tabindex="0">Undo</div>
+ <div id="menuitem-redo" role="menuitem" tabindex="0">Redo</div>
+ </div>
+ </div>
+ </div>
+ <div id="menubar2" role="menubar">
+ <div id="menu-help" role="menuitem" tabindex="0">
+ Help
+ <div id="menupopup-help" role="menu" style="display: none;">
+ <div id="menuitem-about" role="menuitem" tabindex="0">About</div>
+ </div>
+ </div>
+ </div>
+ <div tabindex="0" id="outsidemenu">outsidemenu</div>
+
+ <!-- aria-owns relations -->
+ <div id="menubar3" role="menubar" aria-owns="mb3-mi-outside"></div>
+ <div id="mb3-mi-outside" role="menuitem" tabindex="0">Outside</div>
+
+ <div id="menubar4" role="menubar">
+ <div id="mb4_topitem" role="menuitem" aria-haspopup="true"
+ aria-owns="mb4-menu">Item</div>
+ </div>
+ <div id="mb4-menu" role="menu" style="display:none;">
+ <div role="menuitem" id="mb4-item1" tabindex="0">Item 1.1</div>
+ <div role="menuitem" tabindex="0">Item 1.2</div>
+ </div>
+
+ <!-- focus top-level menu item having haspopup -->
+ <div id="menubar5" role="menubar">
+ <div role="menuitem" aria-haspopup="true" id="mb5-mi" tabindex="0">
+ Item
+ <div role="menu" style="display:none;">
+ <div role="menuitem" tabindex="0">Item 1.1</div>
+ <div role="menuitem" tabindex="0">Item 1.2</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- other aria-owns relations -->
+ <div id="mi6" tabindex="0" role="menuitem">aria-owned item</div>
+ <div aria-owns="mi6">Obla</div>
+
+ <div id="eventdump"></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_aria_objattr.html b/accessible/tests/mochitest/events/test_aria_objattr.html
new file mode 100644
index 0000000000..709089ca02
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_objattr.html
@@ -0,0 +1,68 @@
+<html>
+
+<head>
+ <title>Accessible ARIA object attribute changes</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+ function updateAttribute(aID, aAttr, aValue) {
+ this.node = getNode(aID);
+ this.accessible = getAccessible(this.node);
+
+ this.eventSeq = [
+ new objAttrChangedChecker(aID, aAttr),
+ ];
+
+ this.invoke = function updateAttribute_invoke() {
+ this.node.setAttribute(aAttr, aValue);
+ };
+
+ this.getID = function updateAttribute_getID() {
+ return aAttr + " for " + aID + " " + aValue;
+ };
+ }
+
+ // gA11yEventDumpToConsole = true;
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new updateAttribute("sortable", "aria-sort", "ascending"));
+
+ // For experimental ARIA extensions
+ gQueue.push(new updateAttribute("custom", "aria-blah", "true"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="sortable" role="columnheader" aria-sort="none">aria-sort</div>
+
+ <div id="custom" role="custom" aria-blah="false">Fat free cheese</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_aria_owns.html b/accessible/tests/mochitest/events/test_aria_owns.html
new file mode 100644
index 0000000000..3c638ad838
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_owns.html
@@ -0,0 +1,122 @@
+<html>
+
+<head>
+ <title>Aria-owns targets shouldn't be on invalidation list so shouldn't have
+ show/hide events</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests.
+
+ // gA11yEventDumpToConsole = true; // debug stuff
+ // enableLogging("tree,eventTree,verbose");
+
+ /**
+ * Aria-owns target shouldn't have a show event.
+ * Markup:
+ * <div id="t1_fc" aria-owns="t1_owns"></div>
+ * <span id="t1_owns"></div>
+ */
+ function testAriaOwns() {
+ this.parent = getNode("t1");
+ this.fc = document.createElement("div");
+ this.fc.setAttribute("id", "t1_fc");
+ this.owns = document.createElement("span");
+ this.owns.setAttribute("id", "t1_owns");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.fc),
+ new unexpectedInvokerChecker(EVENT_SHOW, this.owns),
+ ];
+
+ this.invoke = function testAriaOwns_invoke() {
+ getNode("t1").appendChild(this.fc);
+ getNode("t1").appendChild(this.owns);
+ getNode("t1_fc").setAttribute("aria-owns", "t1_owns");
+ };
+
+ this.getID = function testAriaOwns_getID() {
+ return "Aria-owns target shouldn't have show event";
+ };
+ }
+
+ /**
+ * Target of both aria-owns and other aria attribute like aria-labelledby
+ * shouldn't have a show event.
+ * Markup:
+ * <div id="t2_fc" aria-owns="t1_owns"></div>
+ * <div id="t2_sc" aria-labelledby="t2_owns"></div>
+ * <span id="t2_owns"></div>
+ */
+ function testAriaOwnsAndLabelledBy() {
+ this.parent = getNode("t2");
+ this.fc = document.createElement("div");
+ this.fc.setAttribute("id", "t2_fc");
+ this.sc = document.createElement("div");
+ this.sc.setAttribute("id", "t2_sc");
+ this.owns = document.createElement("span");
+ this.owns.setAttribute("id", "t2_owns");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.fc),
+ new invokerChecker(EVENT_SHOW, this.sc),
+ new unexpectedInvokerChecker(EVENT_SHOW, this.owns),
+ ];
+
+ this.invoke = function testAriaOwns_invoke() {
+ getNode("t2").appendChild(this.fc);
+ getNode("t2").appendChild(this.sc);
+ getNode("t2").appendChild(this.owns);
+ getNode("t2_fc").setAttribute("aria-owns", "t2_owns");
+ getNode("t2_sc").setAttribute("aria-labelledby", "t2_owns");
+ };
+
+ this.getID = function testAriaOwns_getID() {
+ return "Aria-owns and aria-labelledby target shouldn't have show event";
+ };
+ }
+
+ var gQueue = null;
+ function doTests() {
+ gQueue = new eventQueue();
+ gQueue.push(new testAriaOwns());
+ gQueue.push(new testAriaOwnsAndLabelledBy());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1296420"
+ title="Aria-owns targets shouldn't be on invalidation list so shouldn't
+ have show/hide events">
+ Mozilla Bug 1296420
+ </a><br>
+
+ <div id="testContainer">
+ <div id="t1"></div>
+
+ <div id="t2"></div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_aria_statechange.html b/accessible/tests/mochitest/events/test_aria_statechange.html
new file mode 100644
index 0000000000..7796d88ec4
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_statechange.html
@@ -0,0 +1,231 @@
+<html>
+
+<head>
+ <title>ARIA state change event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ let PromEvents = {};
+ Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js",
+ PromEvents);
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ // gA11yEventDumpID = "eventdump"; // debugging
+ // gA11yEventDumpToConsole = true; // debugging
+
+ function expandNode(aID, aIsExpanded) {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new expandedStateChecker(aIsExpanded, this.DOMNode),
+ ];
+
+ this.invoke = function expandNode_invoke() {
+ this.DOMNode.setAttribute("aria-expanded",
+ (aIsExpanded ? "true" : "false"));
+ };
+
+ this.getID = function expandNode_getID() {
+ return prettyName(aID) + " aria-expanded changed to '" + aIsExpanded + "'";
+ };
+ }
+
+ function busyify(aID, aIsBusy) {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new stateChangeChecker(STATE_BUSY, kOrdinalState, aIsBusy, this.DOMNode),
+ ];
+
+ this.invoke = function busyify_invoke() {
+ this.DOMNode.setAttribute("aria-busy", (aIsBusy ? "true" : "false"));
+ };
+
+ this.getID = function busyify_getID() {
+ return prettyName(aID) + " aria-busy changed to '" + aIsBusy + "'";
+ };
+ }
+
+ function makeCurrent(aID, aIsCurrent, aValue) {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new stateChangeChecker(EXT_STATE_CURRENT, true, aIsCurrent, this.DOMNode),
+ ];
+
+ this.invoke = function makeCurrent_invoke() {
+ this.DOMNode.setAttribute("aria-current", aValue);
+ };
+
+ this.getID = function makeCurrent_getID() {
+ return prettyName(aID) + " aria-current changed to " + aValue;
+ };
+ }
+
+ async function testToggleAttribute(aID, aAttribute, aIncludeMixed) {
+ let toggleState = aAttribute == "aria-pressed" ? STATE_PRESSED : STATE_CHECKED;
+
+ // bug 472142. Role changes here if aria-pressed is added,
+ // accessible should be recreated?
+ let stateChange = PromEvents.waitForStateChange(aID, toggleState, true);
+ getNode(aID).setAttribute(aAttribute, "true");
+ await stateChange;
+
+ stateChange = PromEvents.waitForStateChange(aID, toggleState, false);
+ getNode(aID).setAttribute(aAttribute, "false");
+ await stateChange;
+
+ if (aIncludeMixed) {
+ stateChange = PromEvents.waitForStateChange(aID, STATE_MIXED, true);
+ getNode(aID).setAttribute(aAttribute, "mixed");
+ await stateChange;
+
+ stateChange = PromEvents.waitForStateChange(aID, STATE_MIXED, false);
+ getNode(aID).setAttribute(aAttribute, "");
+ await stateChange;
+ }
+
+ stateChange = PromEvents.waitForStateChange(aID, toggleState, true);
+ getNode(aID).setAttribute(aAttribute, "true");
+ await stateChange;
+
+ if (aIncludeMixed) {
+ stateChange = Promise.all([
+ PromEvents.waitForStateChange(aID, STATE_MIXED, true),
+ PromEvents.waitForStateChange(aID, toggleState, false)]);
+ getNode(aID).setAttribute(aAttribute, "mixed");
+ await stateChange;
+
+ stateChange = Promise.all([
+ PromEvents.waitForStateChange(aID, STATE_MIXED, false),
+ PromEvents.waitForStateChange(aID, toggleState, true)]);
+ getNode(aID).setAttribute(aAttribute, "true");
+ await stateChange;
+ }
+
+ // bug 472142. Role changes here too if aria-pressed is removed,
+ // accessible should be recreated?
+ stateChange = PromEvents.waitForStateChange(aID, toggleState, false);
+ getNode(aID).removeAttribute(aAttribute);
+ await stateChange;
+ }
+
+ async function doTests() {
+ gQueue = new eventQueue();
+
+ let queueFinished = new Promise(resolve => {
+ gQueue.onFinish = function() {
+ resolve();
+ return DO_NOT_FINISH_TEST;
+ };
+ });
+
+ gQueue.push(new expandNode("section", true));
+ gQueue.push(new expandNode("section", false));
+ gQueue.push(new expandNode("div", true));
+ gQueue.push(new expandNode("div", false));
+
+ gQueue.push(new busyify("aria_doc", true));
+ gQueue.push(new busyify("aria_doc", false));
+
+ gQueue.push(new makeCurrent("current_page_1", false, "false"));
+ gQueue.push(new makeCurrent("current_page_2", true, "page"));
+ gQueue.push(new makeCurrent("current_page_2", false, "false"));
+ gQueue.push(new makeCurrent("current_page_3", true, "true"));
+ gQueue.push(new makeCurrent("current_page_3", false, ""));
+
+ gQueue.invoke();
+ await queueFinished;
+ // Tests beyond this point use await rather than eventQueue.
+
+ await testToggleAttribute("pressable", "aria-pressed", true);
+ await testToggleAttribute("pressable_native", "aria-pressed", true);
+ await testToggleAttribute("checkable", "aria-checked", true);
+ await testToggleAttribute("checkableBool", "aria-checked", false);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=551684"
+ title="No statechange event for aria-expanded on native HTML elements, is fired on ARIA widgets">
+ Mozilla Bug 551684
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=648133"
+ title="fire state change event for aria-busy">
+ Mozilla Bug 648133
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=467143"
+ title="mixed state change event is fired for focused accessible only">
+ Mozilla Bug 467143
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=989958"
+ title="Pressed state is not exposed on a button element with aria-pressed attribute">
+ Mozilla Bug 989958
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563"
+ title="Support ARIA 1.1 switch role">
+ Mozilla Bug 1136563
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1355921"
+ title="Elements with a defined, non-false value for aria-current should expose ATK_STATE_ACTIVE">
+ Mozilla Bug 1355921
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <!-- aria-expanded -->
+ <div id="section" role="section" aria-expanded="false">expandable section</div>
+ <div id="div" aria-expanded="false">expandable native div</div>
+
+ <!-- aria-busy -->
+ <div id="aria_doc" role="document" tabindex="0">A document</div>
+
+ <!-- aria-pressed -->
+ <div id="pressable" role="button"></div>
+ <button id="pressable_native"></button>
+
+ <!-- aria-checked -->
+ <div id="checkable" role="checkbox"></div>
+ <div id="checkableBool" role="switch"></div>
+
+ <!-- aria-current -->
+ <div id="current_page_1" role="link" aria-current="page">1</div>
+ <div id="current_page_2" role="link" aria-current="false">2</div>
+ <div id="current_page_3" role="link">3</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_attrchange.html b/accessible/tests/mochitest/events/test_attrchange.html
new file mode 100644
index 0000000000..edd9195ddd
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_attrchange.html
@@ -0,0 +1,107 @@
+<html>
+
+<head>
+ <title>Accessible attr change event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ async function testGotAttrChange(elem, name, value) {
+ const waitFor = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, elem);
+ if (value) {
+ document.getElementById(elem).setAttribute(name, value);
+ } else {
+ document.getElementById(elem).removeAttribute(name);
+ }
+ await waitFor;
+ }
+
+ async function doTests() {
+ info("Removing summary attr");
+ // after summary is removed, we should have a layout table
+ await testGotAttrChange(
+ "sampleTable",
+ "summary",
+ null
+ );
+
+ info("Setting abbr attr");
+ // after abbr is set we should have a data table again
+ await testGotAttrChange(
+ "cellOne",
+ "abbr",
+ "hello world"
+ );
+
+ info("Removing abbr attr");
+ // after abbr is removed we should have a layout table again
+ await testGotAttrChange(
+ "cellOne",
+ "abbr",
+ null
+ );
+
+ info("Setting scope attr");
+ // after scope is set we should have a data table again
+ await testGotAttrChange(
+ "cellOne",
+ "scope",
+ "col"
+ );
+
+ info("Removing scope attr");
+ // remove scope should give layout
+ await testGotAttrChange(
+ "cellOne",
+ "scope",
+ null
+ );
+
+ info("Setting headers attr");
+ // add headers attr should give data
+ await testGotAttrChange(
+ "cellThree",
+ "headers",
+ "cellOne"
+ );
+
+ info("Removing headers attr");
+ // remove headers attr should give layout
+ await testGotAttrChange(
+ "cellThree",
+ "headers",
+ null
+ );
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+<body>
+ <table id="sampleTable" summary="example summary">
+ <tr>
+ <td id="cellOne">cell1</td>
+ <td>cell2</td>
+ </tr>
+ <tr>
+ <td id="cellThree">cell3</td>
+ <td>cell4</td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_attrs.html b/accessible/tests/mochitest/events/test_attrs.html
new file mode 100644
index 0000000000..c09bd9cf1e
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_attrs.html
@@ -0,0 +1,85 @@
+<html>
+
+<head>
+ <title>Event object attributes tests</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+
+ /**
+ * Test "event-from-input" object attribute.
+ */
+ function eventFromInputChecker(aEventType, aID, aValue, aNoTargetID) {
+ this.type = aEventType;
+ this.target = getAccessible(aID);
+
+ this.noTarget = getNode(aNoTargetID);
+
+ this.check = function checker_check(aEvent) {
+ testAttrs(aEvent.accessible, { "event-from-input": aValue }, true);
+
+ var accessible = getAccessible(this.noTarget);
+ testAbsentAttrs(accessible, { "event-from-input": "" });
+ };
+ }
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true; // debug stuff
+
+ function doTests() {
+ gQueue = new eventQueue();
+
+ var id = "textbox", noTargetId = "textarea";
+ let checker =
+ new eventFromInputChecker(EVENT_FOCUS, id, "false", noTargetId);
+ gQueue.push(new synthFocus(id, checker));
+
+ if (!MAC) { // Mac failure is bug 541093
+ checker =
+ new eventFromInputChecker(EVENT_TEXT_CARET_MOVED, id, "true", noTargetId);
+ gQueue.push(new synthHomeKey(id, checker));
+ }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=540285"
+ title="Event object attributes testing">
+ Mozilla Bug 540285
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="textbox" value="hello">
+ <textarea id="textarea"></textarea>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_bug1322593-2.html b/accessible/tests/mochitest/events/test_bug1322593-2.html
new file mode 100644
index 0000000000..05bd31ffa6
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_bug1322593-2.html
@@ -0,0 +1,77 @@
+<html>
+
+<head>
+ <title>Accessible mutation events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function changeMultipleElements() {
+ this.node1 = getNode("span1");
+ this.node2 = getNode("span2");
+
+ this.eventSeq = [
+ new textChangeChecker("container", 0, 5, "hello", false, undefined, true),
+ new textChangeChecker("container", 6, 11, "world", false, undefined, true),
+ new orderChecker(),
+ new textChangeChecker("container", 0, 1, "a", true, undefined, true),
+ new textChangeChecker("container", 7, 8, "b", true, undefined, true),
+ ];
+
+ this.invoke = function changeMultipleElements_invoke() {
+ this.node1.textContent = "a";
+ this.node2.textContent = "b";
+ };
+
+ this.getID = function changeMultipleElements_invoke_getID() {
+ return "Change the text content of multiple sibling divs";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+// gA11yEventDumpToConsole = true; // debugging
+
+ var gQueue = null;
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new changeMultipleElements());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322593"
+ title="missing text change events when multiple elements updated at once">
+ Mozilla Bug 1322593
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="container">
+ <span id="span1">hello</span>
+ <span>your</span>
+ <span id="span2">world</span>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_bug1322593.html b/accessible/tests/mochitest/events/test_bug1322593.html
new file mode 100644
index 0000000000..968e808106
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_bug1322593.html
@@ -0,0 +1,74 @@
+<html>
+
+<head>
+ <title>Accessible mutation events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function changeMultipleElements() {
+ this.node1 = getNode("div1");
+ this.node2 = getNode("div2");
+
+ this.eventSeq = [
+ new textChangeChecker("div1", 0, 5, "hello", false, undefined, true),
+ new textChangeChecker("div2", 0, 5, "world", false, undefined, true),
+ new orderChecker(),
+ new textChangeChecker("div1", 0, 1, "a", true, undefined, true),
+ new textChangeChecker("div2", 0, 1, "b", true, undefined, true),
+ ];
+
+ this.invoke = function changeMultipleElements_invoke() {
+ this.node1.textContent = "a";
+ this.node2.textContent = "b";
+ };
+
+ this.getID = function changeMultipleElements_invoke_getID() {
+ return "Change the text content of multiple sibling divs";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+// gA11yEventDumpToConsole = true; // debugging
+
+ var gQueue = null;
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new changeMultipleElements());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322593"
+ title="missing text change events when multiple elements updated at once">
+ Mozilla Bug 1322593
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="div1">hello</div>
+ <div id="div2">world</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_caretmove.html b/accessible/tests/mochitest/events/test_caretmove.html
new file mode 100644
index 0000000000..d1091ac7f1
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_caretmove.html
@@ -0,0 +1,142 @@
+<html>
+
+<head>
+ <title>Accessible caret move events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Click checker.
+ */
+ function clickChecker(aCaretOffset, aIsSelectionCollapsed, aID, aExtraNodeOrID, aExtraCaretOffset) {
+ this.__proto__ = new caretMoveChecker(aCaretOffset, aIsSelectionCollapsed, aID);
+
+ this.extraNode = getNode(aExtraNodeOrID);
+
+ this.check = function clickChecker_check(aEvent) {
+ this.__proto__.check(aEvent);
+
+ if (this.extraNode) {
+ var acc = getAccessible(this.extraNode, [nsIAccessibleText]);
+ is(acc.caretOffset, aExtraCaretOffset,
+ "Wrong caret offset for " + aExtraNodeOrID);
+ }
+ };
+ }
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ // gA11yEventDumpToConsole = true;
+
+ function doTests() {
+ // test caret move events and caret offsets
+ gQueue = new eventQueue();
+
+ var id = "textbox";
+ gQueue.push(new synthFocus(id, new caretMoveChecker(5, true, id)));
+ gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, false, id)));
+ gQueue.push(new synthClick(id, new caretMoveChecker(0, true, id)));
+ gQueue.push(new synthRightKey(id, new caretMoveChecker(1, true, id)));
+
+ if (!MAC) {
+ gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, false, id)));
+ gQueue.push(new synthHomeKey(id, new caretMoveChecker(0, true, id)));
+ gQueue.push(new synthRightKey(id, new caretMoveChecker(1, true, id)));
+ }
+ else {
+ todo(false, "Make these tests pass on OSX (bug 650294)");
+ }
+
+ id = "textarea";
+ gQueue.push(new synthClick(id, new caretMoveChecker(0, true, id)));
+ gQueue.push(new synthRightKey(id, new caretMoveChecker(1, true, id)));
+ gQueue.push(new synthDownKey(id, new caretMoveChecker(12, true, id)));
+
+ id = "textarea_wrapped";
+ gQueue.push(new setCaretOffset(id, 4, id));
+ gQueue.push(new synthLeftKey(id, new caretMoveChecker(4, true, id)));
+
+ id = "p";
+ gQueue.push(new synthClick(id, new caretMoveChecker(0, true, id)));
+ gQueue.push(new synthRightKey(id, new caretMoveChecker(1, true, id)));
+ gQueue.push(new synthDownKey(id, new caretMoveChecker(6, true, id)));
+
+ id = "p1_in_div";
+ gQueue.push(new synthClick(id, new clickChecker(0, true, id, "p2_in_div", -1)));
+
+ id = "p";
+ gQueue.push(new synthShiftTab(id, new caretMoveChecker(0, true, id)));
+ id = "textarea";
+ gQueue.push(new synthShiftTab(id, new caretMoveChecker(12, true, id)));
+ id = "p";
+ gQueue.push(new synthTab(id, new caretMoveChecker(0, true, id)));
+
+ // Set caret after a child of span element, i.e. after 'text' text.
+ gQueue.push(new moveCaretToDOMPoint("test1", getNode("test1_span"), 1,
+ 4, "test1"));
+ gQueue.push(new moveCaretToDOMPoint("test2", getNode("test2_span"), 1,
+ 4, "test2"));
+
+ // empty text element
+ gQueue.push(new moveCaretToDOMPoint("test3", getNode("test3"), 0,
+ 0, "test3"));
+ gQueue.push(new moveCaretToDOMPoint("test4", getNode("test4_span"), 0,
+ 0, "test4"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=454377"
+ title="Accessible caret move events testing">
+ Bug 454377
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=567571"
+ title="caret-moved events missing at the end of a wrapped line of text">
+ Bug 567571
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=824901"
+ title="HyperTextAccessible::DOMPointToHypertextOffset fails for node and offset equal to node child count">
+ Bug 824901
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="textbox" value="hello"/>
+
+ <textarea id="textarea">text<br>text</textarea>
+ <p id="p" contentEditable="true"><span>text</span><br/>text</p>
+ <div id="div" contentEditable="true"><p id="p1_in_div">text</p><p id="p2_in_div">text</p></div>
+
+ <p contentEditable="true" id="test1"><span id="test1_span">text</span>ohoho</p>
+ <p contentEditable="true" id="test2"><span><span id="test2_span">text</span></span>ohoho</p>
+ <p contentEditable="true" id="test3"></p>
+ <p contentEditable="true" id="test4"><span id="test4_span"></span></p>
+
+ <textarea id="textarea_wrapped" cols="5">hey friend</textarea>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_coalescence.html b/accessible/tests/mochitest/events/test_coalescence.html
new file mode 100644
index 0000000000..0f8ad52a8b
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_coalescence.html
@@ -0,0 +1,817 @@
+<html>
+
+<head>
+ <title>Accessible mutation events coalescence testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invoker base classes
+
+ const kRemoveElm = 1;
+ const kHideElm = 2;
+ const kAddElm = 3;
+ const kShowElm = 4;
+
+ /**
+ * Base class to test of mutation events coalescence.
+ */
+ function coalescenceBase(aChildAction, aParentAction,
+ aPerformActionOnChildInTheFirstPlace) {
+ // Invoker interface
+
+ this.invoke = function coalescenceBase_invoke() {
+ if (aPerformActionOnChildInTheFirstPlace) {
+ this.invokeAction(this.childNode, aChildAction);
+ this.invokeAction(this.parentNode, aParentAction);
+ } else {
+ this.invokeAction(this.parentNode, aParentAction);
+ this.invokeAction(this.childNode, aChildAction);
+ }
+ };
+
+ this.getID = function coalescenceBase_getID() {
+ var childAction = this.getActionName(aChildAction) + " child";
+ var parentAction = this.getActionName(aParentAction) + " parent";
+
+ if (aPerformActionOnChildInTheFirstPlace)
+ return childAction + " and then " + parentAction;
+
+ return parentAction + " and then " + childAction;
+ };
+
+ this.finalCheck = function coalescenceBase_check() {
+ if (this.getEventType(aChildAction) == EVENT_HIDE) {
+ testIsDefunct(this.child);
+ }
+ if (this.getEventType(aParentAction) == EVENT_HIDE) {
+ testIsDefunct(this.parent);
+ }
+ };
+
+ // Implementation details
+
+ this.invokeAction = function coalescenceBase_invokeAction(aNode, aAction) {
+ switch (aAction) {
+ case kRemoveElm:
+ aNode.remove();
+ break;
+
+ case kHideElm:
+ aNode.style.display = "none";
+ break;
+
+ case kAddElm:
+ if (aNode == this.parentNode)
+ this.hostNode.appendChild(this.parentNode);
+ else
+ this.parentNode.appendChild(this.childNode);
+ break;
+
+ case kShowElm:
+ aNode.style.display = "block";
+ break;
+
+ default:
+ return INVOKER_ACTION_FAILED;
+ }
+ // 0 means the action succeeded.
+ return 0;
+ };
+
+ this.getEventType = function coalescenceBase_getEventType(aAction) {
+ switch (aAction) {
+ case kRemoveElm: case kHideElm:
+ return EVENT_HIDE;
+ case kAddElm: case kShowElm:
+ return EVENT_SHOW;
+ }
+ return 0;
+ };
+
+ this.getActionName = function coalescenceBase_getActionName(aAction) {
+ switch (aAction) {
+ case kRemoveElm:
+ return "remove";
+ case kHideElm:
+ return "hide";
+ case kAddElm:
+ return "add";
+ case kShowElm:
+ return "show";
+ default:
+ return "??";
+ }
+ };
+
+ this.initSequence = function coalescenceBase_initSequence() {
+ // expected events
+ var eventType = this.getEventType(aParentAction);
+ this.eventSeq = [
+ new invokerChecker(eventType, this.parentNode),
+ new invokerChecker(EVENT_REORDER, this.hostNode),
+ ];
+
+ // unexpected events
+ this.unexpectedEventSeq = [
+ new invokerChecker(this.getEventType(aChildAction), this.childNode),
+ new invokerChecker(EVENT_REORDER, this.parentNode),
+ ];
+ };
+ }
+
+ /**
+ * Remove or hide mutation events coalescence testing.
+ */
+ function removeOrHideCoalescenceBase(aChildID, aParentID,
+ aChildAction, aParentAction,
+ aPerformActionOnChildInTheFirstPlace) {
+ this.__proto__ = new coalescenceBase(aChildAction, aParentAction,
+ aPerformActionOnChildInTheFirstPlace);
+
+ this.init = function removeOrHideCoalescenceBase_init() {
+ this.childNode = getNode(aChildID);
+ this.parentNode = getNode(aParentID);
+ this.child = getAccessible(this.childNode);
+ this.parent = getAccessible(this.parentNode);
+ this.hostNode = this.parentNode.parentNode;
+ };
+
+ // Initalization
+
+ this.init();
+ this.initSequence();
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ /**
+ * Remove child node and then its parent node from DOM tree.
+ */
+ function removeChildNParent(aChildID, aParentID) {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kRemoveElm, kRemoveElm,
+ true);
+ }
+
+ /**
+ * Remove parent node and then its child node from DOM tree.
+ */
+ function removeParentNChild(aChildID, aParentID) {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kRemoveElm, kRemoveElm,
+ false);
+ }
+
+ /**
+ * Hide child node and then its parent node.
+ */
+ function hideChildNParent(aChildID, aParentID) {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kHideElm, kHideElm,
+ true);
+ }
+
+ /**
+ * Hide parent node and then its child node.
+ */
+ function hideParentNChild(aChildID, aParentID) {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kHideElm, kHideElm,
+ false);
+ }
+
+ /**
+ * Hide child node and then remove its parent node.
+ */
+ function hideChildNRemoveParent(aChildID, aParentID) {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kHideElm, kRemoveElm,
+ true);
+ }
+
+ /**
+ * Hide parent node and then remove its child node.
+ */
+ function hideParentNRemoveChild(aChildID, aParentID) {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kRemoveElm, kHideElm,
+ false);
+ }
+
+ /**
+ * Remove child node and then hide its parent node.
+ */
+ function removeChildNHideParent(aChildID, aParentID) {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kRemoveElm, kHideElm,
+ true);
+ }
+
+ /**
+ * Remove parent node and then hide its child node.
+ */
+ function removeParentNHideChild(aChildID, aParentID) {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kHideElm, kRemoveElm,
+ false);
+ }
+
+ /**
+ * Create and append parent node and create and append child node to it.
+ */
+ function addParentNChild(aHostID, aPerformActionOnChildInTheFirstPlace) {
+ this.init = function addParentNChild_init() {
+ this.hostNode = getNode(aHostID);
+ this.parentNode = document.createElement("select");
+ this.childNode = document.createElement("option");
+ this.childNode.textContent = "testing";
+ };
+
+ this.__proto__ = new coalescenceBase(kAddElm, kAddElm,
+ aPerformActionOnChildInTheFirstPlace);
+
+ this.init();
+ this.initSequence();
+ }
+
+ /**
+ * Show parent node and show child node to it.
+ */
+ function showParentNChild(aParentID, aChildID,
+ aPerformActionOnChildInTheFirstPlace) {
+ this.init = function showParentNChild_init() {
+ this.parentNode = getNode(aParentID);
+ this.hostNode = this.parentNode.parentNode;
+ this.childNode = getNode(aChildID);
+ };
+
+ this.__proto__ = new coalescenceBase(kShowElm, kShowElm,
+ aPerformActionOnChildInTheFirstPlace);
+
+ this.init();
+ this.initSequence();
+ }
+
+ /**
+ * Create and append child node to the DOM and then show parent node.
+ */
+ function showParentNAddChild(aParentID,
+ aPerformActionOnChildInTheFirstPlace) {
+ this.init = function showParentNAddChild_init() {
+ this.parentNode = getNode(aParentID);
+ this.hostNode = this.parentNode.parentNode;
+ this.childNode = document.createElement("option");
+ this.childNode.textContent = "testing";
+ };
+
+ this.__proto__ = new coalescenceBase(kAddElm, kShowElm,
+ aPerformActionOnChildInTheFirstPlace);
+
+ this.init();
+ this.initSequence();
+ }
+
+ /**
+ * Remove children and parent
+ */
+ function removeGrandChildrenNHideParent(aChild1Id, aChild2Id, aParentId) {
+ this.child1 = getNode(aChild1Id);
+ this.child2 = getNode(aChild2Id);
+ this.parent = getNode(aParentId);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getAccessible(aParentId)),
+ new invokerChecker(EVENT_REORDER, getNode(aParentId).parentNode),
+ new unexpectedInvokerChecker(EVENT_HIDE, getAccessible(aChild1Id)),
+ new unexpectedInvokerChecker(EVENT_HIDE, getAccessible(aChild2Id)),
+ new unexpectedInvokerChecker(EVENT_REORDER, getAccessible(aParentId)),
+ ];
+
+ this.invoke = function removeGrandChildrenNHideParent_invoke() {
+ this.child1.remove();
+ this.child2.remove();
+ this.parent.hidden = true;
+ };
+
+ this.getID = function removeGrandChildrenNHideParent_getID() {
+ return "remove grand children of different parents and then hide their grand parent";
+ };
+ }
+
+ /**
+ * Remove a child, and then its parent.
+ */
+ function test3() {
+ this.o = getAccessible("t3_o");
+ this.ofc = getAccessible("t3_o").firstChild;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.o),
+ new invokerChecker(EVENT_REORDER, "t3_lb"),
+ new unexpectedInvokerChecker(EVENT_HIDE, this.ofc),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.o),
+ ];
+
+ this.invoke = function test3_invoke() {
+ getNode("t3_o").textContent = "";
+ getNode("t3_lb").removeChild(getNode("t3_o"));
+ };
+
+ this.finalCheck = function test3_finalCheck() {
+ testIsDefunct(this.o);
+ testIsDefunct(this.ofc);
+ };
+
+ this.getID = function test3_getID() {
+ return "remove a child, and then its parent";
+ };
+ }
+
+ /**
+ * Remove children, and then a parent of 2nd child.
+ */
+ function test4() {
+ this.o1 = getAccessible("t4_o1");
+ this.o1fc = this.o1.firstChild;
+ this.o2 = getAccessible("t4_o2");
+ this.o2fc = this.o2.firstChild;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.o1fc),
+ new invokerChecker(EVENT_HIDE, this.o2),
+ new invokerChecker(EVENT_REORDER, "t4_lb"),
+ new unexpectedInvokerChecker(EVENT_HIDE, this.o2fc),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.o1),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.o2),
+ ];
+
+ this.invoke = function test4_invoke() {
+ getNode("t4_o1").textContent = "";
+ getNode("t4_o2").textContent = "";
+ getNode("t4_lb").removeChild(getNode("t4_o2"));
+ };
+
+ this.finalCheck = function test4_finalCheck() {
+ testIsDefunct(this.o1fc);
+ testIsDefunct(this.o2);
+ testIsDefunct(this.o2fc);
+ };
+
+ this.getID = function test4_getID() {
+ return "remove children, and then a parent of 2nd child";
+ };
+ }
+
+ /**
+ * Remove a child, remove a parent sibling, remove the parent
+ */
+ function test5() {
+ this.o = getAccessible("t5_o");
+ this.ofc = this.o.firstChild;
+ this.b = getAccessible("t5_b");
+ this.lb = getAccessible("t5_lb");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.b),
+ new invokerChecker(EVENT_HIDE, this.o),
+ new invokerChecker(EVENT_REORDER, "t5"),
+ new unexpectedInvokerChecker(EVENT_HIDE, this.ofc),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.o),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.lb),
+ ];
+
+ this.invoke = function test5_invoke() {
+ getNode("t5_o").textContent = "";
+ getNode("t5").removeChild(getNode("t5_b"));
+ getNode("t5_lb").removeChild(getNode("t5_o"));
+ };
+
+ this.finalCheck = function test5_finalCheck() {
+ testIsDefunct(this.ofc);
+ testIsDefunct(this.o);
+ testIsDefunct(this.b);
+ };
+
+ this.getID = function test5_getID() {
+ return "remove a child, remove a parent sibling, remove the parent";
+ };
+ }
+
+ /**
+ * Insert accessibles with a child node moved by aria-owns
+ * Markup:
+ * <div id="t6_fc">
+ * <div id="t6_owns"></div>
+ * </div>
+ * <div id="t6_sc" aria-owns="t6_owns"></div>
+ */
+ function test6() {
+ this.parent = getNode("t6");
+ this.fc = document.createElement("div");
+ this.fc.setAttribute("id", "t6_fc");
+ this.owns = document.createElement("div");
+ this.owns.setAttribute("id", "t6_owns");
+ this.sc = document.createElement("div");
+ this.sc.setAttribute("id", "t6_sc");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.fc),
+ new invokerChecker(EVENT_SHOW, this.sc),
+ new invokerChecker(EVENT_REORDER, this.parent),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.fc),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.sc),
+ new unexpectedInvokerChecker(EVENT_HIDE, this.owns),
+ new unexpectedInvokerChecker(EVENT_SHOW, this.owns),
+ ];
+
+ this.invoke = function test6_invoke() {
+ getNode("t6").appendChild(this.fc);
+ getNode("t6_fc").appendChild(this.owns);
+ getNode("t6").appendChild(this.sc);
+ getNode("t6_sc").setAttribute("aria-owns", "t6_owns");
+ };
+
+ this.getID = function test6_getID() {
+ return "Insert accessibles with a child node moved by aria-owns";
+ };
+ }
+
+ /**
+ * Insert text nodes under direct and grand children, and then hide
+ * their container by means of aria-owns.
+ *
+ * Markup:
+ * <div id="t7_moveplace" aria-owns="t7_c"></div>
+ * <div id="t7_c">
+ * <div id="t7_c_directchild">ha</div>
+ * <div><div id="t7_c_grandchild">ha</div></div>
+ * </div>
+ */
+ function test7() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t7_c")),
+ new invokerChecker(EVENT_SHOW, getNode("t7_c")),
+ new invokerChecker(EVENT_REORDER, getNode("t7")),
+ new unexpectedInvokerChecker(EVENT_REORDER, getNode("t7_c_directchild")),
+ new unexpectedInvokerChecker(EVENT_REORDER, getNode("t7_c_grandchild")),
+ new unexpectedInvokerChecker(EVENT_SHOW, () => getNode("t7_c_directchild").firstChild),
+ new unexpectedInvokerChecker(EVENT_SHOW, () => getNode("t7_c_grandchild").firstChild),
+ ];
+
+ this.invoke = function test7_invoke() {
+ getNode("t7_c_directchild").textContent = "ha";
+ getNode("t7_c_grandchild").textContent = "ha";
+ getNode("t7_moveplace").setAttribute("aria-owns", "t7_c");
+ };
+
+ this.getID = function test7_getID() {
+ return "Show child accessibles and then hide their container";
+ };
+ }
+
+ /**
+ * Move a node by aria-owns from right to left in the tree, so that
+ * the eventing looks this way:
+ * reorder for 't8_c1'
+ * hide for 't8_c1_child'
+ * show for 't8_c2_moved'
+ * reorder for 't8_c2'
+ * hide for 't8_c2_moved'
+ *
+ * The hide event should be delivered before the paired show event.
+ */
+ function test8() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t8_c1_child")),
+ new invokerChecker(EVENT_HIDE, "t8_c2_moved"),
+ new invokerChecker(EVENT_SHOW, "t8_c2_moved"),
+ new invokerChecker(EVENT_REORDER, "t8_c2"),
+ new invokerChecker(EVENT_REORDER, "t8_c1"),
+ ];
+
+ this.invoke = function test8_invoke() {
+ // Remove a node from 't8_c1' container to give the event tree a
+ // desired structure (the 't8_c1' container node goes first in the event
+ // tree)
+ getNode("t8_c1_child").remove();
+ // then move 't8_c2_moved' from 't8_c2' to 't8_c1'.
+ getNode("t8_c1").setAttribute("aria-owns", "t8_c2_moved");
+ };
+
+ this.getID = function test8_getID() {
+ return "Move a node by aria-owns to left within the tree";
+ };
+ }
+
+ /**
+ * Move 't9_c3_moved' node under 't9_c2_moved', and then move 't9_c2_moved'
+ * node by aria-owns (same as test10 but has different aria-owns
+ * ordering), the eventing looks same way as in test10:
+ * reorder for 't9_c1'
+ * hide for 't9_c1_child'
+ * show for 't9_c2_moved'
+ * reorder for 't9_c2'
+ * hide for 't9_c2_child'
+ * hide for 't9_c2_moved'
+ * reorder for 't9_c3'
+ * hide for 't9_c3_moved'
+ *
+ * The hide events for 't9_c2_moved' and 't9_c3_moved' should be delivered
+ * before the show event for 't9_c2_moved'.
+ */
+ function test9() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t9_c1_child")),
+ new invokerChecker(EVENT_HIDE, getNode("t9_c2_child")),
+ new invokerChecker(EVENT_HIDE, "t9_c3_moved"),
+ new invokerChecker(EVENT_HIDE, "t9_c2_moved"),
+ new invokerChecker(EVENT_SHOW, "t9_c2_moved"),
+ new invokerChecker(EVENT_REORDER, "t9_c3"),
+ new invokerChecker(EVENT_REORDER, "t9_c2"),
+ new invokerChecker(EVENT_REORDER, "t9_c1"),
+ new unexpectedInvokerChecker(EVENT_SHOW, "t9_c3_moved"),
+ ];
+
+ this.invoke = function test9_invoke() {
+ // Remove child nodes from 't9_c1' and 't9_c2' containers to give
+ // the event tree a needed structure ('t9_c1' and 't9_c2' nodes go
+ // first in the event tree),
+ getNode("t9_c1_child").remove();
+ getNode("t9_c2_child").remove();
+ // then do aria-owns magic.
+ getNode("t9_c2_moved").setAttribute("aria-owns", "t9_c3_moved");
+ getNode("t9_c1").setAttribute("aria-owns", "t9_c2_moved");
+ };
+
+ this.getID = function test9_getID() {
+ return "Move node #1 by aria-owns and then move node #2 into node #1";
+ };
+ }
+
+ /**
+ * Move a node 't10_c3_moved' by aria-owns under a node 't10_c2_moved',
+ * moved by under 't10_1', so that the eventing looks this way:
+ * reorder for 't10_c1'
+ * hide for 't10_c1_child'
+ * show for 't10_c2_moved'
+ * reorder for 't10_c2'
+ * hide for 't10_c2_child'
+ * hide for 't10_c2_moved'
+ * reorder for 't10_c3'
+ * hide for 't10_c3_moved'
+ *
+ * The hide events for 't10_c2_moved' and 't10_c3_moved' should be delivered
+ * before the show event for 't10_c2_moved'.
+ */
+ function test10() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t10_c1_child")),
+ new invokerChecker(EVENT_HIDE, getNode("t10_c2_child")),
+ new invokerChecker(EVENT_HIDE, getNode("t10_c2_moved")),
+ new invokerChecker(EVENT_HIDE, getNode("t10_c3_moved")),
+ new invokerChecker(EVENT_SHOW, getNode("t10_c2_moved")),
+ new invokerChecker(EVENT_REORDER, "t10_c2"),
+ new invokerChecker(EVENT_REORDER, "t10_c1"),
+ new invokerChecker(EVENT_REORDER, "t10_c3"),
+ ];
+
+ this.invoke = function test10_invoke() {
+ // Remove child nodes from 't10_c1' and 't10_c2' containers to give
+ // the event tree a needed structure ('t10_c1' and 't10_c2' nodes go first
+ // in the event tree),
+ getNode("t10_c1_child").remove();
+ getNode("t10_c2_child").remove();
+ // then do aria-owns stuff.
+ getNode("t10_c1").setAttribute("aria-owns", "t10_c2_moved");
+ getNode("t10_c2_moved").setAttribute("aria-owns", "t10_c3_moved");
+ };
+
+ this.getID = function test10_getID() {
+ return "Move a node by aria-owns into a node moved by aria-owns to left within the tree";
+ };
+ }
+
+ /**
+ * Move a node by aria-owns from right to left in the tree, and then
+ * move its parent too by aria-owns. No hide event should be fired for
+ * original node.
+ */
+ function test11() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t11_c1_child")),
+ new invokerChecker(EVENT_HIDE, getNode("t11_c2")),
+ new orderChecker(),
+ new asyncInvokerChecker(EVENT_SHOW, "t11_c2_child"),
+ new asyncInvokerChecker(EVENT_SHOW, "t11_c2"),
+ new orderChecker(),
+ new invokerChecker(EVENT_REORDER, "t11"),
+ new unexpectedInvokerChecker(EVENT_HIDE, "t11_c2_child"),
+ new unexpectedInvokerChecker(EVENT_REORDER, "t11_c1"),
+ new unexpectedInvokerChecker(EVENT_REORDER, "t11_c2"),
+ new unexpectedInvokerChecker(EVENT_REORDER, "t11_c3"),
+ ];
+
+ this.invoke = function test11_invoke() {
+ // Remove a node from 't11_c1' container to give the event tree a
+ // desired structure (the 't11_c1' container node goes first in
+ // the event tree),
+ getNode("t11_c1_child").remove();
+ // then move 't11_c2_moved' from 't11_c2' to 't11_c1', and then move
+ // 't11_c2' to 't11_c3'.
+ getNode("t11_c1").setAttribute("aria-owns", "t11_c2_child");
+ getNode("t11_c3").setAttribute("aria-owns", "t11_c2");
+ };
+
+ this.getID = function test11_getID() {
+ return "Move a node by aria-owns to left within the tree";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests.
+
+ gA11yEventDumpToConsole = true; // debug stuff
+ // enableLogging("eventTree");
+
+ var gQueue = null;
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new removeChildNParent("option1", "select1"));
+ gQueue.push(new removeParentNChild("option2", "select2"));
+ gQueue.push(new hideChildNParent("option3", "select3"));
+ gQueue.push(new hideParentNChild("option4", "select4"));
+ gQueue.push(new hideChildNRemoveParent("option5", "select5"));
+ gQueue.push(new hideParentNRemoveChild("option6", "select6"));
+ gQueue.push(new removeChildNHideParent("option7", "select7"));
+ gQueue.push(new removeParentNHideChild("option8", "select8"));
+
+ gQueue.push(new addParentNChild("testContainer", false));
+ gQueue.push(new addParentNChild("testContainer", true));
+ gQueue.push(new showParentNChild("select9", "option9", false));
+ gQueue.push(new showParentNChild("select10", "option10", true));
+ gQueue.push(new showParentNAddChild("select11", false));
+ gQueue.push(new showParentNAddChild("select12", true));
+
+ gQueue.push(new removeGrandChildrenNHideParent("t1_child1", "t1_child2", "t1_parent"));
+ gQueue.push(new test3());
+ gQueue.push(new test4());
+ gQueue.push(new test5());
+ gQueue.push(new test6());
+ gQueue.push(new test7());
+ gQueue.push(new test8());
+ gQueue.push(new test9());
+ gQueue.push(new test10());
+ gQueue.push(new test11());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=513213"
+ title="coalesce events when new event is appended to the queue">
+ Mozilla Bug 513213
+ </a><br>
+ <a target="_blank"
+ title="Rework accessible tree update code"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
+ Mozilla Bug 570275
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="testContainer">
+ <select id="select1">
+ <option id="option1">option</option>
+ </select>
+ <select id="select2">
+ <option id="option2">option</option>
+ </select>
+ <select id="select3">
+ <option id="option3">option</option>
+ </select>
+ <select id="select4">
+ <option id="option4">option</option>
+ </select>
+ <select id="select5">
+ <option id="option5">option</option>
+ </select>
+ <select id="select6">
+ <option id="option6">option</option>
+ </select>
+ <select id="select7">
+ <option id="option7">option</option>
+ </select>
+ <select id="select8">
+ <option id="option8">option</option>
+ </select>
+
+ <select id="select9" style="display: none">
+ <option id="option9" style="display: none">testing</option>
+ </select>
+ <select id="select10" style="display: none">
+ <option id="option10" style="display: none">testing</option>
+ </select>
+ <select id="select11" style="display: none"></select>
+ <select id="select12" style="display: none"></select>
+ </div>
+
+ <div id="testContainer2">
+ <div id="t1_parent">
+ <div id="t1_mid1"><div id="t1_child1"></div></div>
+ <div id="t1_mid2"><div id="t1_child2"></div></div>
+ </div>
+ </div>
+
+ <div id="t3">
+ <div role="listbox" id="t3_lb">
+ <div role="option" id="t3_o">opt</div>
+ </div>
+ </div>
+
+ <div id="t4">
+ <div role="listbox" id="t4_lb">
+ <div role="option" id="t4_o1">opt1</div>
+ <div role="option" id="t4_o2">opt2</div>
+ </div>
+ </div>
+
+ <div id="t5">
+ <div role="button" id="t5_b">btn</div>
+ <div role="listbox" id="t5_lb">
+ <div role="option" id="t5_o">opt</div>
+ </div>
+ </div>
+
+ <div id="t6">
+ </div>
+
+ <div id="t7">
+ <div id="t7_moveplace"></div>
+ <div id="t7_c">
+ <div><div id="t7_c_grandchild"></div></div>
+ <div id="t7_c_directchild"></div>
+ </div>
+ </div>
+
+ <div id="t8">
+ <div id="t8_c1"><div id="t8_c1_child"></div></div>
+ <div id="t8_c2">
+ <div id="t8_c2_moved"></div>
+ </div>
+ </div>
+
+ <div id="t9">
+ <div id="t9_c1"><div id="t9_c1_child"></div></div>
+ <div id="t9_c2">
+ <div id="t9_c2_child"></div>
+ <div id="t9_c2_moved"></div>
+ </div>
+ <div id="t9_c3">
+ <div id="t9_c3_moved"></div>
+ </div>
+ </div>
+
+ <div id="t10">
+ <div id="t10_c1"><div id="t10_c1_child"></div></div>
+ <div id="t10_c2">
+ <div id="t10_c2_child"></div>
+ <div id="t10_c2_moved"></div>
+ </div>
+ <div id="t10_c3">
+ <div id="t10_c3_moved"></div>
+ </div>
+ </div>
+
+ <div id="t11">
+ <div id="t11_c1"><div id="t11_c1_child"></div></div>
+ <div id="t11_c2"><div id="t11_c2_child"></div></div>
+ <div id="t11_c3"></div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_contextmenu.html b/accessible/tests/mochitest/events/test_contextmenu.html
new file mode 100644
index 0000000000..e729f071e7
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_contextmenu.html
@@ -0,0 +1,131 @@
+<html>
+
+<head>
+ <title>Context menu tests</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function showContextMenu(aID) {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENUPOPUP_START, getContextMenuNode()),
+ ];
+
+ this.invoke = function showContextMenu_invoke() {
+ synthesizeMouse(this.DOMNode, 4, 4, { type: "contextmenu", button: 2 });
+ };
+
+ this.getID = function showContextMenu_getID() {
+ return "show context menu";
+ };
+ }
+
+ function selectMenuItem() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getFocusedMenuItem),
+ ];
+
+ this.invoke = function selectMenuItem_invoke() {
+ synthesizeKey("KEY_ArrowDown");
+ };
+
+ this.getID = function selectMenuItem_getID() {
+ return "select first menuitem";
+ };
+ }
+
+ function closeContextMenu(aID) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENUPOPUP_END,
+ getAccessible(getContextMenuNode())),
+ ];
+
+ this.invoke = function closeContextMenu_invoke() {
+ synthesizeKey("KEY_Escape");
+ };
+
+ this.getID = function closeContextMenu_getID() {
+ return "close context menu";
+ };
+ }
+
+ function getContextMenuNode() {
+ return getRootAccessible().DOMDocument.
+ getElementById("contentAreaContextMenu");
+ }
+
+ function getFocusedMenuItem() {
+ var menu = getAccessible(getAccessible(getContextMenuNode()));
+ for (var idx = 0; idx < menu.childCount; idx++) {
+ var item = menu.getChildAt(idx);
+
+ if (hasState(item, STATE_FOCUSED))
+ return getAccessible(item);
+ }
+ return null;
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ var gQueue = null;
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new showContextMenu("input"));
+ gQueue.push(new selectMenuItem());
+ gQueue.push(new closeContextMenu());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ if (AppConstants.platform == "macosx" &&
+ Services.prefs.getBoolPref("widget.macos.native-context-menus", false)) {
+ ok(true, "Native context menus handle accessibility notifications natively and cannot be tested with synthesized key events.");
+ } else {
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ }
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=580535"
+ title="Broken accessibility in context menus">
+ Mozilla Bug 580535
+ </a><br>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input">
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_descrchange.html b/accessible/tests/mochitest/events/test_descrchange.html
new file mode 100644
index 0000000000..1eaecd6b59
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_descrchange.html
@@ -0,0 +1,142 @@
+<html>
+
+<head>
+ <title>Accessible description change event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ let PromEvents = {};
+ Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js",
+ PromEvents);
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function setAttr(aID, aAttr, aValue, aChecker) {
+ this.eventSeq = [ aChecker ];
+ this.invoke = function setAttr_invoke() {
+ getNode(aID).setAttribute(aAttr, aValue);
+ };
+
+ this.getID = function setAttr_getID() {
+ return "set attr '" + aAttr + "', value '" + aValue + "'";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ // gA11yEventDumpToConsole = true; // debuggin
+
+ var gQueue = null;
+ async function doTests() {
+ gQueue = new eventQueue();
+ // Later tests use await.
+ let queueFinished = new Promise(resolve => {
+ gQueue.onFinish = function() {
+ resolve();
+ return DO_NOT_FINISH_TEST;
+ };
+ });
+
+ gQueue.push(new setAttr("tst1", "aria-describedby", "display",
+ new invokerChecker(EVENT_DESCRIPTION_CHANGE, "tst1")));
+ gQueue.push(new setAttr("tst1", "title", "title",
+ new unexpectedInvokerChecker(EVENT_DESCRIPTION_CHANGE, "tst1")));
+
+ // A title has lower priority over text content. There should be no name change event.
+ gQueue.push(new setAttr("tst2", "title", "title",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
+
+ gQueue.invoke();
+ await queueFinished;
+ // Tests beyond this point use await rather than eventQueue.
+
+ const describedBy = getNode("describedBy");
+ const description = getNode("description");
+ let descChanged = PromEvents.waitForEvent(
+ EVENT_DESCRIPTION_CHANGE,
+ describedBy
+ );
+ info("Changing text of aria-describedby target");
+ description.textContent = "d2";
+ await descChanged;
+ descChanged = PromEvents.waitForEvent(
+ EVENT_DESCRIPTION_CHANGE,
+ describedBy
+ );
+ info("Adding node to aria-describedby target");
+ description.innerHTML = '<p id="descriptionChild">d3</p>';
+ await descChanged;
+ descChanged = PromEvents.waitForEvent(
+ EVENT_DESCRIPTION_CHANGE,
+ describedBy
+ );
+ info("Changing text of aria-describedby target's child");
+ getNode("descriptionChild").textContent = "d4";
+ await descChanged;
+
+ const lateDescribedBy = getNode("lateDescribedBy");
+ descChanged = PromEvents.waitForEvent(
+ EVENT_DESCRIPTION_CHANGE,
+ lateDescribedBy
+ );
+ info("Setting aria-describedby");
+ lateDescribedBy.setAttribute("aria-describedby", "lateDescription");
+ await descChanged;
+ descChanged = PromEvents.waitForEvent(
+ EVENT_DESCRIPTION_CHANGE,
+ lateDescribedBy
+ );
+ info("Changing text of late aria-describedby target's child");
+ getNode("lateDescriptionChild").textContent = "d2";
+ await descChanged;
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=991969"
+ title="Event not fired when description changes">
+ Bug 991969
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <button id="tst1">btn1</button>
+ <button id="tst2">btn2</button>
+
+ <div id="describedBy" aria-describedby="description"></div>
+ <div id="description">d1</div>
+
+ <div id="lateDescribedBy"></div>
+ <div id="lateDescription"><p id="lateDescriptionChild">d1</p></div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_dragndrop.html b/accessible/tests/mochitest/events/test_dragndrop.html
new file mode 100644
index 0000000000..2613a310a2
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_dragndrop.html
@@ -0,0 +1,106 @@
+<html>
+
+<head>
+ <title>Accessible drag and drop event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ // aria grabbed invoker
+ function changeGrabbed(aNodeOrID, aGrabValue) {
+ this.DOMNode = getNode(aNodeOrID);
+
+ this.invoke = function changeGrabbed_invoke() {
+ if (aGrabValue != undefined) {
+ this.DOMNode.setAttribute("aria-grabbed", aGrabValue);
+ }
+ };
+
+ this.check = function changeGrabbed_check() {
+ testAttrs(aNodeOrID, {"grabbed": aGrabValue}, true);
+ };
+
+ this.getID = function changeGrabbed_getID() {
+ return prettyName(aNodeOrID) + " aria-grabbed changed";
+ };
+ }
+
+ // aria dropeffect invoker
+ function changeDropeffect(aNodeOrID, aDropeffectValue) {
+ this.DOMNode = getNode(aNodeOrID);
+
+ this.invoke = function changeDropeffect_invoke() {
+ if (aDropeffectValue != undefined) {
+ this.DOMNode.setAttribute("aria-dropeffect", aDropeffectValue);
+ }
+ };
+
+ this.check = function changeDropeffect_check() {
+ testAttrs(aNodeOrID, {"dropeffect": aDropeffectValue}, true);
+ };
+
+ this.getID = function changeDropeffect_getID() {
+ return prettyName(aNodeOrID) + " aria-dropeffect changed";
+ };
+ }
+
+ function doTests() {
+ // Test aria attribute mutation events
+ gQueue = new eventQueue(nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED);
+
+ let id = "grabbable";
+ gQueue.push(new changeGrabbed(id, "true"));
+ gQueue.push(new changeGrabbed(id, "false"));
+ todo(false, "uncomment this test when 472142 is fixed.");
+ // gQueue.push(new changeGrabbed(id, "undefined"));
+
+ id = "dropregion";
+ gQueue.push(new changeDropeffect(id, "copy"));
+ gQueue.push(new changeDropeffect(id, "execute"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=510441"
+ title="Add support for nsIAccessibleEvent::OBJECT_ATTRIBUTE_CHANGED">
+ Mozilla Bug 510441
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <!-- ARIA grabbed -->
+ <div id="grabbable" role="button" aria-grabbed="foo">button</div>
+
+ <!-- ARIA dropeffect -->
+ <div id="dropregion" role="region" aria-dropeffect="none">button</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_flush.html b/accessible/tests/mochitest/events/test_flush.html
new file mode 100644
index 0000000000..7d7b60b81e
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_flush.html
@@ -0,0 +1,74 @@
+<html>
+
+<head>
+ <title>Flush delayed events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ SimpleTest.expectAssertions(0, 1);
+
+ var gFocusHandler = {
+ handleEvent(aEvent) {
+ switch (this.count) {
+ case 0:
+ is(aEvent.DOMNode, getNode("input1"),
+ "Focus event for input1 was expected!");
+ getAccessible("input2").takeFocus();
+ break;
+
+ case 1:
+ is(aEvent.DOMNode, getNode("input2"),
+ "Focus event for input2 was expected!");
+
+ unregisterA11yEventListener(EVENT_FOCUS, gFocusHandler);
+ SimpleTest.finish();
+ break;
+
+ default:
+ ok(false, "Wrong focus event!");
+ }
+
+ this.count++;
+ },
+
+ count: 0,
+ };
+
+ function doTests() {
+ registerA11yEventListener(EVENT_FOCUS, gFocusHandler);
+
+ getAccessible("input1").takeFocus();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=477551"
+ title="DocAccessible::FlushPendingEvents isn't robust">
+ Mozilla Bug 477551
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input1">
+ <input id="input2">
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html
new file mode 100644
index 0000000000..661284619a
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html
@@ -0,0 +1,327 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=429547
+-->
+<head>
+ <title>aria-activedescendant focus tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ let PromEvents = {};
+ Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js",
+ PromEvents);
+ // gA11yEventDumpToConsole = true; // debugging
+
+ function changeARIAActiveDescendant(aContainer, aItem, aPrevItemId) {
+ let itemID = Node.isInstance(aItem) ? aItem.id : aItem;
+ this.eventSeq = [new focusChecker(aItem)];
+
+ if (aPrevItemId) {
+ this.eventSeq.push(
+ new stateChangeChecker(EXT_STATE_ACTIVE, true, false, aPrevItemId)
+ );
+ }
+
+ this.eventSeq.push(
+ new stateChangeChecker(EXT_STATE_ACTIVE, true, true, aItem)
+ );
+
+ this.invoke = function changeARIAActiveDescendant_invoke() {
+ getNode(aContainer).setAttribute("aria-activedescendant", itemID);
+ };
+
+ this.getID = function changeARIAActiveDescendant_getID() {
+ return "change aria-activedescendant on " + itemID;
+ };
+ }
+
+ function clearARIAActiveDescendant(aID, aPrevItemId) {
+ this.eventSeq = [
+ new focusChecker(aID),
+ ];
+
+ if (aPrevItemId) {
+ this.eventSeq.push(
+ new stateChangeChecker(EXT_STATE_ACTIVE, true, false, aPrevItemId)
+ );
+ }
+
+ this.invoke = function clearARIAActiveDescendant_invoke() {
+ getNode(aID).removeAttribute("aria-activedescendant");
+ };
+
+ this.getID = function clearARIAActiveDescendant_getID() {
+ return "clear aria-activedescendant on container " + aID;
+ };
+ }
+
+ /**
+ * Change aria-activedescendant to an invalid (non-existent) id.
+ * Ensure that focus is fired on the element itself.
+ */
+ function changeARIAActiveDescendantInvalid(aID, aInvalidID, aPrevItemId) {
+ if (!aInvalidID) {
+ aInvalidID = "invalid";
+ }
+
+ this.eventSeq = [
+ new focusChecker(aID),
+ ];
+
+ if (aPrevItemId) {
+ this.eventSeq.push(
+ new stateChangeChecker(EXT_STATE_ACTIVE, true, false, aPrevItemId)
+ );
+ }
+
+ this.invoke = function changeARIAActiveDescendant_invoke() {
+ getNode(aID).setAttribute("aria-activedescendant", aInvalidID);
+ };
+
+ this.getID = function changeARIAActiveDescendant_getID() {
+ return "change aria-activedescendant to invalid id";
+ };
+ }
+
+ function insertItemNFocus(aID, aNewItemID, aPrevItemId) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, aNewItemID),
+ new focusChecker(aNewItemID),
+ ];
+
+ if (aPrevItemId) {
+ this.eventSeq.push(
+ new stateChangeChecker(EXT_STATE_ACTIVE, true, false, aPrevItemId)
+ );
+ }
+
+ this.eventSeq.push(
+ new stateChangeChecker(EXT_STATE_ACTIVE, true, true, aNewItemID)
+ );
+
+ this.invoke = function insertItemNFocus_invoke() {
+ var container = getNode(aID);
+
+ var itemNode = document.createElement("div");
+ itemNode.setAttribute("id", aNewItemID);
+ itemNode.setAttribute("role", "listitem");
+ itemNode.textContent = aNewItemID;
+ container.appendChild(itemNode);
+
+ container.setAttribute("aria-activedescendant", aNewItemID);
+ };
+
+ this.getID = function insertItemNFocus_getID() {
+ return "insert new node and focus it with ID: " + aNewItemID;
+ };
+ }
+
+ /**
+ * Change the id of an element to another id which is the target of
+ * aria-activedescendant.
+ * If another element already has the desired id, remove it from that
+ * element first.
+ * Ensure that focus is fired on the target element which was given the
+ * desired id.
+ * @param aFromID The existing id of the target element.
+ * @param aToID The desired id to be given to the target element.
+ */
+ function moveARIAActiveDescendantID(aFromID, aToID) {
+ this.eventSeq = [
+ new focusChecker(aToID),
+ new stateChangeChecker(EXT_STATE_ACTIVE, true, true, aToID),
+ ];
+
+ this.invoke = function moveARIAActiveDescendantID_invoke() {
+ let orig = document.getElementById(aToID);
+ if (orig) {
+ orig.id = "";
+ }
+ document.getElementById(aFromID).id = aToID;
+ };
+
+ this.getID = function moveARIAActiveDescendantID_getID() {
+ return "move aria-activedescendant id " + aToID;
+ };
+ }
+
+ var gQueue = null;
+ async function doTest() {
+ gQueue = new eventQueue();
+ // Later tests use await.
+ let queueFinished = new Promise(resolve => {
+ gQueue.onFinish = function() {
+ resolve();
+ return DO_NOT_FINISH_TEST;
+ };
+ });
+
+ gQueue.push(new synthFocus("listbox", new focusChecker("item1")));
+ gQueue.push(new changeARIAActiveDescendant("listbox", "item2", "item1"));
+ gQueue.push(new changeARIAActiveDescendant("listbox", "item3", "item2"));
+
+ gQueue.push(new synthFocus("combobox_entry", new focusChecker("combobox_entry")));
+ gQueue.push(new changeARIAActiveDescendant("combobox", "combobox_option2"));
+
+ gQueue.push(new synthFocus("listbox", new focusChecker("item3")));
+ gQueue.push(new insertItemNFocus("listbox", "item4", "item3"));
+
+ gQueue.push(new clearARIAActiveDescendant("listbox", "item4"));
+ gQueue.push(new changeARIAActiveDescendant("listbox", "item1"));
+ gQueue.push(new changeARIAActiveDescendantInvalid("listbox", "invalid", "item1"));
+
+ gQueue.push(new changeARIAActiveDescendant("listbox", "roaming"));
+ gQueue.push(new moveARIAActiveDescendantID("roaming2", "roaming"));
+ gQueue.push(new changeARIAActiveDescendantInvalid("listbox", "roaming3", "roaming"));
+ gQueue.push(new moveARIAActiveDescendantID("roaming", "roaming3"));
+
+ gQueue.push(new synthFocus("activedesc_nondesc_input",
+ new focusChecker("activedesc_nondesc_option")));
+
+ let shadowRoot = document.getElementById("shadow").shadowRoot;
+ let shadowListbox = shadowRoot.getElementById("shadowListbox");
+ let shadowItem1 = shadowRoot.getElementById("shadowItem1");
+ let shadowItem2 = shadowRoot.getElementById("shadowItem2");
+ gQueue.push(new synthFocus(shadowListbox, new focusChecker(shadowItem1)));
+ gQueue.push(new changeARIAActiveDescendant(shadowListbox, shadowItem2));
+
+ gQueue.invoke();
+ await queueFinished;
+ // Tests beyond this point use await rather than eventQueue.
+
+ info("Testing simultaneous insertion, relocation and aria-activedescendant");
+ let comboboxWithHiddenList = getNode("comboboxWithHiddenList");
+ let evtProm = PromEvents.waitForEvent(EVENT_FOCUS, comboboxWithHiddenList);
+ comboboxWithHiddenList.focus();
+ await evtProm;
+ testStates(comboboxWithHiddenList, STATE_FOCUSED);
+ // hiddenList is owned, so unhiding causes insertion and relocation.
+ getNode("hiddenList").hidden = false;
+ evtProm = Promise.all([
+ PromEvents.waitForEvent(EVENT_FOCUS, "hiddenListOption"),
+ PromEvents.waitForStateChange("hiddenListOption", EXT_STATE_ACTIVE, true, true),
+ ]);
+ comboboxWithHiddenList.setAttribute("aria-activedescendant", "hiddenListOption");
+ await evtProm;
+ testStates("hiddenListOption", STATE_FOCUSED);
+
+ info("Testing active state changes when not focused");
+ testStates("listbox", 0, 0, STATE_FOCUSED);
+ evtProm = Promise.all([
+ PromEvents.waitForStateChange("roaming3", EXT_STATE_ACTIVE, false, true),
+ PromEvents.waitForStateChange("item1", EXT_STATE_ACTIVE, true, true),
+ ]);
+ getNode("listbox").setAttribute("aria-activedescendant", "item1");
+ await evtProm;
+
+ info("Testing that focus is always fired first");
+ const listbox = getNode("listbox");
+ evtProm = PromEvents.waitForEvent(EVENT_FOCUS, "item1");
+ listbox.focus();
+ await evtProm;
+ const item1 = getNode("item1");
+ evtProm = PromEvents.waitForOrderedEvents([
+ [EVENT_FOCUS, "item2"],
+ [EVENT_NAME_CHANGE, item1],
+ ], "Focus then name change");
+ item1.setAttribute("aria-label", "changed");
+ listbox.setAttribute("aria-activedescendant", "item2");
+ await evtProm;
+
+ info("Setting aria-activedescendant to invalid id on non-focused node");
+ const combobox_entry = getNode("combobox_entry");
+ evtProm = PromEvents.waitForEvents({
+ expected: [[EVENT_FOCUS, combobox_entry]],
+ unexpected: [[EVENT_FOCUS, listbox]],
+ });
+ combobox_entry.focus();
+ listbox.setAttribute("aria-activedescendant", "invalid");
+ await evtProm;
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=429547"
+ title="Support aria-activedescendant usage in nsIAccesible::TakeFocus()">
+ Mozilla Bug 429547
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=761102"
+ title="Focus may be missed when ARIA active-descendant is changed on active composite widget">
+ Mozilla Bug 761102
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="listbox" aria-activedescendant="item1" id="listbox" tabindex="1"
+ aria-owns="item3">
+ <div role="listitem" id="item1">item1</div>
+ <div role="listitem" id="item2">item2</div>
+ <div role="listitem" id="roaming">roaming</div>
+ <div role="listitem" id="roaming2">roaming2</div>
+ </div>
+ <div role="listitem" id="item3">item3</div>
+
+ <div role="combobox" id="combobox">
+ <input id="combobox_entry">
+ <ul>
+ <li role="option" id="combobox_option1">option1</li>
+ <li role="option" id="combobox_option2">option2</li>
+ </ul>
+ </div>
+
+ <!-- aria-activedescendant targeting a non-descendant -->
+ <input id="activedesc_nondesc_input" aria-activedescendant="activedesc_nondesc_option">
+ <div role="listbox">
+ <div role="option" id="activedesc_nondesc_option">option</div>
+ </div>
+
+ <div id="shadow"></div>
+ <script>
+ let host = document.getElementById("shadow");
+ let shadow = host.attachShadow({mode: "open"});
+ let listbox = document.createElement("div");
+ listbox.id = "shadowListbox";
+ listbox.setAttribute("role", "listbox");
+ listbox.setAttribute("tabindex", "0");
+ shadow.appendChild(listbox);
+ let item = document.createElement("div");
+ item.id = "shadowItem1";
+ item.setAttribute("role", "option");
+ listbox.appendChild(item);
+ listbox.setAttribute("aria-activedescendant", "shadowItem1");
+ item = document.createElement("div");
+ item.id = "shadowItem2";
+ item.setAttribute("role", "option");
+ listbox.appendChild(item);
+ </script>
+
+ <div id="comboboxWithHiddenList" tabindex="0" role="combobox" aria-owns="hiddenList">
+ </div>
+ <div id="hiddenList" hidden role="listbox">
+ <div id="hiddenListOption" role="option"></div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_autocomplete.html b/accessible/tests/mochitest/events/test_focus_autocomplete.html
new file mode 100644
index 0000000000..c179398cc0
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_autocomplete.html
@@ -0,0 +1,83 @@
+<!doctype html>
+
+<head>
+ <title>Form Autocomplete Tests</title>
+
+ <link rel="stylesheet"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script src="../common.js"></script>
+ <script src="../promisified-events.js"></script>
+ <script src="../role.js"></script>
+
+ <script type="application/javascript">
+ const { TestUtils } = ChromeUtils.import(
+ "resource://testing-common/TestUtils.jsm");
+
+ async function waitForFocusOnOptionWithname(name) {
+ let event = await waitForEvent(
+ EVENT_FOCUS,
+ evt => evt.accessible.role == ROLE_COMBOBOX_OPTION
+ );
+ while (!event.accessible.name) {
+ // Sometimes, the name is null for a very short time after the focus
+ // event.
+ await waitForEvent(EVENT_NAME_CHANGE, event.accessible);
+ }
+ is(event.accessible.name, name, "Got focus on option with name " + name);
+ }
+
+ async function doTests() {
+ const input = getNode("input");
+ info("Focusing the input");
+ let focused = waitForEvent(EVENT_FOCUS, input);
+ input.focus();
+ await focused;
+
+ let shown = waitForEvent(EVENT_SHOW, event =>
+ event.accessible.role == ROLE_GROUPING &&
+ event.accessible.firstChild.role == ROLE_COMBOBOX_LIST);
+ info("Pressing ArrowDown to open the popup");
+ synthesizeKey("KEY_ArrowDown");
+ await shown;
+ // The popup still doesn't seem to be ready even once it's fired an a11y
+ // show event!
+ const controller = Cc["@mozilla.org/autocomplete/controller;1"].
+ getService(Ci.nsIAutoCompleteController);
+ info("Waiting for popup to be fully open and ready");
+ await TestUtils.waitForCondition(() => controller.input.popupOpen);
+
+ focused = waitForFocusOnOptionWithname("a");
+ info("Pressing ArrowDown to focus first item");
+ synthesizeKey("KEY_ArrowDown");
+ await focused;
+
+ focused = waitForFocusOnOptionWithname("b");
+ info("Pressing ArrowDown to focus the second item");
+ synthesizeKey("KEY_ArrowDown");
+ await focused;
+
+ focused = waitForEvent(EVENT_FOCUS, input);
+ info("Pressing enter to select the second item");
+ synthesizeKey("KEY_Enter");
+ await focused;
+ is(input.value, "b", "input value filled with second item");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+<body>
+ <input id="input" list="list">
+ <datalist id="list">
+ <option id="a" value="a">
+ <option id="b" value="b">
+ </datalist>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_autocomplete.xhtml b/accessible/tests/mochitest/events/test_focus_autocomplete.xhtml
new file mode 100644
index 0000000000..69cdac14c5
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_autocomplete.xhtml
@@ -0,0 +1,507 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<!-- Firefox searchbar -->
+<?xml-stylesheet href="chrome://browser/content/browser.css"
+ type="text/css"?>
+<!-- SeaMonkey searchbar -->
+<?xml-stylesheet href="chrome://navigator/content/navigator.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Accessible focus event testing">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript"
+ src="../autocomplete.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Hacky stuffs
+
+ // This is the hacks needed to use a searchbar without browser.js.
+ var BrowserSearch = {
+ updateOpenSearchBadge() {}
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function loadFormAutoComplete(aIFrameID)
+ {
+ this.iframeNode = getNode(aIFrameID);
+ this.iframe = getAccessible(aIFrameID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.iframe)
+ ];
+
+ this.invoke = function loadFormAutoComplete_invoke()
+ {
+ var url = "data:text/html,<html><body><form id='form'>" +
+ "<input id='input' name='a11ytest-formautocomplete'>" +
+ "</form></body></html>";
+ this.iframeNode.setAttribute("src", url);
+ }
+
+ this.getID = function loadFormAutoComplete_getID()
+ {
+ return "load form autocomplete page";
+ }
+ }
+
+ function initFormAutoCompleteBy(aIFrameID, aAutoCompleteValue)
+ {
+ this.iframe = getAccessible(aIFrameID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.iframe)
+ ];
+
+ this.invoke = function initFormAutoCompleteBy_invoke()
+ {
+ var iframeDOMDoc = getIFrameDOMDoc(aIFrameID);
+
+ var inputNode = iframeDOMDoc.getElementById("input");
+ inputNode.value = aAutoCompleteValue;
+ var formNode = iframeDOMDoc.getElementById("form");
+ formNode.submit();
+ }
+
+ this.getID = function initFormAutoCompleteBy_getID()
+ {
+ return "init form autocomplete by '" + aAutoCompleteValue + "'";
+ }
+ }
+
+ function loadHTML5ListAutoComplete(aIFrameID)
+ {
+ this.iframeNode = getNode(aIFrameID);
+ this.iframe = getAccessible(aIFrameID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.iframe)
+ ];
+
+ this.invoke = function loadHTML5ListAutoComplete_invoke()
+ {
+ var url = "data:text/html,<html><body>" +
+ "<datalist id='cities'><option>hello</option><option>hi</option></datalist>" +
+ "<input id='input' list='cities'>" +
+ "</body></html>";
+ this.iframeNode.setAttribute("src", url);
+ }
+
+ this.getID = function loadHTML5ListAutoComplete_getID()
+ {
+ return "load HTML5 list autocomplete page";
+ }
+ }
+
+ function removeChar(aID, aCheckerOrEventSeq)
+ {
+ this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
+
+ this.invoke = function removeChar_invoke()
+ {
+ synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
+ synthesizeKey("KEY_Delete");
+ }
+
+ this.getID = function removeChar_getID()
+ {
+ return "remove char on " + prettyName(aID);
+ }
+ }
+
+ function replaceOnChar(aID, aChar, aCheckerOrEventSeq)
+ {
+ this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
+
+ this.invoke = function replaceOnChar_invoke()
+ {
+ this.DOMNode.select();
+ sendString(aChar);
+ }
+
+ this.getID = function replaceOnChar_getID()
+ {
+ return "replace on char '" + aChar + "' for" + prettyName(aID);
+ }
+ }
+
+ function focusOnMouseOver(aIDFunc, aIDFuncArg)
+ {
+ this.eventSeq = [ new focusChecker(aIDFunc, aIDFuncArg) ];
+
+ this.invoke = function focusOnMouseOver_invoke()
+ {
+ this.id = aIDFunc(aIDFuncArg);
+ this.node = getNode(this.id);
+ this.window = this.node.ownerGlobal;
+
+ if (this.node.localName == "tree") {
+ var tree = getAccessible(this.node);
+ var accessible = getAccessible(this.id);
+ if (tree != accessible) {
+ var itemX = {}, itemY = {}, treeX = {}, treeY = {};
+ accessible.getBounds(itemX, itemY, {}, {});
+ tree.getBounds(treeX, treeY, {}, {});
+ this.x = itemX.value - treeX.value;
+ this.y = itemY.value - treeY.value;
+ }
+ }
+
+ // Generate mouse move events in timeouts until autocomplete popup list
+ // doesn't have it, the reason is do that because autocomplete popup
+ // ignores mousemove events firing in too short range.
+ synthesizeMouse(this.node, this.x, this.y, { type: "mousemove" });
+ this.doMouseMoveFlood(this);
+ }
+
+ this.finalCheck = function focusOnMouseOver_getID()
+ {
+ this.isFlooding = false;
+ }
+
+ this.getID = function focusOnMouseOver_getID()
+ {
+ return "mouse over on " + prettyName(aIDFunc(aIDFuncArg));
+ }
+
+ this.doMouseMoveFlood = function focusOnMouseOver_doMouseMoveFlood(aThis)
+ {
+ synthesizeMouse(aThis.node, aThis.x + 1, aThis.y + 1,
+ { type: "mousemove" }, aThis.window);
+
+ if (aThis.isFlooding)
+ aThis.window.setTimeout(aThis.doMouseMoveFlood, 0, aThis);
+ }
+
+ this.id = null;
+ this.node = null;
+ this.window = null;
+
+ this.isFlooding = true;
+ this.x = 1;
+ this.y = 1;
+ }
+
+ function selectByClick(aIDFunc, aIDFuncArg,
+ aFocusTargetFunc, aFocusTargetFuncArg)
+ {
+ this.eventSeq = [ new focusChecker(aFocusTargetFunc, aFocusTargetFuncArg) ];
+
+ this.invoke = function selectByClick_invoke()
+ {
+ var id = aIDFunc(aIDFuncArg);
+ var node = getNode(id);
+ var targetWindow = node.ownerGlobal;
+
+ if (node.localName == "tree") {
+ var tree = getAccessible(node);
+ var accessible = getAccessible(id);
+ if (tree != accessible) {
+ var itemX = {}, itemY = {}, treeX = {}, treeY = {};
+ accessible.getBounds(itemX, itemY, {}, {});
+ tree.getBounds(treeX, treeY, {}, {});
+ this.x = itemX.value - treeX.value;
+ this.y = itemY.value - treeY.value;
+ }
+ }
+
+ synthesizeMouseAtCenter(node, {}, targetWindow);
+ }
+
+ this.getID = function selectByClick_getID()
+ {
+ return "select by click " + prettyName(aIDFunc(aIDFuncArg));
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Target getters
+
+ function getItem(aItemObj)
+ {
+ var autocompleteNode = aItemObj.autocompleteNode;
+
+ // XUL searchbar
+ if (autocompleteNode.localName == "searchbar") {
+ let popupNode = autocompleteNode._popup;
+ if (popupNode) {
+ let list = getAccessible(popupNode);
+ return list.getChildAt(aItemObj.index);
+ }
+ }
+
+ // XUL autocomplete
+ let popupNode = autocompleteNode.popup;
+ if (!popupNode) {
+ // HTML form autocomplete
+ var controller = Cc["@mozilla.org/autocomplete/controller;1"].
+ getService(Ci.nsIAutoCompleteController);
+ popupNode = controller.input.popup;
+ }
+
+ if (popupNode) {
+ if ("richlistbox" in popupNode) {
+ let list = getAccessible(popupNode.richlistbox);
+ return list.getChildAt(aItemObj.index);
+ }
+
+ let list = getAccessible(popupNode.tree);
+ return list.getChildAt(aItemObj.index + 1);
+ }
+ return null;
+ }
+
+ function getTextEntry(aID)
+ {
+ // For form autocompletes the autocomplete widget and text entry widget
+ // is the same widget, for XUL autocompletes the text entry is a first
+ // child.
+ var localName = getNode(aID).localName;
+
+ // HTML form autocomplete
+ if (localName == "input")
+ return getAccessible(aID);
+
+ // XUL searchbar
+ if (localName == "searchbar")
+ return getAccessible(getNode(aID).textbox);
+
+ return null;
+ }
+
+ function itemObj(aID, aIdx)
+ {
+ this.autocompleteNode = getNode(aID);
+
+ this.autocomplete = this.autocompleteNode.localName == "searchbar" ?
+ getAccessible(this.autocompleteNode.textbox) :
+ getAccessible(this.autocompleteNode);
+
+ this.index = aIdx;
+ }
+
+ function getIFrameDOMDoc(aIFrameID)
+ {
+ return getNode(aIFrameID).contentDocument;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test helpers
+
+ function queueAutoCompleteTests(aID)
+ {
+ // focus autocomplete text entry
+ gQueue.push(new synthFocus(aID, new focusChecker(getTextEntry, aID)));
+
+ // open autocomplete popup
+ gQueue.push(new synthDownKey(aID, new nofocusChecker()));
+
+ // select second option ('hi' option), focus on it
+ gQueue.push(new synthUpKey(aID,
+ new focusChecker(getItem, new itemObj(aID, 1))));
+
+ // choose selected option, focus on text entry
+ gQueue.push(new synthEnterKey(aID, new focusChecker(getTextEntry, aID)));
+
+ // remove char, autocomplete popup appears
+ gQueue.push(new removeChar(aID, new nofocusChecker()));
+
+ // select first option ('hello' option), focus on it
+ gQueue.push(new synthDownKey(aID,
+ new focusChecker(getItem, new itemObj(aID, 0))));
+
+ // mouse move on second option ('hi' option), focus on it
+ gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 1)));
+
+ // autocomplete popup updated (no selected item), focus on textentry
+ gQueue.push(new synthKey(aID, "e", null, new focusChecker(getTextEntry, aID)));
+
+ // select first option ('hello' option), focus on it
+ gQueue.push(new synthDownKey(aID,
+ new focusChecker(getItem, new itemObj(aID, 0))));
+
+ // popup gets hidden, focus on textentry
+ gQueue.push(new synthRightKey(aID, new focusChecker(getTextEntry, aID)));
+
+ // popup gets open, no focus
+ gQueue.push(new synthOpenComboboxKey(aID, new nofocusChecker()));
+
+ // select first option again ('hello' option), focus on it
+ gQueue.push(new synthDownKey(aID,
+ new focusChecker(getItem, new itemObj(aID, 0))));
+
+ // no option is selected, focus on text entry
+ gQueue.push(new synthUpKey(aID, new focusChecker(getTextEntry, aID)));
+
+ // close popup, no focus
+ gQueue.push(new synthEscapeKey(aID, new nofocusChecker()));
+
+ // autocomplete popup appears (no selected item), focus stays on textentry
+ gQueue.push(new replaceOnChar(aID, "h", new nofocusChecker()));
+
+ // mouse move on first option ('hello' option), focus on it
+ gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 0)));
+
+ // click first option ('hello' option), popup closes, focus on text entry
+ gQueue.push(new selectByClick(getItem, new itemObj(aID, 0), getTextEntry, aID));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gInitQueue = null;
+ function initTests()
+ {
+ if (SEAMONKEY || MAC) {
+ todo(false, "Skipping this test on SeaMonkey ftb. (Bug 718237), and on Mac (bug 746177)");
+ shutdownAutoComplete();
+ SimpleTest.finish();
+ return;
+ }
+
+ gInitQueue = new eventQueue();
+ gInitQueue.push(new loadFormAutoComplete("iframe"));
+ gInitQueue.push(new initFormAutoCompleteBy("iframe", "hello"));
+ gInitQueue.push(new initFormAutoCompleteBy("iframe", "hi"));
+ gInitQueue.push(new loadHTML5ListAutoComplete("iframe2"));
+ gInitQueue.onFinish = function initQueue_onFinish()
+ {
+ SimpleTest.executeSoon(doTests);
+ return DO_NOT_FINISH_TEST;
+ }
+ gInitQueue.invoke();
+ }
+
+ var gQueue = null;
+ function doTests()
+ {
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ ////////////////////////////////////////////////////////////////////////////
+ // tree popup autocomplete tests
+ queueAutoCompleteTests("autocomplete");
+
+ ////////////////////////////////////////////////////////////////////////////
+ // richlistbox popup autocomplete tests
+ queueAutoCompleteTests("richautocomplete");
+
+ ////////////////////////////////////////////////////////////////////////////
+ // HTML form autocomplete tests
+ queueAutoCompleteTests(getIFrameDOMDoc("iframe").getElementById("input"));
+
+ ////////////////////////////////////////////////////////////////////////////
+ // HTML5 list autocomplete tests
+ queueAutoCompleteTests(getIFrameDOMDoc("iframe2").getElementById("input"));
+
+ ////////////////////////////////////////////////////////////////////////////
+ // searchbar tests
+
+ // focus searchbar, focus on text entry
+ gQueue.push(new synthFocus("searchbar",
+ new focusChecker(getTextEntry, "searchbar")));
+ // open search engine popup, no focus
+ gQueue.push(new synthOpenComboboxKey("searchbar", new nofocusChecker()));
+ // select first item, focus on it
+ gQueue.push(new synthDownKey("searchbar",
+ new focusChecker(getItem, new itemObj("searchbar", 0))));
+ // mouse over on second item, focus on it
+ gQueue.push(new focusOnMouseOver(getItem, new itemObj("searchbar", 1)));
+ // press enter key, focus on text entry
+ gQueue.push(new synthEnterKey("searchbar",
+ new focusChecker(getTextEntry, "searchbar")));
+ // click on search button, open popup, focus goes to document
+ var searchBtn = getAccessible(getNode("searchbar").searchButton);
+ gQueue.push(new synthClick(searchBtn, new focusChecker(document)));
+ // select first item, focus on it
+ gQueue.push(new synthDownKey("searchbar",
+ new focusChecker(getItem, new itemObj("searchbar", 0))));
+ // close popup, focus goes on document
+ gQueue.push(new synthEscapeKey("searchbar", new focusChecker(document)));
+
+ gQueue.onFinish = function()
+ {
+ // unregister 'test-a11y-search' autocomplete search
+ shutdownAutoComplete();
+ }
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Register 'test-a11y-search' autocomplete search.
+ // XPFE AutoComplete needs to register early.
+ initAutoComplete([ "hello", "hi" ],
+ [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]);
+
+ addA11yLoadEvent(initTests);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=383759"
+ title="Focus event inconsistent for search box autocomplete">
+ Mozilla Bug 383759
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559766"
+ title="Add accessibility support for @list on HTML input and for HTML datalist">
+ Mozilla Bug 559766
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <html:input is="autocomplete-input"
+ id="autocomplete"
+ autocompletesearch="test-a11y-search"/>
+
+ <html:input is="autocomplete-input"
+ id="richautocomplete"
+ autocompletesearch="test-a11y-search"
+ autocompletepopup="richpopup"/>
+ <panel is="autocomplete-richlistbox-popup"
+ id="richpopup"
+ type="autocomplete-richlistbox"
+ noautofocus="true"/>
+
+ <iframe id="iframe"/>
+
+ <iframe id="iframe2"/>
+
+ <searchbar id="searchbar"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_canvas.html b/accessible/tests/mochitest/events/test_focus_canvas.html
new file mode 100644
index 0000000000..e2464e41a6
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_canvas.html
@@ -0,0 +1,58 @@
+<html>
+
+<head>
+ <title>Accessible focus testing in canvas subdom</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthFocus("button"));
+ gQueue.push(new synthTab("button", new focusChecker("textbox")));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="Expose content in Canvas element"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">
+ Mozilla Bug 495912
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <canvas>
+ <input id="button" type="button">
+ <input id="textbox">
+ </canvas>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_contextmenu.xhtml b/accessible/tests/mochitest/events/test_focus_contextmenu.xhtml
new file mode 100644
index 0000000000..a0c92212dc
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_contextmenu.xhtml
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Context menu focus testing">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var winLowerThanVista = navigator.platform.indexOf("Win") == 0;
+ if (winLowerThanVista) {
+ var version = Services.sysinfo.getProperty("version");
+ version = parseFloat(version);
+ winLowerThanVista = !(version >= 6.0);
+ }
+
+ var gQueue = null;
+ function doTests()
+ {
+ // bug 746183 - Whole file times out on OS X
+ if (MAC || winLowerThanVista) {
+ todo(false, "Reenable on mac after fixing bug 746183!");
+ SimpleTest.finish();
+ return;
+ }
+
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthFocus("button"));
+ gQueue.push(new synthContextMenu("button", [
+ new invokerChecker(EVENT_MENUPOPUP_START, "contextmenu"),
+ new invokerChecker("popupshown", "contextmenu"),
+ ]));
+ gQueue.push(new synthDownKey("button", new focusChecker("item1")));
+ gQueue.push(new synthEscapeKey("contextmenu", new focusChecker("button")));
+
+ gQueue.push(new synthContextMenu("button",
+ new invokerChecker(EVENT_MENUPOPUP_START, "contextmenu")));
+ gQueue.push(new synthDownKey("contextmenu", new focusChecker("item1")));
+ gQueue.push(new synthDownKey("item1", new focusChecker("item2")));
+ gQueue.push(new synthRightKey("item2", new focusChecker("item2.1")));
+ if (WIN) {
+ todo(false, "synthEscapeKey for item2.1 and item2 disabled due to bug 691580");
+ } else {
+ gQueue.push(new synthEscapeKey("item2.1", new focusChecker("item2")));
+ gQueue.push(new synthEscapeKey("item2", new focusChecker("button")));
+ }
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <button id="button" context="contextmenu" label="button"/>
+ <menupopup id="contextmenu">
+ <menuitem id="item1" label="item1"/>
+ <menu id="item2" label="item2">
+ <menupopup>
+ <menuitem id="item2.1" label="item2.1"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_controls.html b/accessible/tests/mochitest/events/test_focus_controls.html
new file mode 100644
index 0000000000..4d25e78908
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_controls.html
@@ -0,0 +1,76 @@
+<html>
+
+<head>
+ <title>Accessible focus testing on HTML controls</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTests() {
+ gQueue = new eventQueue(EVENT_FOCUS);
+
+ gQueue.push(new synthFocus("textbox"));
+ gQueue.push(new synthFocus("textarea"));
+ gQueue.push(new synthFocus("button1"));
+ gQueue.push(new synthFocus("button2"));
+ gQueue.push(new synthFocus("checkbox"));
+ gQueue.push(new synthFocus("radio1"));
+ gQueue.push(new synthDownKey("radio1", new focusChecker("radio2")));
+
+ // no focus events for checkbox or radio inputs when they are checked
+ // programmatically
+ gQueue.push(new changeCurrentItem("checkbox"));
+ gQueue.push(new changeCurrentItem("radio1"));
+
+ let fileBrowseButton = getAccessible("file").firstChild;
+ gQueue.push(new synthFocus("file", new focusChecker(fileBrowseButton)));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="textbox">
+ <textarea id="textarea"></textarea>
+
+ <input id="button1" type="button" value="button">
+ <button id="button2">button</button>
+ <input id="checkbox" type="checkbox">
+ <input id="radio1" type="radio" name="radiogroup">
+ <input id="radio2" type="radio" name="radiogroup">
+ <input id="file" type="file">
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_doc.html b/accessible/tests/mochitest/events/test_focus_doc.html
new file mode 100644
index 0000000000..a35fc06ed0
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_doc.html
@@ -0,0 +1,92 @@
+<html>
+
+<head>
+ <title>Accessible document focus event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ var gQueue = null;
+
+ // var gA11yEventDumpID = "eventdump";
+ // gA11yEventDumpToConsole = true;
+
+ function doTests() {
+ // setup
+ var frameDoc = document.getElementById("iframe").contentDocument;
+ frameDoc.designMode = "on";
+ var frameDocAcc = getAccessible(frameDoc, [nsIAccessibleDocument]);
+ var buttonAcc = getAccessible("b1");
+
+ var frame2Doc = document.getElementById("iframe2").contentDocument;
+ var frame2Input = frame2Doc.getElementById("input");
+ var frame2DocAcc = getAccessible(frame2Doc);
+ var frame2InputAcc = getAccessible(frame2Input);
+
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ // try to give focus to contentEditable frame twice to cover bug 512059
+ gQueue.push(new synthFocus(buttonAcc));
+ gQueue.push(new synthTab(frameDocAcc, new focusChecker(frameDocAcc)));
+ gQueue.push(new synthFocus(buttonAcc));
+ gQueue.push(new synthTab(frameDocAcc, new focusChecker(frameDocAcc)));
+
+ // focus on not editable document
+ gQueue.push(new synthFocus(frame2InputAcc));
+ gQueue.push(new synthShiftTab(frame2DocAcc, new focusChecker(frame2DocAcc)));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512058"
+ title="Can't set focus to designMode document via accessibility APIs">
+ Mozilla Bug 512058
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512059"
+ title="Accessibility focus event never fired for designMode document after the first focus">
+ Mozilla Bug 512059
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=618046"
+ title="No focus change event when Shift+Tab at top of screen">
+ Mozilla Bug 618046
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="eventdump"></div>
+
+ <div id="testContainer">
+ <button id="b1">a button</button>
+ <iframe id="iframe" src="about:blank"></iframe>
+ <button id="b2">a button</button>
+ <iframe id="iframe2" src="data:text/html,<html><input id='input'></html>"></iframe>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_general.html b/accessible/tests/mochitest/events/test_focus_general.html
new file mode 100644
index 0000000000..6919ed8860
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_general.html
@@ -0,0 +1,176 @@
+<html>
+
+<head>
+ <title>Accessible focus testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function focusElmWhileSubdocIsFocused(aID) {
+ this.DOMNode = getNode(aID);
+
+ this.invoke = function focusElmWhileSubdocIsFocused_invoke() {
+ this.DOMNode.focus();
+ };
+
+ this.eventSeq = [
+ new focusChecker(this.DOMNode),
+ ];
+
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.DOMNode.ownerDocument),
+ ];
+
+ this.getID = function focusElmWhileSubdocIsFocused_getID() {
+ return "Focus element while subdocument is focused " + prettyName(aID);
+ };
+ }
+
+ function imageMapChecker(aID) {
+ var node = getNode(aID);
+ this.type = EVENT_FOCUS;
+ this.match = function imageMapChecker_match(aEvent) {
+ return aEvent.DOMNode == node;
+ };
+ }
+
+ function topMenuChecker() {
+ this.type = EVENT_FOCUS;
+ this.match = function topMenuChecker_match(aEvent) {
+ return aEvent.accessible.role == ROLE_PARENT_MENUITEM;
+ };
+ }
+
+ function contextMenuChecker() {
+ this.type = EVENT_MENUPOPUP_START;
+ this.match = function contextMenuChecker_match(aEvent) {
+ return aEvent.accessible.role == ROLE_MENUPOPUP;
+ };
+ }
+
+ function focusContextMenuItemChecker() {
+ this.__proto__ = new focusChecker();
+
+ this.match = function focusContextMenuItemChecker_match(aEvent) {
+ return aEvent.accessible.role == ROLE_MENUITEM;
+ };
+ }
+
+ /**
+ * Do tests.
+ */
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTests() {
+ var frameDoc = document.getElementById("iframe").contentDocument;
+
+ var editableDoc = document.getElementById("editabledoc").contentDocument;
+ editableDoc.designMode = "on";
+
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthFocus("editablearea"));
+ gQueue.push(new synthFocus("navarea"));
+ gQueue.push(new synthTab("navarea", new focusChecker(frameDoc)));
+ gQueue.push(new focusElmWhileSubdocIsFocused("link"));
+
+ gQueue.push(new synthTab(editableDoc, new focusChecker(editableDoc)));
+ if (WIN || LINUX) {
+ // Alt key is used to active menubar and focus menu item on Windows,
+ // other platforms requires setting a ui.key.menuAccessKeyFocuses
+ // preference.
+ gQueue.push(new toggleTopMenu(editableDoc, new topMenuChecker()));
+ gQueue.push(new toggleTopMenu(editableDoc, new focusChecker(editableDoc)));
+ }
+ if (!(MAC && Services.prefs.getBoolPref("widget.macos.native-context-menus", false))) {
+ // Context menu accessibility is handled natively and not testable when
+ // native context menus are used on macOS.
+ gQueue.push(new synthContextMenu(editableDoc, new contextMenuChecker()));
+ gQueue.push(new synthDownKey(editableDoc, new focusContextMenuItemChecker()));
+ gQueue.push(new synthEscapeKey(editableDoc, new focusChecker(editableDoc)));
+ } else {
+ // If this test is run as part of multiple tests, it is displayed in the test harness iframe.
+ // In the non-native context menu case, right-clicking the editableDoc causes the editableDoc
+ // to scroll fully into view, and as a side-effect, the img below it ends up on the screen.
+ // When we're skipping the context menu check, scroll img onto the screen manually, because
+ // otherwise it may remain out-of-view and clipped by the test harness iframe.
+ var img = document.querySelector("img");
+ gQueue.push(new scrollIntoView(img, new nofocusChecker(img)));
+ }
+ if (SEAMONKEY) {
+ todo(false, "shift tab from editable document fails on (Windows) SeaMonkey! (Bug 718235)");
+ } else if (LINUX || MAC) {
+ todo(false, "shift tab from editable document fails on linux and Mac, bug 746519!");
+ } else {
+ gQueue.push(new synthShiftTab("link", new focusChecker("link")));
+ } // ! SEAMONKEY
+
+ gQueue.push(new synthFocus("a", new imageMapChecker("a")));
+ gQueue.push(new synthFocus("b", new imageMapChecker("b")));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=352220"
+ title="Inconsistent focus events when returning to a document frame">
+ Mozilla Bug 352220
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=550338"
+ title="Broken focus when returning to editable documents from menus">
+ Mozilla Bug 550338
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=961696"
+ title="Accessible object:state-changed:focused events for imagemap links are broken">
+ Mozilla Bug 961696
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="editablearea" contentEditable="true">editable area</div>
+ <div id="navarea" tabindex="0">navigable area</div>
+ <iframe id="iframe" src="data:text/html,<html></html>"></iframe>
+ <a id="link" href="">link</a>
+ <iframe id="editabledoc" src="about:blank"></iframe>
+
+ <map name="atoz_map">
+ <area id="a" coords="0,0,13,14" shape="rect">
+ <area id="b" coords="17,0,30,14" shape="rect">
+ </map>
+ <img width="447" height="15" usemap="#atoz_map" src="../letters.gif">
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_general.xhtml b/accessible/tests/mochitest/events/test_focus_general.xhtml
new file mode 100644
index 0000000000..c446359b32
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_general.xhtml
@@ -0,0 +1,124 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Accessible focus event testing">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTests()
+ {
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthFocus("textbox",
+ new focusChecker(getNode("textbox"))));
+ gQueue.push(new synthFocusOnFrame("editabledoc"));
+ gQueue.push(new synthFocus("radioclothes",
+ new focusChecker("radiosweater")));
+ gQueue.push(new synthDownKey("radiosweater",
+ new focusChecker("radiojacket")));
+ gQueue.push(new synthFocus("checkbox"));
+ gQueue.push(new synthFocus("button"));
+ gQueue.push(new synthFocus("checkbutton"));
+ gQueue.push(new synthFocus("radiobutton"));
+
+ // focus menubutton
+ gQueue.push(new synthFocus("menubutton"));
+ // click menubutton, open popup, focus stays on menu button
+ gQueue.push(new synthClick("menubutton", new nofocusChecker()));
+ // select first menu item ("item 1"), focus on menu item
+ gQueue.push(new synthDownKey("menubutton", new focusChecker("mb_item1")));
+ // choose select menu item, focus gets back to menubutton
+ gQueue.push(new synthEnterKey("mb_item1", new focusChecker("menubutton")));
+ // press enter to open popup, focus stays on menubutton
+ gQueue.push(new synthEnterKey("menubutton", new nofocusChecker()));
+ // select second menu item ("item 2"), focus on menu item
+ gQueue.push(new synthUpKey("menubutton", new focusChecker("mb_item2")));
+ // close the popup
+ gQueue.push(new synthEscapeKey("menubutton", new focusChecker("menubutton")));
+
+ // clicking on button having associated popup doesn't change focus
+ gQueue.push(new synthClick("popupbutton", [
+ new nofocusChecker(),
+ new invokerChecker("popupshown", "backpopup")
+ ]));
+
+ // select first menu item ("item 1"), focus on menu item
+ gQueue.push(new synthDownKey("popupbutton", new focusChecker("bp_item1")));
+ // choose select menu item, focus gets back to menubutton
+ gQueue.push(new synthEnterKey("bp_item1", new focusChecker("menubutton")));
+ // show popup again for the next test
+ gQueue.push(new synthClick("popupbutton", new nofocusChecker()));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=552368"
+ title=" fire focus event on document accessible whenever the root or body element is focused">
+ Mozilla Bug 552368
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <html:input id="textbox" value="hello"/>
+ <iframe id="editabledoc" src="focus.html"/>
+ <radiogroup id="radioclothes">
+ <radio id="radiosweater" label="radiosweater"/>
+ <radio id="radiocap" label="radiocap" disabled="true"/>
+ <radio id="radiojacket" label="radiojacket"/>
+ </radiogroup>
+ <checkbox id="checkbox" label="checkbox"/>
+ <button id="button" label="button"/>
+
+ <button id="menubutton" type="menu" label="menubutton">
+ <menupopup>
+ <menuitem id="mb_item1" label="item1"/>
+ <menuitem id="mb_item2" label="item2"/>
+ </menupopup>
+ </button>
+
+ <button id="checkbutton" type="checkbox" label="checkbutton"/>
+ <button id="radiobutton" type="radio" group="rbgroup" label="radio1"/>
+
+ <popupset>
+ <menupopup id="backpopup" position="after_start">
+ <menuitem id="bp_item1" label="Page 1"/>
+ <menuitem id="bp_item2" label="Page 2"/>
+ </menupopup>
+ </popupset>
+ <button id="popupbutton" label="Pop Me Up" popup="backpopup"/>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_listcontrols.xhtml b/accessible/tests/mochitest/events/test_focus_listcontrols.xhtml
new file mode 100644
index 0000000000..848657d3b3
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_listcontrols.xhtml
@@ -0,0 +1,153 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible focus event testing">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ let PromEvents = {};
+ Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js",
+ PromEvents);
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ async function doTests()
+ {
+ // Test focus events.
+ gQueue = new eventQueue();
+ // Later tests use await.
+ let queueFinished = new Promise(resolve => {
+ gQueue.onFinish = function() {
+ resolve();
+ return DO_NOT_FINISH_TEST;
+ };
+ });
+
+ gQueue.push(new synthFocus("richlistbox", new focusChecker("rlb_item1")));
+ gQueue.push(new synthDownKey("rlb_item1", new focusChecker("rlb_item2")));
+ gQueue.push(new synthFocus("multiselrichlistbox", new focusChecker("msrlb_item1")));
+ gQueue.push(new synthDownKey("msrlb_item1", new focusChecker("msrlb_item2"), { shiftKey: true }));
+ gQueue.push(new synthFocus("emptyrichlistbox", new focusChecker("emptyrichlistbox")));
+
+ gQueue.push(new synthFocus("menulist"));
+ gQueue.push(new synthClick("menulist", new focusChecker("ml_tangerine"),
+ { where: "center" }));
+ gQueue.push(new synthDownKey("ml_tangerine", new focusChecker("ml_marmalade")));
+ gQueue.push(new synthEscapeKey("ml_marmalade", new focusChecker("menulist")));
+
+ // On Windows, items get selected during navigation.
+ let expectedItem = WIN ? "ml_strawberry" : "ml_marmalade";
+ gQueue.push(new synthDownKey("menulist", new nofocusChecker(expectedItem)));
+ gQueue.push(new synthOpenComboboxKey("menulist", new focusChecker(expectedItem)));
+ gQueue.push(new synthEnterKey(expectedItem, new focusChecker("menulist")));
+
+ // no focus events for unfocused list controls when current item is
+ // changed.
+ gQueue.push(new synthFocus("emptyrichlistbox"));
+
+ gQueue.push(new changeCurrentItem("richlistbox", "rlb_item1"));
+ gQueue.push(new changeCurrentItem("menulist", WIN ? "ml_marmalade" : "ml_tangerine"));
+
+ gQueue.invoke();
+ await queueFinished;
+ // Tests beyond this point use await rather than eventQueue.
+
+ // When a menulist contains something other than XUL menuitems, we need
+ // to manage focus with aria-activedescendant.
+ info("Testing opening a menupopup with aria-activedescendant");
+ let popupDiv1 = getNode("menupopup_ad_div1");
+ let focused = PromEvents.waitForEvent(EVENT_FOCUS, popupDiv1);
+ let popup = getNode("menupopup_ad");
+ popup.openPopup();
+ await focused;
+ info("Testing removal of previous active descendant + setting new active descendant");
+ focused = PromEvents.waitForEvent(EVENT_FOCUS, "menupopup_ad_div2");
+ popupDiv1.remove();
+ popup.setAttribute("aria-activedescendant", "menupopup_ad_div2");
+ await focused;
+ popup.hidePopup();
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=433418"
+ title="Accessibles for focused HTML Select elements are not getting focused state">
+ Mozilla Bug 433418
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=474893"
+ title="List controls should fire a focus event on the selected child when tabbing or when the selected child changes while the list is focused">
+ Mozilla Bug 474893
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=552368"
+ title=" fire focus event on document accessible whenever the root or body element is focused">
+ Mozilla Bug 552368
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <richlistbox id="richlistbox">
+ <richlistitem id="rlb_item1">
+ <description>A XUL Description!</description>
+ </richlistitem>
+ <richlistitem id="rlb_item2">
+ <button label="A XUL Button"/>
+ </richlistitem>
+ </richlistbox>
+ <richlistbox id="multiselrichlistbox" seltype="multiple">
+ <richlistitem id="msrlb_item1">
+ <description>A XUL Description!</description>
+ </richlistitem>
+ <richlistitem id="msrlb_item2">
+ <button label="A XUL Button"/>
+ </richlistitem>
+ </richlistbox>
+ <richlistbox id="emptyrichlistbox" seltype="multiple"/>
+
+ <menulist id="menulist">
+ <menupopup>
+ <menuitem id="ml_tangerine" label="tangerine trees"/>
+ <menuitem id="ml_marmalade" label="marmalade skies"/>
+ <menuitem id="ml_strawberry" label="strawberry fields"/>
+ </menupopup>
+ </menulist>
+
+ <menulist>
+ <menupopup id="menupopup_ad" aria-activedescendant="menupopup_ad_div1">
+ <div id="menupopup_ad_div1" role="option"></div>
+ <div id="menupopup_ad_div2" role="option"></div>
+ </menupopup>
+ </menulist>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_menu.xhtml b/accessible/tests/mochitest/events/test_focus_menu.xhtml
new file mode 100644
index 0000000000..dda10517eb
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_menu.xhtml
@@ -0,0 +1,117 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Menu focus testing">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ // gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTests()
+ {
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ if (WIN) {
+ gQueue.push(new toggleTopMenu("fruit", new focusChecker("fruit")));
+ gQueue.push(new synthRightKey("fruit", new focusChecker("vehicle")));
+ gQueue.push(new synthEscapeKey("vehicle", new focusChecker(document)));
+ }
+
+ // mouse move activate items but no focus event until menubar is active
+ gQueue.push(new synthMouseMove("fruit", new nofocusChecker("apple")));
+
+ // mouseover and click on menuitem makes it active before menubar is
+ // active
+ gQueue.push(new synthClick("fruit", new focusChecker("fruit"), { where: "center" }));
+
+ // mouseover on menuitem when menubar is active
+ gQueue.push(new synthMouseMove("apple", new focusChecker("apple")));
+
+ // keydown on disabled menuitem (disabled items are skipped on linux)
+ if (WIN)
+ gQueue.push(new synthDownKey("apple", new focusChecker("orange")));
+
+ // menu and menuitem are both active
+ // XXX: intermitent failure because two focus events may be coalesced,
+ // think to workaround or fix this issue, when done enable queue invoker
+ // below and remove next two.
+ //gQueue.push(new synthRightKey("apple",
+ // [ new focusChecker("vehicle"),
+ // new focusChecker("cycle")]));
+ gQueue.push(new synthMouseMove("vehicle", new focusChecker("vehicle")));
+ gQueue.push(new synthDownKey("vehicle", new focusChecker("cycle")));
+
+ // open submenu
+ gQueue.push(new synthRightKey("cycle", new focusChecker("tricycle")));
+
+ // move to first menu in cycle, DOMMenuItemActive is fired for fruit,
+ // cycle and apple menuitems (bug 685191)
+ todo(false, "focus is fired for 'cycle' menuitem");
+ //gQueue.push(new synthRightKey("vehicle", new focusChecker("apple")));
+
+ // click menuitem to close menu, focus gets back to document
+ gQueue.push(new synthClick("tricycle", new focusChecker(document), { where: "center" }));
+
+ //enableLogging("focus,DOMEvents,tree"); // logging for bug708927
+ //gQueue.onFinish = function() { disableLogging(); }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menubar>
+ <menu id="fruit" label="Fruit">
+ <menupopup>
+ <menuitem id="apple" label="Apple"/>
+ <menuitem id="orange" label="Orange" disabled="true"/>
+ </menupopup>
+ </menu>
+ <menu id="vehicle" label="Vehicle">
+ <menupopup id="vehiclePopup">
+ <menu id="cycle" label="cycle">
+ <menupopup>
+ <menuitem id="tricycle" label="tricycle"/>
+ </menupopup>
+ </menu>
+ <menuitem id="car" label="Car" disabled="true"/>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_name.html b/accessible/tests/mochitest/events/test_focus_name.html
new file mode 100644
index 0000000000..aa77923909
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_name.html
@@ -0,0 +1,116 @@
+<html>
+
+<head>
+ <title>Accessible name testing on focus</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Checker for invokers.
+ */
+ function actionChecker(aID, aDescription) {
+ this.__proto__ = new invokerChecker(EVENT_FOCUS, aID);
+
+ this.check = function actionChecker_check(aEvent) {
+ var target = aEvent.accessible;
+ is(target.description, aDescription,
+ "Wrong description for " + prettyName(target));
+ };
+ }
+
+ var gFocusHandler = {
+ handleEvent: function gFocusHandler_handleEvent(aEvent) {
+ var elm = aEvent.target;
+ if (elm.nodeType != Node.ELEMENT_NODE)
+ return;
+
+ gTooltipElm.style.display = "block";
+
+ elm.setAttribute("aria-describedby", "tooltip");
+ },
+ };
+
+ var gBlurHandler = {
+ handleEvent: function gBlurHandler_handleEvent(aEvent) {
+ gTooltipElm.style.display = "none";
+
+ var elm = aEvent.target;
+ if (elm.nodeType == Node.ELEMENT_NODE)
+ elm.removeAttribute("aria-describedby");
+ },
+ };
+
+ /**
+ * Do tests.
+ */
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ var gButtonElm = null;
+ var gTextboxElm = null;
+ var gTooltipElm = null;
+
+ function doTests() {
+ gButtonElm = getNode("button");
+ gTextboxElm = getNode("textbox");
+ gTooltipElm = getNode("tooltip");
+
+ gButtonElm.addEventListener("focus", gFocusHandler);
+ gButtonElm.addEventListener("blur", gBlurHandler);
+ gTextboxElm.addEventListener("focus", gFocusHandler);
+ gTextboxElm.addEventListener("blur", gBlurHandler);
+
+ // The aria-describedby is changed on DOM focus. Accessible description
+ // should be updated when a11y focus is fired.
+ gQueue = new eventQueue(nsIAccessibleEvent.EVENT_FOCUS);
+ gQueue.onFinish = function() {
+ gButtonElm.removeEventListener("focus", gFocusHandler);
+ gButtonElm.removeEventListener("blur", gBlurHandler);
+ gTextboxElm.removeEventListener("focus", gFocusHandler);
+ gTextboxElm.removeEventListener("blur", gBlurHandler);
+ };
+
+ var descr = "It's a tooltip";
+ gQueue.push(new synthFocus("button", new actionChecker("button", descr)));
+ gQueue.push(new synthTab("textbox", new actionChecker("textbox", descr)));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=520709"
+ title="mochitest to ensure name/description are updated on a11y focus if they were changed on DOM focus">
+ Mozilla Bug 520709
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="tooltip" style="display: none" aria-hidden="true">It's a tooltip</div>
+ <button id="button">button</button>
+ <input id="textbox">
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_removal.html b/accessible/tests/mochitest/events/test_focus_removal.html
new file mode 100644
index 0000000000..eb47b07075
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_removal.html
@@ -0,0 +1,95 @@
+<html>
+
+<head>
+ <title>Test removal of focused accessible</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+ async function setFocus(aNodeToFocus, aExpectedFocus) {
+ let expected = aExpectedFocus || aNodeToFocus;
+ let focused = waitForEvent(EVENT_FOCUS, expected);
+ info("Focusing " + aNodeToFocus.id);
+ aNodeToFocus.focus();
+ await focused;
+ ok(true, expected.id + " focused after " +
+ aNodeToFocus.id + ".focus()");
+ }
+
+ async function expectFocusAfterRemove(aNodeToRemove, aExpectedFocus, aDisplayNone = false) {
+ let focused = waitForEvent(EVENT_FOCUS, aExpectedFocus);
+ info("Removing " + aNodeToRemove.id);
+ if (aDisplayNone) {
+ aNodeToRemove.style.display = "none";
+ } else {
+ aNodeToRemove.remove();
+ }
+ await focused;
+ let friendlyExpected = aExpectedFocus == document ?
+ "document" : aExpectedFocus.id;
+ ok(true, friendlyExpected + " focused after " +
+ aNodeToRemove.id + " removed");
+ }
+
+ async function doTests() {
+ info("Testing removal of focused node itself");
+ let button = getNode("button");
+ await setFocus(button);
+ await expectFocusAfterRemove(button, document);
+
+ info("Testing removal of focused node's parent");
+ let dialog = getNode("dialog");
+ let dialogButton = getNode("dialogButton");
+ await setFocus(dialogButton);
+ await expectFocusAfterRemove(dialog, document);
+
+ info("Testing removal of aria-activedescendant target");
+ let listbox = getNode("listbox");
+ let option = getNode("option");
+ await setFocus(listbox, option);
+ await expectFocusAfterRemove(option, listbox);
+
+ info("Test hiding focused element with display: none");
+ let groupingButton = getNode("groupingButton");
+ await setFocus(groupingButton);
+ await expectFocusAfterRemove(groupingButton, document, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <button id="button"></button>
+
+ <div role="dialog" id="dialog">
+ <button id="dialogButton"></button>
+ </div>
+
+ <div role="listbox" id="listbox" tabindex="0" aria-activedescendant="option">
+ <div role="option" id="option"></div>
+ </div>
+
+ <div role="grouping" id="grouping">
+ <button id="groupingButton">
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_selects.html b/accessible/tests/mochitest/events/test_focus_selects.html
new file mode 100644
index 0000000000..881c8049fd
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_selects.html
@@ -0,0 +1,173 @@
+<html>
+
+<head>
+ <title>Accessible focus testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+ var gQueue = null;
+
+ async function doTests() {
+ // Bug 746534 - File causes crash or hang on OS X
+ if (MAC) {
+ todo(false, "Bug 746534 - test file causes crash or hang on OS X");
+ SimpleTest.finish();
+ return;
+ }
+
+ let p = waitForEvent(EVENT_FOCUS, "orange");
+ // first item is focused until there's selection
+ getNode("list").focus();
+ await p;
+
+ p = waitForEvents({
+ expected: [[EVENT_SELECTION, "orange"]],
+ unexpected: [
+ [EVENT_FOCUS],
+ stateChangeEventArgs("orange", EXT_STATE_ACTIVE, true, true),
+ ],
+ });
+ // item is selected and stays focused and active
+ synthesizeKey("VK_DOWN");
+ await p;
+
+ p = waitForEvents([
+ stateChangeEventArgs("orange", EXT_STATE_ACTIVE, false, true),
+ stateChangeEventArgs("apple", EXT_STATE_ACTIVE, true, true),
+ [EVENT_FOCUS, "apple"],
+ ]);
+ // last selected item is focused
+ synthesizeKey("VK_DOWN", { shiftKey: true });
+ await p;
+
+ p = waitForEvents({
+ expected: [
+ [EVENT_FOCUS, "orange"],
+ stateChangeEventArgs("orange", EXT_STATE_ACTIVE, true, true),
+ ],
+ unexpected: [
+ [EVENT_FOCUS, "apple"],
+ stateChangeEventArgs("apple", EXT_STATE_ACTIVE, true, true),
+ ],
+ });
+ // no focus event if nothing is changed
+ synthesizeKey("VK_DOWN");
+ // current item is focused
+ synthesizeKey("VK_UP", { ctrlKey: true });
+ await p;
+
+ p = waitForEvent(EVENT_FOCUS, "emptylist");
+ // focus on empty list (no items to be focused)
+ synthesizeKey("VK_TAB");
+ await p;
+
+ p = waitForEvents({
+ expected: [[EVENT_FOCUS, "orange"]],
+ unexpected: [stateChangeEventArgs("orange", EXT_STATE_ACTIVE, true, true)],
+ });
+ // current item is focused
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ await p;
+
+ p = waitForEvent(EVENT_FOCUS, "combobox");
+ getNode("combobox").focus();
+ await p;
+
+ p = waitForEvents({
+ expected: [[EVENT_SELECTION, "cb_apple"]],
+ unexpected: [
+ [EVENT_FOCUS],
+ stateChangeEventArgs("cb_apple", EXT_STATE_ACTIVE, true, true),
+ ],
+ });
+ // collapsed combobox keeps a focus
+ synthesizeKey("VK_DOWN");
+ await p;
+
+ // no focus events for unfocused list controls when current item is
+ // changed
+
+ p = waitForEvent(EVENT_FOCUS, "emptylist");
+ getNode("emptylist").focus();
+ await p;
+
+ p = waitForEvents({
+ expected: [[EVENT_SELECTION, "orange"]],
+ unexpected: [
+ [EVENT_FOCUS],
+ stateChangeEventArgs("orange", EXT_STATE_ACTIVE, true, true),
+ ],
+ });
+ // An unfocused selectable list gets selection change events,
+ // but not active or focus change events.
+ getNode("list").selectedIndex = getNode("orange").index;
+ await p;
+
+ p = waitForEvents({
+ expected: [[EVENT_SELECTION, "cb_orange"]],
+ unexpected: [
+ [EVENT_FOCUS],
+ stateChangeEventArgs("cb_orange", EXT_STATE_ACTIVE, true, true),
+ ],
+ });
+ // An unfocused selectable combobox gets selection change events,
+ // but not focus events nor active state change events.
+ getNode("cb_orange").selected = true;
+ await p;
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=433418"
+ title="Accessibles for focused HTML Select elements are not getting focused state">
+ Mozilla Bug 433418
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=474893"
+ title="List controls should fire a focus event on the selected child when tabbing or when the selected child changes while the list is focused">
+ Mozilla Bug 474893
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="list" size="5" multiple="">
+ <option id="orange">Orange</option>
+ <option id="apple">Apple</option>
+ </select>
+
+ <select id="emptylist" size="5"></select>
+
+ <select id="combobox">
+ <option id="cb_orange">Orange</option>
+ <option id="cb_apple">Apple</option>
+ </select>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_tabbox.xhtml b/accessible/tests/mochitest/events/test_focus_tabbox.xhtml
new file mode 100644
index 0000000000..1b808831dc
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_tabbox.xhtml
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Tabbox focus testing">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTests()
+ {
+ if (MAC) {
+ todo(false, "Tests disabled because of imminent failure.");
+ SimpleTest.finish();
+ return;
+ }
+
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ var input = getNode("input");
+ gQueue.push(new synthClick("tab1", new focusChecker("tab1")));
+ gQueue.push(new synthTab("tab1", new focusChecker("checkbox1")));
+ gQueue.push(new synthKey("tab1", "VK_TAB", { ctrlKey: true },
+ new focusChecker(input)));
+ gQueue.push(new synthKey("tab2", "VK_TAB", { ctrlKey: true },
+ new focusChecker("tab3")));
+ gQueue.push(new synthKey("tab3", "VK_TAB", { ctrlKey: true },
+ new focusChecker("tab1")));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=370396"
+ title="Control+Tab to an empty tab panel in a tabbox causes focus to leave the tabbox">
+ Mozilla Bug 370396
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tabbox>
+ <tabs>
+ <tab id="tab1" label="Tab1" selected="true"/>
+ <tab id="tab2" label="Tab2" />
+ <tab id="tab3" label="Tab3" />
+ </tabs>
+ <tabpanels>
+ <tabpanel orient="vertical">
+ <groupbox orient="vertical">
+ <checkbox id="checkbox1" label="Monday" width="75"/>
+ <checkbox label="Tuesday" width="75"/>
+ <checkbox label="Wednesday" width="75"/>
+ <checkbox label="Thursday" width="75"/>
+ <checkbox label="Friday" width="75"/>
+ <checkbox label="Saturday" width="75"/>
+ <checkbox label="Sunday" width="75"/>
+ </groupbox>
+
+ <spacer style="height: 10px" />
+ <label value="Label After checkboxes" />
+ </tabpanel>
+ <tabpanel orient="vertical">
+ <html:input id="input" />
+ </tabpanel>
+ <tabpanel orient="vertical">
+ <description>Tab 3 content</description>
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_tree.xhtml b/accessible/tests/mochitest/events/test_focus_tree.xhtml
new file mode 100644
index 0000000000..f36816c788
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_tree.xhtml
@@ -0,0 +1,117 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL tree focus testing">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function focusTree(aTreeID)
+ {
+ var checker = new focusChecker(getFirstTreeItem, aTreeID);
+ this.__proto__ = new synthFocus(aTreeID, [ checker ]);
+ }
+
+ function moveToNextItem(aTreeID)
+ {
+ var checker = new focusChecker(getSecondTreeItem, aTreeID);
+ this.__proto__ = new synthDownKey(aTreeID, [ checker ]);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Helpers
+
+ function getTreeItemAt(aTreeID, aIdx)
+ { return getAccessible(aTreeID).getChildAt(aIdx + 1); }
+
+ function getFirstTreeItem(aTreeID)
+ { return getTreeItemAt(aTreeID, 0); }
+
+ function getSecondTreeItem(aTreeID)
+ { return getTreeItemAt(aTreeID, 1); }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ var gQueue = null;
+
+ //gA11yEventDumpID = "debug"; // debugging
+ //gA11yEventDumpToConsole = true; // debugging
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new focusTree("tree"));
+ gQueue.push(new moveToNextItem("tree"));
+ gQueue.push(new synthFocus("emptytree"));
+
+ // no focus event for changed current item for unfocused tree
+ gQueue.push(new changeCurrentItem("tree", 0));
+
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(5));
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=386821"
+ title="Need better solution for firing delayed event against xul tree">
+ Mozilla Bug 386821
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=406308"
+ title="Don't fire accessible focus events if widget is not actually in focus, confuses screen readers">
+ Mozilla Bug 406308
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="debug"/>
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col1" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+ <tree id="emptytree" flex="1">
+ <treecols>
+ <treecol id="emptytree_col1" flex="1" primary="true" label="column"/>
+ <treecol id="emptytree_col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="emptytree_treechildren"/>
+ </tree>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/events/test_focusable_statechange.html b/accessible/tests/mochitest/events/test_focusable_statechange.html
new file mode 100644
index 0000000000..5501f55187
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focusable_statechange.html
@@ -0,0 +1,128 @@
+<html>
+
+<head>
+ <title>Test removal of focused accessible</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+ function focusableStateChange(id, enabled) {
+ return [EVENT_STATE_CHANGE, e => {
+ e.QueryInterface(nsIAccessibleStateChangeEvent);
+ return getAccessible(id) == e.accessible &&
+ e.state == STATE_FOCUSABLE && (enabled == undefined || e.isEnabled == enabled);
+ }];
+ }
+
+ function editableStateChange(id, enabled) {
+ return [EVENT_STATE_CHANGE, e => {
+ e.QueryInterface(nsIAccessibleStateChangeEvent);
+ return getAccessible(id) == e.accessible &&
+ e.state == EXT_STATE_EDITABLE && e.isExtraState &&
+ (enabled == undefined || e.isEnabled == enabled);
+ }];
+ }
+
+ async function doTests() {
+ info("disable buttons.");
+ // Expect focusable change with 'disabled',
+ // and don't expect it with 'aria-disabled'.
+ let p = waitForEvents({
+ expected: [focusableStateChange("button2", false)],
+ unexpected: [focusableStateChange("button1")]
+ });
+ getNode("button1").setAttribute("aria-disabled", "true");
+ getNode("button2").disabled = true;
+ await p;
+
+ info("re-enable button");
+ // Expect focusable change with 'disabled',
+ // and don't expect it with 'aria-disabled'.
+ p = waitForEvents({
+ expected: [focusableStateChange("button2", true)],
+ unexpected: [focusableStateChange("button1")]
+ });
+ getNode("button1").setAttribute("aria-disabled", "false");
+ getNode("button2").disabled = false;
+ await p;
+
+ info("add tabindex");
+ // Expect focusable change on non-input,
+ // and don't expect event on an already focusable input.
+ p = waitForEvents({
+ expected: [focusableStateChange("div", true)],
+ unexpected: [focusableStateChange("button2")]
+ });
+ getNode("button2").tabIndex = "0";
+ getNode("div").tabIndex = "0";
+ await p;
+
+ info("remove tabindex");
+ // Expect focusable change when removing tabindex.
+ p = waitForEvent(...focusableStateChange("div", false));
+ getNode("div").removeAttribute("tabindex");
+ await p;
+
+ p = waitForEvent(...focusableStateChange("link", false));
+ getNode("link").removeAttribute("href");
+ await p;
+
+ info("add contenteditable");
+ // Expect editable change on non-input,
+ // and don't expect event on a native input.
+ p = waitForEvents({
+ expected: [focusableStateChange("div", true), editableStateChange("div", true)],
+ unexpected: [focusableStateChange("input"), editableStateChange("input")]
+ });
+ getNode("input").contentEditable = true;
+ getNode("div").contentEditable = true;
+ await p;
+
+ info("remove contenteditable");
+ // Expect editable change on non-input,
+ // and don't expect event on a native input.
+ p = waitForEvents({
+ expected: [focusableStateChange("div", false), editableStateChange("div", false)],
+ unexpected: [focusableStateChange("input"), editableStateChange("input")]
+ });
+ getNode("input").contentEditable = false;
+ getNode("div").contentEditable = false;
+ await p;
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <button id="button1"></button>
+ <button id="button2"></button>
+
+ <div id="div">Hello</div>
+
+ <a id="link" href="#">A link</a>
+
+ <input id="input" value="Hello">
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_fromUserInput.html b/accessible/tests/mochitest/events/test_fromUserInput.html
new file mode 100644
index 0000000000..b3617358cf
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_fromUserInput.html
@@ -0,0 +1,112 @@
+<html>
+
+<head>
+ <title>Testing of isFromUserInput in text events</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ /**
+ * Remove text data from HTML input.
+ */
+ function removeTextFromInput(aID, aStart, aEnd, aText, aFromUser) {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new textChangeChecker(aID, aStart, aEnd, aText, false, aFromUser),
+ ];
+
+ this.invoke = function removeTextFromInput_invoke() {
+ this.DOMNode.focus();
+ this.DOMNode.setSelectionRange(aStart, aEnd);
+
+ synthesizeKey("KEY_Delete");
+ };
+
+ this.getID = function removeTextFromInput_getID() {
+ return "Remove text from " + aStart + " to " + aEnd + " for " +
+ prettyName(aID);
+ };
+ }
+
+ /**
+ * Remove text data from text node.
+ */
+ function removeTextFromContentEditable(aID, aStart, aEnd, aText, aFromUser) {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new textChangeChecker(aID, aStart, aEnd, aText, false, aFromUser),
+ ];
+
+ this.invoke = function removeTextFromContentEditable_invoke() {
+ this.DOMNode.focus();
+ this.textNode = getNode(aID).firstChild;
+ var selection = window.getSelection();
+ var range = document.createRange();
+ range.setStart(this.textNode, aStart);
+ range.setEnd(this.textNode, aEnd);
+ selection.addRange(range);
+
+ synthesizeKey("KEY_Delete");
+ };
+
+ this.getID = function removeTextFromContentEditable_getID() {
+ return "Remove text from " + aStart + " to " + aEnd + " for " +
+ prettyName(aID);
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+
+ var gQueue = null;
+
+ function doTests() {
+ gQueue = new eventQueue();
+
+ // Focused editable text node
+ gQueue.push(new removeTextFromContentEditable("div", 0, 3, "hel", true));
+
+ // Focused editable HTML input
+ gQueue.push(new removeTextFromInput("input", 1, 2, "n", true));
+
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+
+ </script>
+</head>
+
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=686909"
+ title="isFromUserInput flag on accessible text change events not correct">
+ Mozilla Bug 686909
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <div id="eventdump"></div>
+
+ <div id="div" contentEditable="true">hello</div>
+ <input id="input" value="input">
+
+</body>
+
+</html>
diff --git a/accessible/tests/mochitest/events/test_label.xhtml b/accessible/tests/mochitest/events/test_label.xhtml
new file mode 100644
index 0000000000..5780629dc6
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_label.xhtml
@@ -0,0 +1,178 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Tests: accessible XUL label/description events">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ const kRecreated = 0;
+ const kTextRemoved = 1;
+ const kTextChanged = 2;
+
+ const kNoValue = 0;
+
+ /**
+ * Set/remove @value attribute.
+ */
+ function setValue(aID, aValue, aResult, aOldValue)
+ {
+ this.labelNode = getNode(aID);
+
+ this.eventSeq = [];
+
+ switch (aResult) {
+ case kRecreated:
+ this.eventSeq.push(new invokerChecker(EVENT_HIDE, this.labelNode));
+ this.eventSeq.push(new invokerChecker(EVENT_SHOW, this.labelNode));
+ break;
+ case kTextRemoved:
+ this.eventSeq.push(
+ new textChangeChecker(this.labelNode, 0, aOldValue.length,
+ aOldValue, false));
+ break;
+ case kTextChanged:
+ this.eventSeq.push(
+ new textChangeChecker(this.labelNode, 0, aOldValue.length,
+ aOldValue, false));
+ this.eventSeq.push(
+ new textChangeChecker(this.labelNode, 0, aValue.length,
+ aValue, true));
+ break;
+ }
+
+ this.invoke = function setValue_invoke()
+ {
+ if (aValue === kNoValue)
+ this.labelNode.removeAttribute("value");
+ else
+ this.labelNode.setAttribute("value", aValue);
+ }
+
+ this.finalCheck = function setValue_finalCheck()
+ {
+ let tree =
+ { LABEL: [] };
+
+ const expectChild = (() => {
+ if (aValue === kNoValue) {
+ return false;
+ }
+ if (aValue === "") {
+ return this.labelNode.nodeName == "label";
+ }
+ return true;
+ })();
+
+ if (expectChild) {
+ tree.LABEL.push({ STATICTEXT: [ ] });
+ }
+ testAccessibleTree(aID, tree);
+ }
+
+ this.getID = function setValue_getID()
+ {
+ return "set @value='" + aValue + "' for label " + prettyName(aID);
+ }
+ }
+
+ /**
+ * Change @crop attribute.
+ */
+ function setCrop(aID, aCropValue, aRemovedText, aInsertedText)
+ {
+ this.labelNode = getNode(aID);
+ this.width = this.labelNode.getBoundingClientRect().width;
+ this.charWidth = this.width / this.labelNode.value.length;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.labelNode),
+ new invokerChecker(EVENT_SHOW, this.labelNode),
+ ];
+
+ this.invoke = function setCrop_invoke()
+ {
+ if (!this.labelNode.hasAttribute("crop"))
+ this.labelNode.style.width = Math.floor(this.width - 2 * this.charWidth) + "px";
+
+ this.labelNode.setAttribute("crop", aCropValue);
+ }
+
+ this.getID = function setCrop_finalCheck()
+ {
+ return "set crop " + aCropValue;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new setValue("label", "shiroka strana", kRecreated));
+ gQueue.push(new setValue("label", "?<>!+_", kTextChanged, "shiroka strana"));
+ gQueue.push(new setValue("label", "", kRecreated));
+ gQueue.push(new setValue("label", kNoValue, kRecreated));
+
+ gQueue.push(new setValue("descr", "hello world", kRecreated));
+ gQueue.push(new setValue("descr", "si_ya", kTextChanged, "hello world"));
+ gQueue.push(new setValue("descr", "", kTextRemoved, "si_ya"));
+ gQueue.push(new setValue("descr", kNoValue, kRecreated));
+
+ gQueue.push(new setCrop("croplabel", "center"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=396166"
+ title="xul:label@value accessible should implement nsIAccessibleText">
+ Bug 396166
+ </a>
+ <br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <label id="label"/>
+ <description id="descr"/>
+
+ <hbox>
+ <label id="croplabel" value="valuetocro"
+ style="font-family: monospace;"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/events/test_menu.xhtml b/accessible/tests/mochitest/events/test_menu.xhtml
new file mode 100644
index 0000000000..271831ba83
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_menu.xhtml
@@ -0,0 +1,200 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible menu events testing for XUL menu">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script><![CDATA[
+ function openFileMenu()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENU_START, "menubar"),
+ new invokerChecker("popupshown", "menupopup-file")
+ // new invokerChecker(EVENT_FOCUS, getNode("menuitem-newtab")) intermitent failure
+ ];
+
+ this.invoke = function openFileMenu_invoke()
+ {
+ synthesizeKey("F", {altKey: true, shiftKey: true});
+ }
+
+ this.getID = function openFileMenu_getID()
+ {
+ return "open file menu by alt+F press";
+ }
+ }
+
+ function openEditMenu()
+ {
+ this.eventSeq = [
+ new invokerChecker("popuphidden", "menupopup-file"),
+ new invokerChecker("popupshown", "menupopup-edit"),
+ // new invokerChecker(EVENT_FOCUS, getNode("menuitem-undo")) intermitent failure
+ ];
+
+ this.invoke = function openEditMenu_invoke()
+ {
+ synthesizeKey("KEY_ArrowRight");
+ }
+
+ this.getID = function openEditMenu_getID()
+ {
+ return "open edit menu by lef arrow press";
+ }
+ }
+
+ function closeEditMenu()
+ {
+ this.eventSeq = [
+ //new invokerChecker(EVENT_FOCUS, document), intermitent failure
+ new invokerChecker("popuphidden", "menupopup-edit"),
+ ];
+
+ this.invoke = function closeEditMenu_invoke()
+ {
+ synthesizeKey("KEY_Escape");
+ }
+
+ this.getID = function closeEditMenu_getID()
+ {
+ return "close edit menu";
+ }
+ }
+
+ function focusFileMenu()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENU_START, getNode("menubar"))
+ // new invokerChecker(EVENT_FOCUS, getNode("menuitem-file")) //intermitent failure
+ ];
+
+ this.invoke = function focusFileMenu_invoke()
+ {
+ synthesizeKey("KEY_Alt");
+ }
+
+ this.getID = function focusFileMenu_getID()
+ {
+ return "activate menubar, focus file menu (atl press)";
+ }
+ }
+
+ function focusEditMenu()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getNode("menuitem-edit"))
+ ];
+
+ this.invoke = function focusEditMenu_invoke()
+ {
+ synthesizeKey("KEY_ArrowRight");
+ }
+
+ this.getID = function focusEditMenu_getID()
+ {
+ return "focus edit menu";
+ }
+ }
+
+ function leaveMenubar()
+ {
+ this.eventSeq = [
+ //new invokerChecker(EVENT_FOCUS, document), intermitent failure
+ new invokerChecker(EVENT_MENU_END, "menubar")
+ ];
+
+ this.invoke = function leaveMenubar_invoke()
+ {
+ synthesizeKey("KEY_Escape");
+ }
+
+ this.getID = function leaveMenubar_getID()
+ {
+ return "leave menubar";
+ }
+ }
+
+ /**
+ * Do tests.
+ */
+
+ //gA11yEventDumpID = "eventdump";
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTests()
+ {
+ if (!WIN && !LINUX) {
+ todo(false, "Enable this test on other platforms.");
+ SimpleTest.finish();
+ return;
+ }
+
+ todo(false,
+ "Fix intermitent failures. Focus may randomly occur before or after menupopup events!");
+
+ gQueue = new eventQueue();
+
+ gQueue.push(new openFileMenu());
+ gQueue.push(new openEditMenu());
+ gQueue.push(new closeEditMenu());
+ gQueue.push(new leaveMenubar());
+
+ // Alt key is used to active menubar and focus menu item on Windows,
+ // other platforms requires setting a ui.key.menuAccessKeyFocuses
+ // preference.
+ if (WIN || LINUX) {
+ gQueue.push(new focusFileMenu());
+ gQueue.push(new focusEditMenu());
+ gQueue.push(new leaveMenubar());
+ }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ ]]></script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=615189"
+ title="Clean up FireAccessibleFocusEvent">
+ Mozilla Bug 615189
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menubar id="menubar">
+ <menu id="menuitem-file" label="File" accesskey="F">
+ <menupopup id="menupopup-file">
+ <menuitem id="menuitem-newtab" label="New Tab"/>
+ </menupopup>
+ </menu>
+ <menu id="menuitem-edit" label="Edit" accesskey="E">
+ <menupopup id="menupopup-edit">
+ <menuitem id="menuitem-undo" label="Undo"/>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <vbox id="eventdump" role="log"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_mutation.html b/accessible/tests/mochitest/events/test_mutation.html
new file mode 100644
index 0000000000..7ee876570b
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_mutation.html
@@ -0,0 +1,580 @@
+<html>
+
+<head>
+ <title>Accessible mutation events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ div.displayNone a { display:none; }
+ div.visibilityHidden a { visibility:hidden; }
+</style>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Invokers.
+ */
+ var kNoEvents = 0;
+
+ var kShowEvent = 1;
+ var kHideEvent = 2;
+ var kReorderEvent = 4;
+ var kShowEvents = kShowEvent | kReorderEvent;
+ var kHideEvents = kHideEvent | kReorderEvent;
+ var kHideAndShowEvents = kHideEvents | kShowEvent;
+
+ /**
+ * Base class to test mutation a11y events.
+ *
+ * @param aNodeOrID [in] node invoker's action is executed for
+ * @param aEventTypes [in] events to register (see constants above)
+ * @param aDoNotExpectEvents [in] boolean indicates if events are expected
+ */
+ function mutateA11yTree(aNodeOrID, aEventTypes, aDoNotExpectEvents) {
+ // Interface
+ this.DOMNode = getNode(aNodeOrID);
+ this.doNotExpectEvents = aDoNotExpectEvents;
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [];
+
+ /**
+ * Change default target (aNodeOrID) registered for the given event type.
+ */
+ this.setTarget = function mutateA11yTree_setTarget(aEventType, aTarget) {
+ var type = this.getA11yEventType(aEventType);
+ for (var idx = 0; idx < this.getEventSeq().length; idx++) {
+ if (this.getEventSeq()[idx].type == type) {
+ this.getEventSeq()[idx].target = aTarget;
+ return idx;
+ }
+ }
+ return -1;
+ };
+
+ /**
+ * Replace the default target currently registered for a given event type
+ * with the nodes in the targets array.
+ */
+ this.setTargets = function mutateA11yTree_setTargets(aEventType, aTargets) {
+ var targetIdx = this.setTarget(aEventType, aTargets[0]);
+
+ var type = this.getA11yEventType(aEventType);
+ for (var i = 1; i < aTargets.length; i++) {
+ let checker = new invokerChecker(type, aTargets[i]);
+ this.getEventSeq().splice(++targetIdx, 0, checker);
+ }
+ };
+
+ // Implementation
+ this.getA11yEventType = function mutateA11yTree_getA11yEventType(aEventType) {
+ if (aEventType == kReorderEvent)
+ return nsIAccessibleEvent.EVENT_REORDER;
+
+ if (aEventType == kHideEvent)
+ return nsIAccessibleEvent.EVENT_HIDE;
+
+ if (aEventType == kShowEvent)
+ return nsIAccessibleEvent.EVENT_SHOW;
+
+ return 0;
+ };
+
+ this.getEventSeq = function mutateA11yTree_getEventSeq() {
+ return this.doNotExpectEvents ? this.unexpectedEventSeq : this.eventSeq;
+ };
+
+ if (aEventTypes & kHideEvent) {
+ let checker = new invokerChecker(this.getA11yEventType(kHideEvent),
+ this.DOMNode);
+ this.getEventSeq().push(checker);
+ }
+
+ if (aEventTypes & kShowEvent) {
+ let checker = new invokerChecker(this.getA11yEventType(kShowEvent),
+ this.DOMNode);
+ this.getEventSeq().push(checker);
+ }
+
+ if (aEventTypes & kReorderEvent) {
+ let checker = new invokerChecker(this.getA11yEventType(kReorderEvent),
+ this.DOMNode.parentNode);
+ this.getEventSeq().push(checker);
+ }
+ }
+
+ /**
+ * Change CSS style for the given node.
+ */
+ function changeStyle(aNodeOrID, aProp, aValue, aEventTypes) {
+ this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false);
+
+ this.invoke = function changeStyle_invoke() {
+ this.DOMNode.style[aProp] = aValue;
+ };
+
+ this.getID = function changeStyle_getID() {
+ return aNodeOrID + " change style " + aProp + " on value " + aValue;
+ };
+ }
+
+ /**
+ * Change class name for the given node.
+ */
+ function changeClass(aParentNodeOrID, aNodeOrID, aClassName, aEventTypes) {
+ this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false);
+
+ this.invoke = function changeClass_invoke() {
+ this.parentDOMNode.className = aClassName;
+ };
+
+ this.getID = function changeClass_getID() {
+ return aNodeOrID + " change class " + aClassName;
+ };
+
+ this.parentDOMNode = getNode(aParentNodeOrID);
+ }
+
+ /**
+ * Clone the node and append it to its parent.
+ */
+ function cloneAndAppendToDOM(aNodeOrID, aEventTypes,
+ aTargetsFunc, aReorderTargetFunc) {
+ var eventTypes = aEventTypes || kShowEvents;
+ var doNotExpectEvents = (aEventTypes == kNoEvents);
+
+ this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes,
+ doNotExpectEvents);
+
+ this.invoke = function cloneAndAppendToDOM_invoke() {
+ var newElm = this.DOMNode.cloneNode(true);
+ newElm.removeAttribute("id");
+
+ var targets = aTargetsFunc ?
+ aTargetsFunc(newElm) : [newElm];
+ this.setTargets(kShowEvent, targets);
+
+ if (aReorderTargetFunc) {
+ var reorderTarget = aReorderTargetFunc(this.DOMNode);
+ this.setTarget(kReorderEvent, reorderTarget);
+ }
+
+ this.DOMNode.parentNode.appendChild(newElm);
+ };
+
+ this.getID = function cloneAndAppendToDOM_getID() {
+ return aNodeOrID + " clone and append to DOM.";
+ };
+ }
+
+ /**
+ * Removes the node from DOM.
+ */
+ function removeFromDOM(aNodeOrID, aEventTypes,
+ aTargetsFunc, aReorderTargetFunc) {
+ var eventTypes = aEventTypes || kHideEvents;
+ var doNotExpectEvents = (aEventTypes == kNoEvents);
+
+ this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes,
+ doNotExpectEvents);
+
+ this.invoke = function removeFromDOM_invoke() {
+ this.DOMNode.remove();
+ };
+
+ this.getID = function removeFromDOM_getID() {
+ return prettyName(aNodeOrID) + " remove from DOM.";
+ };
+
+ if (aTargetsFunc && (eventTypes & kHideEvent))
+ this.setTargets(kHideEvent, aTargetsFunc(this.DOMNode));
+
+ if (aReorderTargetFunc && (eventTypes & kReorderEvent))
+ this.setTarget(kReorderEvent, aReorderTargetFunc(this.DOMNode));
+ }
+
+ /**
+ * Clone the node and replace the original node by cloned one.
+ */
+ function cloneAndReplaceInDOM(aNodeOrID) {
+ this.__proto__ = new mutateA11yTree(aNodeOrID, kHideAndShowEvents,
+ false);
+
+ this.invoke = function cloneAndReplaceInDOM_invoke() {
+ this.DOMNode.parentNode.replaceChild(this.newElm, this.DOMNode);
+ };
+
+ this.getID = function cloneAndReplaceInDOM_getID() {
+ return aNodeOrID + " clone and replace in DOM.";
+ };
+
+ this.newElm = this.DOMNode.cloneNode(true);
+ this.newElm.removeAttribute("id");
+ this.setTarget(kShowEvent, this.newElm);
+ }
+
+ /**
+ * Trigger content insertion (flush layout), removal and insertion of
+ * the same element for the same parent.
+ */
+ function test1(aContainerID) {
+ this.divNode = document.createElement("div");
+ this.divNode.setAttribute("id", "div-test1");
+ this.containerNode = getNode(aContainerID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.divNode),
+ new invokerChecker(EVENT_REORDER, this.containerNode),
+ ];
+
+ this.invoke = function test1_invoke() {
+ this.containerNode.appendChild(this.divNode);
+ getComputedStyle(this.divNode, "").color;
+ this.containerNode.removeChild(this.divNode);
+ this.containerNode.appendChild(this.divNode);
+ };
+
+ this.getID = function test1_getID() {
+ return "fuzzy test #1: content insertion (flush layout), removal and" +
+ "reinsertion";
+ };
+ }
+
+ /**
+ * Trigger content insertion (flush layout), removal and insertion of
+ * the same element for the different parents.
+ */
+ function test2(aContainerID, aTmpContainerID) {
+ this.divNode = document.createElement("div");
+ this.divNode.setAttribute("id", "div-test2");
+ this.containerNode = getNode(aContainerID);
+ this.tmpContainerNode = getNode(aTmpContainerID);
+ this.container = getAccessible(this.containerNode);
+ this.tmpContainer = getAccessible(this.tmpContainerNode);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.divNode),
+ new invokerChecker(EVENT_REORDER, this.containerNode),
+ ];
+
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_REORDER, this.tmpContainerNode),
+ ];
+
+ this.invoke = function test2_invoke() {
+ this.tmpContainerNode.appendChild(this.divNode);
+ getComputedStyle(this.divNode, "").color;
+ this.tmpContainerNode.removeChild(this.divNode);
+ this.containerNode.appendChild(this.divNode);
+ };
+
+ this.getID = function test2_getID() {
+ return "fuzzy test #2: content insertion (flush layout), removal and" +
+ "reinsertion under another container";
+ };
+ }
+
+ /**
+ * Content insertion (flush layout) and then removal (nothing was changed).
+ */
+ function test3(aContainerID) {
+ this.divNode = document.createElement("div");
+ this.divNode.setAttribute("id", "div-test3");
+ this.containerNode = getNode(aContainerID);
+
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_SHOW, this.divNode),
+ new invokerChecker(EVENT_HIDE, this.divNode),
+ new invokerChecker(EVENT_REORDER, this.containerNode),
+ ];
+
+ this.invoke = function test3_invoke() {
+ this.containerNode.appendChild(this.divNode);
+ getComputedStyle(this.divNode, "").color;
+ this.containerNode.removeChild(this.divNode);
+ };
+
+ this.getID = function test3_getID() {
+ return "fuzzy test #3: content insertion (flush layout) and removal";
+ };
+ }
+
+ function insertReferredElm(aContainerID) {
+ this.containerNode = getNode(aContainerID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.firstChild; }, this.containerNode),
+ new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.lastChild; }, this.containerNode),
+ new invokerChecker(EVENT_REORDER, this.containerNode),
+ ];
+
+ this.invoke = function insertReferredElm_invoke() {
+ let span = document.createElement("span");
+ span.setAttribute("id", "insertReferredElms_span");
+ let input = document.createElement("input");
+ input.setAttribute("aria-labelledby", "insertReferredElms_span");
+ this.containerNode.appendChild(span);
+ this.containerNode.appendChild(input);
+ };
+
+ this.getID = function insertReferredElm_getID() {
+ return "insert inaccessible element and then insert referring element to make it accessible";
+ };
+ }
+
+ function showHiddenParentOfVisibleChild() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("c4_child")),
+ new invokerChecker(EVENT_SHOW, getNode("c4_middle")),
+ new invokerChecker(EVENT_REORDER, getNode("c4")),
+ ];
+
+ this.invoke = function showHiddenParentOfVisibleChild_invoke() {
+ getNode("c4_middle").style.visibility = "visible";
+ };
+
+ this.getID = function showHiddenParentOfVisibleChild_getID() {
+ return "show hidden parent of visible child";
+ };
+ }
+
+ function hideNDestroyDoc() {
+ this.txt = null;
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, () => { return this.txt; }),
+ ];
+
+ this.invoke = function hideNDestroyDoc_invoke() {
+ this.txt = getAccessible("c5").firstChild.firstChild;
+ this.txt.DOMNode.remove();
+ };
+
+ this.check = function hideNDestroyDoc_check() {
+ getNode("c5").remove();
+ };
+
+ this.getID = function hideNDestroyDoc_getID() {
+ return "remove text node and destroy a document on hide event";
+ };
+ }
+
+ function hideHideNDestroyDoc() {
+ this.target = null;
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, () => { return this.target; }),
+ ];
+
+ this.invoke = function hideHideNDestroyDoc_invoke() {
+ var doc = getAccessible("c6").firstChild;
+ var l1 = doc.firstChild;
+ this.target = l1.firstChild;
+ var l2 = doc.lastChild;
+ l1.DOMNode.firstChild.remove();
+ l2.DOMNode.firstChild.remove();
+ };
+
+ this.check = function hideHideNDestroyDoc_check() {
+ getNode("c6").remove();
+ };
+
+ this.getID = function hideHideNDestroyDoc_getID() {
+ return "remove text nodes (2 events in the queue) and destroy a document on first hide event";
+ };
+ }
+
+ /**
+ * Target getters.
+ */
+ function getFirstChild(aNode) {
+ return [aNode.firstChild];
+ }
+ function getLastChild(aNode) {
+ return [aNode.lastChild];
+ }
+
+ function getNEnsureFirstChild(aNode) {
+ var node = aNode.firstChild;
+ getAccessible(node);
+ return [node];
+ }
+
+ function getNEnsureChildren(aNode) {
+ var children = [];
+ var node = aNode.firstChild;
+ do {
+ children.push(node);
+ getAccessible(node);
+ node = node.nextSibling;
+ } while (node);
+
+ return children;
+ }
+
+ function getParent(aNode) {
+ return aNode.parentNode;
+ }
+
+ // gA11yEventDumpToConsole = true; // debug stuff
+ // enableLogging("events,verbose");
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ function doTests() {
+ gQueue = new eventQueue();
+
+ // Show/hide events by changing of display style of accessible DOM node
+ // from 'inline' to 'none', 'none' to 'inline'.
+ let id = "link1";
+ getAccessible(id); // ensure accessible is created
+ gQueue.push(new changeStyle(id, "display", "none", kHideEvents));
+ gQueue.push(new changeStyle(id, "display", "inline", kShowEvents));
+
+ // Show/hide events by changing of visibility style of accessible DOM node
+ // from 'visible' to 'hidden', 'hidden' to 'visible'.
+ id = "link2";
+ getAccessible(id);
+ gQueue.push(new changeStyle(id, "visibility", "hidden", kHideEvents));
+ gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents));
+
+ // Show/hide events by changing of visibility style of accessible DOM node
+ // from 'collapse' to 'visible', 'visible' to 'collapse'.
+ id = "link4";
+ gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents));
+ gQueue.push(new changeStyle(id, "visibility", "collapse", kHideEvents));
+
+ // Show/hide events by adding new accessible DOM node and removing old one.
+ id = "link5";
+ gQueue.push(new cloneAndAppendToDOM(id));
+ gQueue.push(new removeFromDOM(id));
+
+ // No show/hide events by adding new not accessible DOM node and removing
+ // old one, no reorder event for their parent.
+ id = "child1";
+ gQueue.push(new cloneAndAppendToDOM(id, kNoEvents));
+ gQueue.push(new removeFromDOM(id, kNoEvents));
+
+ // Show/hide events by adding new accessible DOM node and removing
+ // old one, there is reorder event for their parent.
+ id = "child2";
+ gQueue.push(new cloneAndAppendToDOM(id));
+ gQueue.push(new removeFromDOM(id));
+
+ // Show/hide events by adding new DOM node containing accessible DOM and
+ // removing old one, there is reorder event for their parent.
+ id = "child3";
+ gQueue.push(new cloneAndAppendToDOM(id, kShowEvents, getFirstChild,
+ getParent));
+
+ // Hide event for accessible child of unaccessible removed DOM node and
+ // reorder event for its parent.
+ gQueue.push(new removeFromDOM(id, kHideEvents,
+ getNEnsureFirstChild, getParent));
+
+ // Hide events for accessible children of unaccessible removed DOM node
+ // and reorder event for its parent.
+ gQueue.push(new removeFromDOM("child4", kHideEvents,
+ getNEnsureChildren, getParent));
+
+ // Show/hide events by creating new accessible DOM node and replacing
+ // old one.
+ getAccessible("link6"); // ensure accessible is created
+ gQueue.push(new cloneAndReplaceInDOM("link6"));
+
+ // Show/hide events by changing class name on the parent node.
+ gQueue.push(new changeClass("container2", "link7", "", kShowEvents));
+ gQueue.push(new changeClass("container2", "link7", "displayNone",
+ kHideEvents));
+
+ gQueue.push(new changeClass("container3", "link8", "", kShowEvents));
+ gQueue.push(new changeClass("container3", "link8", "visibilityHidden",
+ kHideEvents));
+
+ gQueue.push(new test1("testContainer"));
+ gQueue.push(new test2("testContainer", "testContainer2"));
+ gQueue.push(new test2("testContainer", "testNestedContainer"));
+ gQueue.push(new test3("testContainer"));
+ gQueue.push(new insertReferredElm("testContainer3"));
+ gQueue.push(new showHiddenParentOfVisibleChild());
+
+ gQueue.push(new hideNDestroyDoc());
+ gQueue.push(new hideHideNDestroyDoc());
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=469985"
+ title=" turn the test from bug 354745 into mochitest">
+ Mozilla Bug 469985</a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=472662"
+ title="no reorder event when html:link display property is changed from 'none' to 'inline'">
+ Mozilla Bug 472662</a>
+ <a target="_blank"
+ title="Rework accessible tree update code"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
+ Mozilla Bug 570275</a>
+ <a target="_blank"
+ title="Develop a way to handle visibility style"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=606125">
+ Mozilla Bug 606125</a>
+ <a target="_blank"
+ title="Update accessible tree on content insertion after layout"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=498015">
+ Mozilla Bug 498015</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <div id="testContainer">
+ <a id="link1" href="http://www.google.com">Link #1</a>
+ <a id="link2" href="http://www.google.com">Link #2</a>
+ <a id="link3" href="http://www.google.com">Link #3</a>
+ <a id="link4" href="http://www.google.com" style="visibility:collapse">Link #4</a>
+ <a id="link5" href="http://www.google.com">Link #5</a>
+
+ <div id="container" role="list">
+ <span id="child1"></span>
+ <span id="child2" role="listitem"></span>
+ <span id="child3"><span role="listitem"></span></span>
+ <span id="child4"><span id="child4_1" role="listitem"></span><span id="child4_2" role="listitem"></span></span>
+ </div>
+
+ <a id="link6" href="http://www.google.com">Link #6</a>
+
+ <div id="container2" class="displayNone"><a id="link7">Link #7</a></div>
+ <div id="container3" class="visibilityHidden"><a id="link8">Link #8</a></div>
+ <div id="testNestedContainer"></div>
+ </div>
+ <div id="testContainer2"></div>
+ <div id="testContainer3"></div>
+
+ <div id="c4">
+ <div style="visibility:hidden" id="c4_middle">
+ <div style="visibility:visible" id="c4_child"></div>
+ </div>
+
+ <iframe id="c5" src="data:text/html,hey"></iframe>
+ <iframe id="c6" src="data:text/html,<label>l</label><label>l</label>"></iframe>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_namechange.html b/accessible/tests/mochitest/events/test_namechange.html
new file mode 100644
index 0000000000..840e2dfb4f
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_namechange.html
@@ -0,0 +1,185 @@
+<html>
+
+<head>
+ <title>Accessible name change event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ let PromEvents = {};
+ Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js",
+ PromEvents);
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function setAttr(aID, aAttr, aValue, aChecker) {
+ this.eventSeq = [ aChecker ];
+ this.invoke = function setAttr_invoke() {
+ getNode(aID).setAttribute(aAttr, aValue);
+ };
+
+ this.getID = function setAttr_getID() {
+ return "set attr '" + aAttr + "', value '" + aValue + "'";
+ };
+ }
+
+ /**
+ * No name change on an accessible, because the accessible is recreated.
+ */
+ function setAttr_recreate(aID, aAttr, aValue) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getAccessible(aID)),
+ new invokerChecker(EVENT_SHOW, aID),
+ ];
+ this.invoke = function setAttr_recreate_invoke() {
+ todo(false, "No accessible recreation should happen, just name change event");
+ getNode(aID).setAttribute(aAttr, aValue);
+ };
+
+ this.getID = function setAttr_recreate_getID() {
+ return "set attr '" + aAttr + "', value '" + aValue + "'";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ // gA11yEventDumpToConsole = true; // debuggin
+
+ var gQueue = null;
+ async function doTests() {
+ gQueue = new eventQueue();
+ // Later tests use await.
+ let queueFinished = new Promise(resolve => {
+ gQueue.onFinish = function() {
+ resolve();
+ return DO_NOT_FINISH_TEST;
+ };
+ });
+
+ gQueue.push(new setAttr("tst1", "aria-label", "hi",
+ new invokerChecker(EVENT_NAME_CHANGE, "tst1")));
+ gQueue.push(new setAttr("tst1", "alt", "alt",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1")));
+ gQueue.push(new setAttr("tst1", "title", "title",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1")));
+ gQueue.push(new setAttr("tst1", "aria-labelledby", "display",
+ new invokerChecker(EVENT_NAME_CHANGE, "tst1")));
+
+ gQueue.push(new setAttr("tst2", "aria-labelledby", "display",
+ new invokerChecker(EVENT_NAME_CHANGE, "tst2")));
+ gQueue.push(new setAttr("tst2", "alt", "alt",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
+ gQueue.push(new setAttr("tst2", "title", "title",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
+ gQueue.push(new setAttr("tst2", "aria-label", "hi",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
+
+ // When `alt` attribute is added or removed from a broken img,
+ // the accessible is recreated.
+ gQueue.push(new setAttr_recreate("tst3", "alt", "one"));
+ // When an `alt` attribute is changed, there is a name change event.
+ gQueue.push(new setAttr("tst3", "alt", "two",
+ new invokerChecker(EVENT_NAME_CHANGE, "tst3")));
+ gQueue.push(new setAttr("tst3", "title", "title",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst3")));
+
+ gQueue.push(new setAttr("tst4", "title", "title",
+ new invokerChecker(EVENT_NAME_CHANGE, "tst4")));
+
+ gQueue.invoke();
+ await queueFinished;
+ // Tests beyond this point use await rather than eventQueue.
+
+ const labelledBy = getNode("labelledBy");
+ const label = getNode("label");
+ let nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, labelledBy);
+ info("Changing text of aria-labelledby target");
+ label.textContent = "l2";
+ await nameChanged;
+ nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, labelledBy);
+ info("Adding node to aria-labelledby target");
+ label.innerHTML = '<p id="labelChild">l3</p>';
+ await nameChanged;
+ nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, labelledBy);
+ info("Changing text of aria-labelledby target's child");
+ getNode("labelChild").textContent = "l4";
+ await nameChanged;
+
+ const lateLabelledBy = getNode("lateLabelledBy");
+ nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, lateLabelledBy);
+ info("Setting aria-labelledby");
+ lateLabelledBy.setAttribute("aria-labelledby", "lateLabel");
+ await nameChanged;
+ nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, lateLabelledBy);
+ info("Changing text of late aria-labelledby target's child");
+ getNode("lateLabelChild").textContent = "l2";
+ await nameChanged;
+
+ nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, "listitem");
+ info("Changing textContent of listitem child");
+ // Changing textContent replaces the text leaf with a new one.
+ getNode("listitem").textContent = "world";
+ await nameChanged;
+
+ nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, "button");
+ info("Changing text of button's text leaf");
+ // Changing the text node's data changes the text without replacing the
+ // leaf.
+ getNode("button").firstChild.data = "after";
+ await nameChanged;
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=991969"
+ title="Event not fired when description changes">
+ Bug 991969
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <img id="tst1" alt="initial" src="../moz.png">
+ <img id="tst2" src="../moz.png">
+ <img id="tst3">
+ <img id="tst4" src="../moz.png">
+
+ <div id="labelledBy" aria-labelledby="label"></div>
+ <div id="label">l1</div>
+
+ <div id="lateLabelledBy"></div>
+ <div id="lateLabel"><p id="lateLabelChild">l1</p></div>
+
+ <ul><li id="listitem">hello</li></ul>
+
+ <button id="button">before</button>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_namechange.xhtml b/accessible/tests/mochitest/events/test_namechange.xhtml
new file mode 100644
index 0000000000..a6dd8cb218
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_namechange.xhtml
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/chrome-harness.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ /**
+ * Check name changed a11y event.
+ */
+ function nameChangeChecker(aMsg, aID)
+ {
+ this.type = EVENT_NAME_CHANGE;
+
+ function targetGetter()
+ {
+ return getAccessible(aID);
+ }
+ Object.defineProperty(this, "target", { get: targetGetter });
+
+ this.getID = function getID()
+ {
+ return aMsg + " name changed";
+ }
+ }
+
+ function changeRichListItemChild()
+ {
+ this.invoke = function changeRichListItemChild_invoke()
+ {
+ getNode('childcontent').setAttribute('value', 'Changed.');
+ }
+
+ this.eventSeq =
+ [
+ new nameChangeChecker("changeRichListItemChild: ", "listitem")
+ ];
+
+ this.getID = function changeRichListItemChild_getID()
+ {
+ return "changeRichListItemChild";
+ }
+ }
+
+ function doTest()
+ {
+ var queue = new eventQueue();
+ queue.push(new changeRichListItemChild());
+ queue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=986054"
+ title="Propagate name change events">
+ Mozilla Bug 986054
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <richlistbox>
+ <richlistitem id="listitem">
+ <description id="childcontent" value="This will be changed."/>
+ </richlistitem>
+ </richlistbox>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_scroll.xhtml b/accessible/tests/mochitest/events/test_scroll.xhtml
new file mode 100644
index 0000000000..d3cc2a7bda
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_scroll.xhtml
@@ -0,0 +1,107 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/chrome-harness.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../promisified-events.js" />
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+
+ function matchesAnchorJumpInTabDocument(aTabIdx) {
+ return evt => {
+ let tabDoc = aTabIdx ? tabDocumentAt(aTabIdx) : currentTabDocument();
+ let anchorAcc = getAccessible(tabDoc.querySelector("a[name='link1']"));
+ return anchorAcc == evt.accessible;
+ }
+ }
+
+ function matchesTabDocumentAt(aTabIdx) {
+ return evt => {
+ let tabDoc = aTabIdx ? tabDocumentAt(aTabIdx) : currentTabDocument();
+ return getAccessible(tabDoc) == evt.accessible;
+ }
+ }
+
+ async function doTest() {
+ const kURL = "http://mochi.test:8888/a11y/accessible/tests/mochitest/events/scroll.html#link1";
+ let evtProm = waitForEvents([
+ [EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument],
+ [EVENT_SCROLLING_START, matchesAnchorJumpInTabDocument()],
+ ], "Load foreground tab then scroll to anchor");
+
+ tabBrowser().loadURI(Services.io.newURI(kURL), {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ // Flush layout, so as to guarantee that the a11y tree is constructed.
+ browserDocument().documentElement.getBoundingClientRect();
+ await evtProm;
+
+
+ evtProm = waitForEvents({
+ expected: [[EVENT_DOCUMENT_LOAD_COMPLETE, matchesTabDocumentAt(1)]],
+ unexpected: [[EVENT_SCROLLING_START, matchesAnchorJumpInTabDocument(1)]],
+ }, "Load background tab, don't dispatch scroll to anchor event");
+
+ tabBrowser().addTab(kURL, {
+ referrerURI: null,
+ charset: "",
+ postData: null,
+ inBackground: true,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ // Flush layout, so as to guarantee that the a11y tree is constructed.
+ browserDocument().documentElement.getBoundingClientRect();
+ await evtProm;
+
+ evtProm = waitForEvent(EVENT_SCROLLING_START,
+ matchesAnchorJumpInTabDocument(),
+ "Scroll to anchor event dispatched when switching to loaded doc.");
+ tabBrowser().selectTabAtIndex(1);
+ await evtProm;
+
+ closeBrowserWindow();
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTest);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=691734"
+ title="Make sure scrolling start event is fired when document receive focus">
+ Mozilla Bug 691734
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="eventdump"></vbox>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_scroll_caret.xhtml b/accessible/tests/mochitest/events/test_scroll_caret.xhtml
new file mode 100644
index 0000000000..f0f0fccfb2
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_scroll_caret.xhtml
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/chrome-harness.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+
+ function getAnchorJumpInTabDocument(aTabIdx)
+ {
+ var tabDoc = aTabIdx ? tabDocumentAt(aTabIdx) : currentTabDocument();
+ return tabDoc.querySelector("h1[id='heading_1']");
+ }
+
+ function loadTab(aURL)
+ {
+ this.eventSeq = [
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
+ new asyncCaretMoveChecker(0, getAnchorJumpInTabDocument)
+ ];
+
+ this.invoke = function loadTab_invoke()
+ {
+ tabBrowser().loadURI(Services.io.newURI(aURL), {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ }
+
+ this.getID = function loadTab_getID()
+ {
+ return "load tab: " + aURL;
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ var url = "http://mochi.test:8888/a11y/accessible/tests/mochitest/events/scroll.html#heading_1";
+ gQueue.push(new loadTab(url));
+ gQueue.onFinish = function() { closeBrowserWindow(); }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTest);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1056459"
+ title="Make sure caret move event is fired when document receive focus">
+ Mozilla Bug 1056459
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="eventdump"></vbox>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_selection.html b/accessible/tests/mochitest/events/test_selection.html
new file mode 100644
index 0000000000..a749dd9c4c
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_selection.html
@@ -0,0 +1,109 @@
+<html>
+
+<head>
+ <title>Accessible selection event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ // gA11yEventDumpToConsole = true; // debuggin
+
+ var gQueue = null;
+ function doTests() {
+ gQueue = new eventQueue();
+
+
+ // closed combobox
+ gQueue.push(new synthFocus("combobox"));
+ gQueue.push(new synthDownKey("cb1_item1",
+ selChangeSeq("cb1_item1", "cb1_item2")));
+
+ // listbox
+ gQueue.push(new synthClick("lb1_item1",
+ new invokerChecker(EVENT_SELECTION, "lb1_item1")));
+ gQueue.push(new synthDownKey("lb1_item1",
+ selChangeSeq("lb1_item1", "lb1_item2")));
+
+ // multiselectable listbox
+ gQueue.push(new synthClick("lb2_item1",
+ selChangeSeq(null, "lb2_item1")));
+ gQueue.push(new synthDownKey("lb2_item1",
+ selAddSeq("lb2_item2"),
+ { shiftKey: true }));
+ gQueue.push(new synthUpKey("lb2_item2",
+ selRemoveSeq("lb2_item2"),
+ { shiftKey: true }));
+ gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true },
+ selRemoveSeq("lb2_item1")));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302"
+ title="Incorrect selection events in HTML, XUL and ARIA">
+ Bug 414302
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=810268"
+ title="There's no way to know unselected item when selection in single selection was changed">
+ Bug 810268
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="combobox">
+ <option id="cb1_item1" value="mushrooms">mushrooms
+ <option id="cb1_item2" value="greenpeppers">green peppers
+ <option id="cb1_item3" value="onions" id="onions">onions
+ <option id="cb1_item4" value="tomatoes">tomatoes
+ <option id="cb1_item5" value="olives">olives
+ </select>
+
+ <select id="listbox" size=5>
+ <option id="lb1_item1" value="mushrooms">mushrooms
+ <option id="lb1_item2" value="greenpeppers">green peppers
+ <option id="lb1_item3" value="onions" id="onions">onions
+ <option id="lb1_item4" value="tomatoes">tomatoes
+ <option id="lb1_item5" value="olives">olives
+ </select>
+
+ <p>Pizza</p>
+ <select id="listbox2" multiple size=5>
+ <option id="lb2_item1" value="mushrooms">mushrooms
+ <option id="lb2_item2" value="greenpeppers">green peppers
+ <option id="lb2_item3" value="onions" id="onions">onions
+ <option id="lb2_item4" value="tomatoes">tomatoes
+ <option id="lb2_item5" value="olives">olives
+ </select>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_selection.xhtml b/accessible/tests/mochitest/events/test_selection.xhtml
new file mode 100644
index 0000000000..9c34ddf286
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_selection.xhtml
@@ -0,0 +1,254 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Selection event tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ function advanceTab(aTabsID, aDirection, aNextTabID)
+ {
+ var eventSeq1 = [
+ new invokerChecker(EVENT_SELECTION, aNextTabID)
+ ]
+ defineScenario(this, eventSeq1);
+
+ var eventSeq2 = [
+ new invokerChecker(EVENT_HIDE, getAccessible(aNextTabID)),
+ new invokerChecker(EVENT_SHOW, aNextTabID)
+ ];
+ defineScenario(this, eventSeq2);
+
+ this.invoke = function advanceTab_invoke()
+ {
+ todo(false, "No accessible recreation should happen, just selection event");
+ getNode(aTabsID).advanceSelectedTab(aDirection, true);
+ }
+
+ this.getID = function synthFocus_getID()
+ {
+ return "advanceTab on " + prettyName(aTabsID) + " to " + prettyName(aNextTabID);
+ }
+ }
+
+ function select4FirstItems(aID)
+ {
+ this.listboxNode = getNode(aID);
+ this.eventSeq = [
+ new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0)),
+ new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(1)),
+ new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(2)),
+ new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(3))
+ ];
+
+ this.invoke = function select4FirstItems_invoke()
+ {
+ synthesizeKey("VK_DOWN", { shiftKey: true }); // selects two items
+ synthesizeKey("VK_DOWN", { shiftKey: true });
+ synthesizeKey("VK_DOWN", { shiftKey: true });
+ }
+
+ this.getID = function select4FirstItems_getID()
+ {
+ return "select 4 first items for " + prettyName(aID);
+ }
+ }
+
+ function unselect4FirstItems(aID)
+ {
+ this.listboxNode = getNode(aID);
+ this.eventSeq = [
+ new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(3)),
+ new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(2)),
+ new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(1)),
+ new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0))
+ ];
+
+ this.invoke = function unselect4FirstItems_invoke()
+ {
+ synthesizeKey("VK_UP", { shiftKey: true });
+ synthesizeKey("VK_UP", { shiftKey: true });
+ synthesizeKey("VK_UP", { shiftKey: true });
+ synthesizeKey(" ", { ctrlKey: true }); // unselect first item
+ }
+
+ this.getID = function unselect4FirstItems_getID()
+ {
+ return "unselect 4 first items for " + prettyName(aID);
+ }
+ }
+
+ function selectAllItems(aID)
+ {
+ this.listboxNode = getNode(aID);
+ this.eventSeq = [
+ new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode))
+ ];
+
+ this.invoke = function selectAllItems_invoke()
+ {
+ synthesizeKey("VK_END", { shiftKey: true });
+ }
+
+ this.getID = function selectAllItems_getID()
+ {
+ return "select all items for " + prettyName(aID);
+ }
+ }
+
+ function unselectAllItemsButFirst(aID)
+ {
+ this.listboxNode = getNode(aID);
+ this.eventSeq = [
+ new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode))
+ ];
+
+ this.invoke = function unselectAllItemsButFirst_invoke()
+ {
+ synthesizeKey("VK_HOME", { shiftKey: true });
+ }
+
+ this.getID = function unselectAllItemsButFirst_getID()
+ {
+ return "unselect all items for " + prettyName(aID);
+ }
+ }
+
+ function unselectSelectItem(aID)
+ {
+ this.listboxNode = getNode(aID);
+ this.eventSeq = [
+ new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0)),
+ new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0))
+ ];
+
+ this.invoke = function unselectSelectItem_invoke()
+ {
+ synthesizeKey(" ", { ctrlKey: true }); // select item
+ synthesizeKey(" ", { ctrlKey: true }); // unselect item
+ }
+
+ this.getID = function unselectSelectItem_getID()
+ {
+ return "unselect and then select first item for " + prettyName(aID);
+ }
+ }
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ //enableLogging("events");
+ //gA11yEventDumpToConsole = true; // debuggin
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ //////////////////////////////////////////////////////////////////////////
+ // tabbox
+ gQueue.push(new advanceTab("tabs", 1, "tab3"));
+
+ //////////////////////////////////////////////////////////////////////////
+ // single selection listbox, the first item is selected by default
+
+ gQueue.push(new synthClick("lb1_item2",
+ new invokerChecker(EVENT_SELECTION, "lb1_item2")));
+ gQueue.push(new synthUpKey("lb1_item2",
+ new invokerChecker(EVENT_SELECTION, "lb1_item1")));
+ gQueue.push(new synthDownKey("lb1_item1",
+ new invokerChecker(EVENT_SELECTION, "lb1_item2")));
+
+ //////////////////////////////////////////////////////////////////////////
+ // multiselectable listbox
+ gQueue.push(new synthClick("lb2_item1",
+ new invokerChecker(EVENT_SELECTION, "lb2_item1")));
+ gQueue.push(new synthDownKey("lb2_item1",
+ new invokerChecker(EVENT_SELECTION_ADD, "lb2_item2"),
+ { shiftKey: true }));
+ gQueue.push(new synthUpKey("lb2_item2",
+ new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item2"),
+ { shiftKey: true }));
+ gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true },
+ new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item1")));
+
+ //////////////////////////////////////////////////////////////////////////
+ // selection event coalescence
+
+ // fire 4 selection_add events
+ gQueue.push(new select4FirstItems("listbox2"));
+ // fire 4 selection_remove events
+ gQueue.push(new unselect4FirstItems("listbox2"));
+ // fire selection_within event
+ gQueue.push(new selectAllItems("listbox2"));
+ // fire selection_within event
+ gQueue.push(new unselectAllItemsButFirst("listbox2"));
+ // fire selection_remove/add events
+ gQueue.push(new unselectSelectItem("listbox2"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302"
+ title="Incorrect selection events in HTML, XUL and ARIA">
+ Mozilla Bug 414302
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <tabbox id="tabbox" selectedIndex="1">
+ <tabs id="tabs">
+ <tab id="tab1" label="tab1"/>
+ <tab id="tab2" label="tab2"/>
+ <tab id="tab3" label="tab3"/>
+ <tab id="tab4" label="tab4"/>
+ </tabs>
+ <tabpanels>
+ <tabpanel><!-- tabpanel First elements go here --></tabpanel>
+ <tabpanel><button id="b1" label="b1"/></tabpanel>
+ <tabpanel><button id="b2" label="b2"/></tabpanel>
+ <tabpanel></tabpanel>
+ </tabpanels>
+ </tabbox>
+
+ <richlistbox id="listbox">
+ <richlistitem id="lb1_item1"><label value="item1"/></richlistitem>
+ <richlistitem id="lb1_item2"><label value="item2"/></richlistitem>
+ </richlistbox>
+
+ <richlistbox id="listbox2" seltype="multiple">
+ <richlistitem id="lb2_item1"><label value="item1"/></richlistitem>
+ <richlistitem id="lb2_item2"><label value="item2"/></richlistitem>
+ <richlistitem id="lb2_item3"><label value="item3"/></richlistitem>
+ <richlistitem id="lb2_item4"><label value="item4"/></richlistitem>
+ <richlistitem id="lb2_item5"><label value="item5"/></richlistitem>
+ <richlistitem id="lb2_item6"><label value="item6"/></richlistitem>
+ <richlistitem id="lb2_item7"><label value="item7"/></richlistitem>
+ </richlistbox>
+
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_selection_aria.html b/accessible/tests/mochitest/events/test_selection_aria.html
new file mode 100644
index 0000000000..c479868e03
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_selection_aria.html
@@ -0,0 +1,122 @@
+<html>
+
+<head>
+ <title>ARIA selection event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function selectItem(aSelectID, aItemID) {
+ this.selectNode = getNode(aSelectID);
+ this.itemNode = getNode(aItemID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SELECTION, aItemID),
+ ];
+
+ this.invoke = function selectItem_invoke() {
+ var itemNode = this.selectNode.querySelector("*[aria-selected='true']");
+ if (itemNode)
+ itemNode.removeAttribute("aria-selected");
+
+ this.itemNode.setAttribute("aria-selected", "true");
+ };
+
+ this.getID = function selectItem_getID() {
+ return "select item " + prettyName(aItemID);
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ var gQueue = null;
+
+ // gA11yEventDumpToConsole = true; // debug stuff
+
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new selectItem("tablist", "tab1"));
+ gQueue.push(new selectItem("tablist", "tab2"));
+
+ gQueue.push(new selectItem("tree", "treeitem1"));
+ gQueue.push(new selectItem("tree", "treeitem1a"));
+ gQueue.push(new selectItem("tree", "treeitem1a1"));
+
+ gQueue.push(new selectItem("tree2", "tree2item1"));
+ gQueue.push(new selectItem("tree2", "tree2item1a"));
+ gQueue.push(new selectItem("tree2", "tree2item1a1"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=569653"
+ title="Make selection events async">
+ Mozilla Bug 569653
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=804040"
+ title="Selection event not fired when selection of ARIA tab changes">
+ Mozilla Bug 804040
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="tablist" id="tablist">
+ <div role="tab" id="tab1">tab1</div>
+ <div role="tab" id="tab2">tab2</div>
+ </div>
+
+ <div id="tree" role="tree">
+ <div id="treeitem1" role="treeitem">Canada
+ <div id="treeitem1a" role="treeitem">- Ontario
+ <div id="treeitem1a1" role="treeitem">-- Toronto</div>
+ </div>
+ <div id="treeitem1b" role="treeitem">- Manitoba</div>
+ </div>
+ <div id="treeitem2" role="treeitem">Germany</div>
+ <div id="treeitem3" role="treeitem">Russia</div>
+ </div>
+
+ <div id="tree2" role="tree" aria-multiselectable="true">
+ <div id="tree2item1" role="treeitem">Canada
+ <div id="tree2item1a" role="treeitem">- Ontario
+ <div id="tree2item1a1" role="treeitem">-- Toronto</div>
+ </div>
+ <div id="tree2item1b" role="treeitem">- Manitoba</div>
+ </div>
+ <div id="tree2item2" role="treeitem">Germany</div>
+ <div id="tree2item3" role="treeitem">Russia</div>
+ </div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_statechange.html b/accessible/tests/mochitest/events/test_statechange.html
new file mode 100644
index 0000000000..e8ec08a4b3
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_statechange.html
@@ -0,0 +1,582 @@
+<html>
+
+<head>
+ <title>Accessible state change event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ async function openNode(aIDDetails, aIDSummary, aIsOpen) {
+ let p = waitForStateChange(aIDSummary, STATE_EXPANDED, aIsOpen, false);
+ if (aIsOpen) {
+ getNode(aIDDetails).setAttribute("open", "");
+ } else {
+ getNode(aIDDetails).removeAttribute("open");
+ }
+ await p;
+ }
+
+ async function makeEditableDoc(aDocNode, aIsEnabled) {
+ let p = waitForStateChange(aDocNode, EXT_STATE_EDITABLE, true, true);
+ aDocNode.designMode = "on";
+ await p;
+ }
+
+ async function invalidInput(aNodeOrID) {
+ let p = waitForStateChange(aNodeOrID, STATE_INVALID, true, false);
+ getNode(aNodeOrID).value = "I am not an email";
+ await p;
+ }
+
+ async function changeCheckInput(aID, aIsChecked) {
+ let p = waitForStateChange(aID, STATE_CHECKED, aIsChecked, false);
+ getNode(aID).checked = aIsChecked;
+ await p;
+ }
+
+ async function changeRequiredState(aID, aIsRequired) {
+ let p = waitForStateChange(aID, STATE_REQUIRED, aIsRequired, false);
+ getNode(aID).required = aIsRequired;
+ await p;
+ }
+
+ async function stateChangeOnFileInput(aID, aAttr, aValue,
+ aState, aIsExtraState, aIsEnabled) {
+ let fileControlNode = getNode(aID);
+ let fileControl = getAccessible(fileControlNode);
+ let browseButton = fileControl.firstChild;
+ let p = waitForEvents([
+ stateChangeEventArgs(fileControl, aState, aIsEnabled, aIsExtraState),
+ stateChangeEventArgs(browseButton, aState, aIsEnabled, aIsExtraState)])
+ fileControlNode.setAttribute(aAttr, aValue);
+ await p;
+ }
+
+ function toggleSentinel() {
+ let sentinel = getNode("sentinel");
+ if (sentinel.hasAttribute("aria-busy")) {
+ sentinel.removeAttribute("aria-busy");
+ } else {
+ sentinel.setAttribute("aria-busy", "true");
+ }
+ }
+
+ async function toggleStateChange(aID, aAttr, aState, aIsExtraState) {
+ let p = waitForEvents([
+ stateChangeEventArgs(aID, aState, true, aIsExtraState),
+ [EVENT_STATE_CHANGE, "sentinel"]
+ ]);
+ getNode(aID).setAttribute(aAttr, "true");
+ toggleSentinel();
+ await p;
+ p = waitForEvents([
+ stateChangeEventArgs(aID, aState, false, aIsExtraState),
+ [EVENT_STATE_CHANGE, "sentinel"]
+ ]);
+ getNode(aID).setAttribute(aAttr, "false");
+ toggleSentinel();
+ await p;
+ }
+
+ async function dupeStateChange(aID, aAttr, aValue,
+ aState, aIsExtraState, aIsEnabled) {
+ let p = waitForEvents([
+ stateChangeEventArgs(aID, aState, aIsEnabled, aIsExtraState),
+ [EVENT_STATE_CHANGE, "sentinel"]
+ ]);
+ getNode(aID).setAttribute(aAttr, aValue);
+ getNode(aID).setAttribute(aAttr, aValue);
+ toggleSentinel();
+ await p;
+ }
+
+ async function oppositeStateChange(aID, aAttr, aState, aIsExtraState) {
+ let p = waitForEvents({
+ expected: [[EVENT_STATE_CHANGE, "sentinel"]],
+ unexpected: [
+ stateChangeEventArgs(aID, aState, false, aIsExtraState),
+ stateChangeEventArgs(aID, aState, true, aIsExtraState)
+ ]
+ });
+ getNode(aID).setAttribute(aAttr, "false");
+ getNode(aID).setAttribute(aAttr, "true");
+ toggleSentinel();
+ await p;
+ }
+
+ /**
+ * Change concomitant ARIA and native attribute at once.
+ */
+ async function echoingStateChange(aID, aARIAAttr, aAttr, aValue,
+ aState, aIsExtraState, aIsEnabled) {
+ let p = waitForStateChange(aID, aState, aIsEnabled, aIsExtraState);
+ if (aValue == null) {
+ getNode(aID).removeAttribute(aARIAAttr);
+ getNode(aID).removeAttribute(aAttr);
+ } else {
+ getNode(aID).setAttribute(aARIAAttr, aValue);
+ getNode(aID).setAttribute(aAttr, aValue);
+ }
+ await p;
+ }
+
+ async function testLinked() {
+ let p = waitForStateChange("link1", STATE_LINKED, false, false);
+ getNode("link1").removeAttribute("href");
+ await p;
+
+ p = waitForStateChange("link2", STATE_LINKED, false, false);
+ getNode("link2").removeAttribute("onclick");
+ await p;
+
+ p = waitForStateChange("link3", STATE_LINKED, true, false);
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ getNode("link3").setAttribute("href", "http://example.com");
+ await p;
+ }
+
+ async function testHasPopup() {
+ let p = waitForStateChange("popupButton", STATE_HASPOPUP, true, false);
+ getNode("popupButton").setAttribute("aria-haspopup", "true");
+ await p;
+
+ p = waitForStateChange("popupButton", STATE_HASPOPUP, false, false);
+ getNode("popupButton").setAttribute("aria-haspopup", "false");
+ await p;
+
+ p = waitForStateChange("popupButton", STATE_HASPOPUP, true, false);
+ getNode("popupButton").setAttribute("aria-haspopup", "true");
+ await p;
+
+ p = waitForStateChange("popupButton", STATE_HASPOPUP, false, false);
+ getNode("popupButton").removeAttribute("aria-haspopup");
+ await p;
+ }
+
+ async function testDefaultSubmitChange() {
+ testStates("default-button",
+ STATE_DEFAULT, 0,
+ 0, 0,
+ "button should have DEFAULT state");
+ let button = document.createElement("button");
+ button.textContent = "new default";
+ let p = waitForStateChange("default-button", STATE_DEFAULT, false, false);
+ getNode("default-button").before(button);
+ await p;
+ testStates("default-button",
+ 0, 0,
+ STATE_DEFAULT, 0,
+ "button should not have DEFAULT state");
+ p = waitForStateChange("default-button", STATE_DEFAULT, true, false);
+ button.remove();
+ await p;
+ testStates("default-button",
+ STATE_DEFAULT, 0,
+ 0, 0,
+ "button should have DEFAULT state");
+ }
+
+ async function testReadOnly() {
+ let p = waitForStateChange("email", STATE_READONLY, true, false);
+ getNode("email").setAttribute("readonly", "true");
+ await p;
+ p = waitForStateChange("email", STATE_READONLY, false, false);
+ getNode("email").removeAttribute("readonly");
+ await p;
+ }
+
+ async function testReadonlyUntilEditable() {
+ testStates("article",
+ STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE,
+ "article is READONLY and not EDITABLE");
+ let p = waitForEvents([
+ stateChangeEventArgs("article", STATE_READONLY, false, false),
+ stateChangeEventArgs("article", EXT_STATE_EDITABLE, true, true)]);
+ getNode("article").contentEditable = "true";
+ await p;
+ testStates("article",
+ 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0,
+ "article is EDITABLE and not READONLY");
+ p = waitForEvents([
+ stateChangeEventArgs("article", STATE_READONLY, true, false),
+ stateChangeEventArgs("article", EXT_STATE_EDITABLE, false, true)]);
+ getNode("article").contentEditable = "false";
+ await p;
+ testStates("article",
+ STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE,
+ "article is READONLY and not EDITABLE");
+ }
+
+ async function testAnimatedImage() {
+ testStates("animated-image",
+ STATE_ANIMATED, 0,
+ 0, 0,
+ "image should be animated 1");
+ let p = waitForStateChange("animated-image", STATE_ANIMATED, false, false);
+ getNode("animated-image").src = "../animated-gif-finalframe.gif";
+ await p;
+ testStates("animated-image",
+ 0, 0,
+ STATE_ANIMATED, 0,
+ "image should not be animated 2");
+ p = waitForStateChange("animated-image", STATE_ANIMATED, true, false);
+ getNode("animated-image").src = "../animated-gif.gif";
+ await p;
+ testStates("animated-image",
+ STATE_ANIMATED, 0,
+ 0, 0,
+ "image should be animated 3");
+ }
+
+ async function testImageLoad() {
+ let img = document.createElement("img");
+ img.id = "image";
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ img.src = "http://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs";
+ let p = waitForEvent(EVENT_SHOW, "image");
+ getNode("eventdump").before(img);
+ await p;
+ testStates("image",
+ STATE_INVISIBLE, 0,
+ 0, 0,
+ "image should be invisible");
+ p = waitForStateChange("image", STATE_INVISIBLE, false, false);
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await fetch("http://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs?complete");
+ await p;
+ testStates("image",
+ 0, 0,
+ STATE_INVISIBLE, 0,
+ "image should be invisible");
+ }
+
+ async function testMultiSelectable(aID, aAttribute) {
+ testStates(aID,
+ 0, 0,
+ STATE_MULTISELECTABLE | STATE_EXTSELECTABLE, 0,
+ `${aID} should not be multiselectable`);
+ let p = waitForEvents([
+ stateChangeEventArgs(aID, STATE_MULTISELECTABLE, true, false),
+ stateChangeEventArgs(aID, STATE_EXTSELECTABLE, true, false),
+ ]);
+ getNode(aID).setAttribute(aAttribute, true);
+ await p;
+ testStates(aID,
+ STATE_MULTISELECTABLE | STATE_EXTSELECTABLE, 0,
+ 0, 0,
+ `${aID} should not be multiselectable`);
+ p = waitForEvents([
+ stateChangeEventArgs(aID, STATE_MULTISELECTABLE, false, false),
+ stateChangeEventArgs(aID, STATE_EXTSELECTABLE, false, false),
+ ]);
+ getNode(aID).removeAttribute(aAttribute);
+ await p;
+ testStates(aID,
+ 0, 0,
+ STATE_MULTISELECTABLE | STATE_EXTSELECTABLE, 0,
+ `${aID} should not be multiselectable`);
+ }
+
+ async function testAutocomplete() {
+ // A text input will have autocomplete via browser's form autofill...
+ testStates("input",
+ 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ 0, 0,
+ "input supports autocompletion");
+ // unless it is explicitly turned off.
+ testStates("input-autocomplete-off",
+ 0, 0,
+ 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "input-autocomplete-off does not support autocompletion");
+ // An input with a datalist will always have autocomplete.
+ testStates("input-list",
+ 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ 0, 0,
+ "input-list supports autocompletion");
+ // password fields don't get autocomplete.
+ testStates("input-password",
+ 0, 0,
+ 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "input-autocomplete-off does not support autocompletion");
+
+ let p = waitForEvents({
+ expected: [
+ // Setting the form's autocomplete attribute to "off" will cause
+ // "input" to lost its autocomplete state.
+ stateChangeEventArgs("input", EXT_STATE_SUPPORTS_AUTOCOMPLETION, false, true)
+ ],
+ unexpected: [
+ // "input-list" should preserve its autocomplete state regardless of
+ // forms "autocomplete" attribute
+ [EVENT_STATE_CHANGE, "input-list"],
+ // "input-autocomplete-off" already has its autocomplte off, so no state
+ // change here.
+ [EVENT_STATE_CHANGE, "input-autocomplete-off"],
+ // passwords never get autocomplete
+ [EVENT_STATE_CHANGE, "input-password"],
+ ]
+ });
+
+ getNode("form").setAttribute("autocomplete", "off");
+
+ await p;
+
+ // Same when we remove the form's autocomplete attribute.
+ p = waitForEvents({
+ expected: [stateChangeEventArgs("input", EXT_STATE_SUPPORTS_AUTOCOMPLETION, true, true)],
+ unexpected: [
+ [EVENT_STATE_CHANGE, "input-list"],
+ [EVENT_STATE_CHANGE, "input-autocomplete-off"],
+ [EVENT_STATE_CHANGE, "input-password"],
+ ]
+ });
+
+ getNode("form").removeAttribute("autocomplete");
+
+ await p;
+
+ p = waitForEvents({
+ expected: [
+ // Forcing autocomplete off on an input will cause a state change
+ stateChangeEventArgs("input", EXT_STATE_SUPPORTS_AUTOCOMPLETION, false, true),
+ // Associating a datalist with an autocomplete=off input
+ // will give it an autocomplete state, regardless.
+ stateChangeEventArgs("input-autocomplete-off", EXT_STATE_SUPPORTS_AUTOCOMPLETION, true, true),
+ // XXX: datalist inputs also get a HASPOPUP state, the inconsistent
+ // use of that state is inexplicable, but lets make sure we fire state
+ // change events for it anyway.
+ stateChangeEventArgs("input-autocomplete-off", STATE_HASPOPUP, true, false),
+ ],
+ unexpected: [
+ // Forcing autocomplete off with a dataset input does nothing.
+ [EVENT_STATE_CHANGE, "input-list"],
+ // passwords never get autocomplete
+ [EVENT_STATE_CHANGE, "input-password"],
+ ]
+ });
+
+ getNode("input").setAttribute("autocomplete", "off");
+ getNode("input-list").setAttribute("autocomplete", "off");
+ getNode("input-autocomplete-off").setAttribute("list", "browsers");
+ getNode("input-password").setAttribute("autocomplete", "off");
+
+ await p;
+ }
+
+ async function doTests() {
+ // Test opening details objects
+ await openNode("detailsOpen", "summaryOpen", true);
+ await openNode("detailsOpen", "summaryOpen", false);
+ await openNode("detailsOpen1", "summaryOpen1", true);
+ await openNode("detailsOpen2", "summaryOpen2", true);
+ await openNode("detailsOpen3", "summaryOpen3", true);
+ await openNode("detailsOpen4", "summaryOpen4", true);
+ await openNode("detailsOpen5", "summaryOpen5", true);
+ await openNode("detailsOpen6", "summaryOpen6", true);
+
+ // Test delayed editable state change
+ var doc = document.getElementById("iframe").contentDocument;
+ await makeEditableDoc(doc);
+
+ // invalid state change
+ await invalidInput("email");
+
+ // checked state change
+ await changeCheckInput("checkbox", true);
+ await changeCheckInput("checkbox", false);
+ await changeCheckInput("radio", true);
+ await changeCheckInput("radio", false);
+
+ // required state change
+ await changeRequiredState("checkbox", true);
+
+ // file input inherited state changes
+ await stateChangeOnFileInput("file", "aria-busy", "true",
+ STATE_BUSY, false, true);
+ await stateChangeOnFileInput("file", "aria-required", "true",
+ STATE_REQUIRED, false, true);
+ await stateChangeOnFileInput("file", "aria-invalid", "true",
+ STATE_INVALID, false, true);
+
+ await dupeStateChange("div", "aria-busy", "true",
+ STATE_BUSY, false, true);
+ await oppositeStateChange("div", "aria-busy",
+ STATE_BUSY, false);
+
+ await echoingStateChange("text1", "aria-disabled", "disabled", "true",
+ EXT_STATE_ENABLED, true, false);
+ await echoingStateChange("text1", "aria-disabled", "disabled", null,
+ EXT_STATE_ENABLED, true, true);
+
+ await testReadOnly();
+
+ await testReadonlyUntilEditable();
+
+ await testLinked();
+
+ await testHasPopup();
+
+ await toggleStateChange("textbox", "aria-multiline", EXT_STATE_MULTI_LINE, true);
+
+ await testDefaultSubmitChange();
+
+ await testAnimatedImage();
+
+ await testImageLoad();
+
+ await testMultiSelectable("listbox", "aria-multiselectable");
+
+ await testMultiSelectable("select", "multiple");
+
+ await testAutocomplete();
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+<style>
+ details.openBefore::before{
+ content: "before detail content: ";
+ background: blue;
+ }
+ summary.openBefore::before{
+ content: "before summary content: ";
+ background: green;
+ }
+ details.openAfter::after{
+ content: " :after detail content";
+ background: blue;
+ }
+ summary.openAfter::after{
+ content: " :after summary content";
+ background: green;
+ }
+</style>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=564471"
+ title="Make state change events async">
+ Bug 564471
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=555728"
+ title="Fire a11y event based on HTML5 constraint validation">
+ Bug 555728
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017"
+ title="File input control should be propogate states to descendants">
+ Bug 699017
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=788389"
+ title="Fire statechange event whenever checked state is changed not depending on focused state">
+ Bug 788389
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=926812"
+ title="State change event not fired when both disabled and aria-disabled are toggled">
+ Bug 926812
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- open -->
+ <details id="detailsOpen"><summary id="summaryOpen">open</summary>details can be opened</details>
+ <details id="detailsOpen1">order doesn't matter<summary id="summaryOpen1">open</summary></details>
+ <details id="detailsOpen2"><div>additional elements don't matter</div><summary id="summaryOpen2">open</summary></details>
+ <details id="detailsOpen3" class="openBefore"><summary id="summaryOpen3">summary</summary>content</details>
+ <details id="detailsOpen4" class="openAfter"><summary id="summaryOpen4">summary</summary>content</details>
+ <details id="detailsOpen5"><summary id="summaryOpen5" class="openBefore">summary</summary>content</details>
+ <details id="detailsOpen6"><summary id="summaryOpen6" class="openAfter">summary</summary>content</details>
+
+
+ <div id="testContainer">
+ <iframe id="iframe"></iframe>
+ </div>
+
+ <input id="email" type='email'>
+
+ <input id="checkbox" type="checkbox">
+ <input id="radio" type="radio">
+
+ <input id="file" type="file">
+
+ <div id="div"></div>
+
+ <!-- A sentinal guards from events of interest being fired after it emits a state change -->
+ <div id="sentinel"></div>
+
+ <input id="text1">
+
+ <a id="link1" href="#">I am a link link</a>
+ <a id="link2" onclick="console.log('hi')">I am a link-ish link</a>
+ <a id="link3">I am a non-link link</a>
+
+ <div id="textbox" role="textbox" aria-multiline="false">hello</div>
+
+ <form id="form">
+ <button id="default-button">hello</button>
+ <button>world</button>
+ <input id="input">
+ <input id="input-autocomplete-off" autocomplete="off">
+ <input id="input-list" list="browsers">
+ <input id="input-password" type="password">
+ <datalist id="browsers">
+ <option value="Internet Explorer">
+ <option value="Firefox">
+ <option value="Google Chrome">
+ <option value="Opera">
+ <option value="Safari">
+ </datalist>
+ </form>
+
+ <div id="article" role="article">hello</div>
+
+ <img id="animated-image" src="../animated-gif.gif">
+
+ <ul id="listbox" role="listbox">
+ <li role="option">one</li>
+ <li role="option">two</li>
+ <li role="option">three</li>
+ <li role="option">four</li>
+ <li role="option">five</li>
+ </ul>
+
+ <select id="select" size="2">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+ <option>four</option>
+ <option>five</option>
+ <option>size</option>
+ </select>
+
+ <div id="eventdump"></div>
+
+ <div id="eventdump"></div>
+ <button id="popupButton">action</button>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_statechange.xhtml b/accessible/tests/mochitest/events/test_statechange.xhtml
new file mode 100644
index 0000000000..4d63c664f1
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_statechange.xhtml
@@ -0,0 +1,117 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL state change event tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../promisified-events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function offscreenChangeEvent(acc, enabled) {
+ return [
+ EVENT_STATE_CHANGE,
+ event => {
+ const scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent);
+ return event.accessible == acc &&
+ scEvent.state == STATE_OFFSCREEN &&
+ scEvent.isEnabled == enabled;
+ }
+ ];
+ }
+
+ async function testTabpanels() {
+ const tabs = getNode("tabs");
+ is(tabs.selectedIndex, 0, "tab1 initially selected");
+ const panel1 = getAccessible("panel1");
+ testStates(panel1, 0, 0, STATE_OFFSCREEN);
+ const panel2 = getAccessible("panel2");
+ testStates(panel2, STATE_OFFSCREEN);
+ const panel3 = getAccessible("panel3");
+ testStates(panel3, STATE_OFFSCREEN);
+
+ let events = waitForEvents([
+ offscreenChangeEvent(panel1, true),
+ offscreenChangeEvent(panel2, false)
+ ]);
+ info("Selecting tab2");
+ tabs.selectedIndex = 1;
+ await events;
+
+ events = waitForEvents([
+ offscreenChangeEvent(panel2, true),
+ offscreenChangeEvent(panel3, false)
+ ]);
+ info("Selecting tab3");
+ tabs.selectedIndex = 2;
+ await events;
+
+ events = waitForEvents([
+ offscreenChangeEvent(panel3, true),
+ offscreenChangeEvent(panel1, false)
+ ]);
+ info("Selecting tab1");
+ tabs.selectedIndex = 0;
+ await events;
+ }
+
+ async function testPressed() {
+ const toolbarbuttonCheckbox = getNode("toolbarbuttonCheckbox");
+ testStates(toolbarbuttonCheckbox, 0, 0, STATE_PRESSED);
+ info("Checking toolbarbuttonCheckbox");
+ let changed = waitForStateChange(toolbarbuttonCheckbox, STATE_PRESSED, true);
+ toolbarbuttonCheckbox.setAttribute("checked", true);
+ await changed;
+ info("Unchecking toolbarbuttonCheckbox");
+ changed = waitForStateChange(toolbarbuttonCheckbox, STATE_PRESSED, false);
+ toolbarbuttonCheckbox.removeAttribute("checked");
+ await changed;
+ }
+
+ async function doTests() {
+ await testTabpanels();
+ await testPressed();
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <tabbox id="tabbox" selectedIndex="0">
+ <tabs id="tabs">
+ <tab id="tab1" label="tab1"/>
+ <tab id="tab2" label="tab2"/>
+ <tab id="tab3" label="tab3"/>
+ </tabs>
+ <tabpanels>
+ <hbox id="panel1"><button label="b1"/></hbox>
+ <hbox id="panel2"><button label="b2"/></hbox>
+ <hbox id="panel3"><button label="b3"/></hbox>
+ </tabpanels>
+ </tabbox>
+
+ <toolbarbutton id="toolbarbuttonCheckbox" type="checkbox">toolbarbuttonCheckbox</toolbarbutton>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_text.html b/accessible/tests/mochitest/events/test_text.html
new file mode 100644
index 0000000000..8a0bd7f9a4
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_text.html
@@ -0,0 +1,310 @@
+<html>
+
+<head>
+ <title>Accessible mutation events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ /**
+ * Base text remove invoker and checker.
+ */
+ function textRemoveInvoker(aID, aStart, aEnd, aText) {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new textChangeChecker(aID, aStart, aEnd, aText, false),
+ ];
+ }
+
+ function textInsertInvoker(aID, aStart, aEnd, aText) {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new textChangeChecker(aID, aStart, aEnd, aText, true),
+ ];
+ }
+
+ /**
+ * Remove inaccessible child node containing accessibles.
+ */
+ function removeChildSpan(aID) {
+ this.__proto__ = new textRemoveInvoker(aID, 0, 5, "33322");
+
+ this.invoke = function removeChildSpan_invoke() {
+ // remove HTML span, a first child of the node
+ this.DOMNode.firstChild.remove();
+ };
+
+ this.getID = function removeChildSpan_getID() {
+ return "Remove inaccessible span containing accessible nodes" + prettyName(aID);
+ };
+ }
+
+ /**
+ * Insert inaccessible child node containing accessibles.
+ */
+ function insertChildSpan(aID, aInsertAllTogether) {
+ this.__proto__ = new textInsertInvoker(aID, 0, 5, "33322");
+
+ this.invoke = function insertChildSpan_invoke() {
+ // <span><span>333</span><span>22</span></span>
+ if (aInsertAllTogether) {
+ let topSpan = document.createElement("span");
+ let fSpan = document.createElement("span");
+ fSpan.textContent = "333";
+ topSpan.appendChild(fSpan);
+ let sSpan = document.createElement("span");
+ sSpan.textContent = "22";
+ topSpan.appendChild(sSpan);
+
+ this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]);
+ } else {
+ let topSpan = document.createElement("span");
+ this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]);
+
+ let fSpan = document.createElement("span");
+ fSpan.textContent = "333";
+ topSpan.appendChild(fSpan);
+
+ let sSpan = document.createElement("span");
+ sSpan.textContent = "22";
+ topSpan.appendChild(sSpan);
+ }
+ };
+
+ this.getID = function insertChildSpan_getID() {
+ return "Insert inaccessible span containing accessibles" +
+ prettyName(aID);
+ };
+ }
+
+ /**
+ * Remove child embedded accessible.
+ */
+ function removeChildDiv(aID) {
+ this.__proto__ = new textRemoveInvoker(aID, 5, 6, kEmbedChar);
+
+ this.invoke = function removeChildDiv_invoke() {
+ var childDiv = this.DOMNode.childNodes[1];
+
+ // Ensure accessible is created to get text remove event when it's
+ // removed.
+ getAccessible(childDiv);
+
+ this.DOMNode.removeChild(childDiv);
+ };
+
+ this.getID = function removeChildDiv_getID() {
+ return "Remove accessible div from the middle of text accessible " +
+ prettyName(aID);
+ };
+ }
+
+ /**
+ * Insert child embedded accessible.
+ */
+ function insertChildDiv(aID) {
+ this.__proto__ = new textInsertInvoker(aID, 5, 6, kEmbedChar);
+
+ this.invoke = function insertChildDiv_invoke() {
+ var childDiv = document.createElement("div");
+ // Note after bug 646216, a sole div without text won't be accessible
+ // and would not result in an embedded character.
+ // Therefore, add some text.
+ childDiv.textContent = "hello";
+ this.DOMNode.insertBefore(childDiv, this.DOMNode.childNodes[1]);
+ };
+
+ this.getID = function insertChildDiv_getID() {
+ return "Insert accessible div into the middle of text accessible " +
+ prettyName(aID);
+ };
+ }
+
+ /**
+ * Remove children from text container from first to last child or vice
+ * versa.
+ */
+ function removeChildren(aID, aLastToFirst, aStart, aEnd, aText) {
+ this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText);
+
+ this.invoke = function removeChildren_invoke() {
+ if (aLastToFirst) {
+ while (this.DOMNode.firstChild)
+ this.DOMNode.removeChild(this.DOMNode.lastChild);
+ } else {
+ while (this.DOMNode.firstChild)
+ this.DOMNode.firstChild.remove();
+ }
+ };
+
+ this.getID = function removeChildren_getID() {
+ return "remove children of " + prettyName(aID) +
+ (aLastToFirst ? " from last to first" : " from first to last");
+ };
+ }
+
+ /**
+ * Remove text from HTML input.
+ */
+ function removeTextFromInput(aID, aStart, aEnd, aText) {
+ this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText);
+
+ this.eventSeq.push(new invokerChecker(EVENT_TEXT_VALUE_CHANGE,
+ this.DOMNode));
+
+ this.invoke = function removeTextFromInput_invoke() {
+ this.DOMNode.focus();
+ this.DOMNode.setSelectionRange(aStart, aEnd);
+
+ synthesizeKey("KEY_Delete");
+ };
+
+ this.getID = function removeTextFromInput_getID() {
+ return "Remove text from " + aStart + " to " + aEnd + " for " +
+ prettyName(aID);
+ };
+ }
+
+ /**
+ * Add text into HTML input.
+ */
+ function insertTextIntoInput(aID, aStart, aEnd, aText) {
+ this.__proto__ = new textInsertInvoker(aID, aStart, aEnd, aText);
+
+ this.eventSeq.push(new invokerChecker(EVENT_TEXT_VALUE_CHANGE,
+ this.DOMNode));
+
+ this.invoke = function insertTextIntoInput_invoke() {
+ this.DOMNode.focus();
+ sendString("a");
+ };
+
+ this.getID = function insertTextIntoInput_getID() {
+ return "Insert text to " + aStart + " for " + prettyName(aID);
+ };
+ }
+
+ /**
+ * Remove text data from text node of editable area.
+ */
+ function removeTextFromEditable(aID, aStart, aEnd, aText, aTextNode) {
+ this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText);
+
+ this.invoke = function removeTextFromEditable_invoke() {
+ this.DOMNode.focus();
+
+ var selection = window.getSelection();
+ var range = document.createRange();
+ range.setStart(this.textNode, aStart);
+ range.setEnd(this.textNode, aEnd);
+ selection.addRange(range);
+
+ synthesizeKey("KEY_Delete");
+ };
+
+ this.getID = function removeTextFromEditable_getID() {
+ return "Remove text from " + aStart + " to " + aEnd + " for " +
+ prettyName(aID);
+ };
+
+ this.textNode = getNode(aTextNode);
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+ gA11yEventDumpToConsole = true; // debugging
+
+ var gQueue = null;
+ function doTests() {
+ gQueue = new eventQueue();
+
+ // Text remove event on inaccessible child HTML span removal containing
+ // accessible text nodes.
+ gQueue.push(new removeChildSpan("p"));
+ gQueue.push(new insertChildSpan("p"), true);
+ gQueue.push(new insertChildSpan("p"), false);
+
+ // Remove embedded character.
+ gQueue.push(new removeChildDiv("div"));
+ gQueue.push(new insertChildDiv("div"));
+
+ // Remove all children.
+ var text = kEmbedChar + "txt" + kEmbedChar;
+ gQueue.push(new removeChildren("div2", true, 0, 5, text));
+ gQueue.push(new removeChildren("div3", false, 0, 5, text));
+
+ // Text remove from text node within hypertext accessible.
+ gQueue.push(new removeTextFromInput("input", 1, 3, "al"));
+ gQueue.push(new insertTextIntoInput("input", 1, 2, "a"));
+
+ // bug 570691
+ todo(false, "Fix text change events from editable area, see bug 570691");
+ // var textNode = getNode("editable").firstChild;
+ // gQueue.push(new removeTextFromEditable("editable", 1, 3, "al", textNode));
+ // textNode = getNode("editable2").firstChild.firstChild;
+ // gQueue.push(new removeTextFromEditable("editable2", 1, 3, "al", textNode));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=566293"
+ title=" wrong length of text remove event when inaccessible node containing accessible nodes is removed">
+ Mozilla Bug 566293
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570710"
+ title="Avoid extra array traversal during text event creation">
+ Mozilla Bug 570710
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=574003"
+ title="Coalesce text events on nodes removal">
+ Mozilla Bug 574003
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=575052"
+ title="Cache text offsets within hypertext accessible">
+ Mozilla Bug 575052
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"
+ title="Rework accessible tree update code">
+ Mozilla Bug 570275
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="p"><span><span>333</span><span>22</span></span>1111</p>
+ <div id="div">hello<div>hello</div>hello</div>
+ <div id="div2"><div>txt</div>txt<div>txt</div></div>
+ <div id="div3"><div>txt</div>txt<div>txt</div></div>
+ <input id="input" value="value">
+ <div contentEditable="true" id="editable">value</div>
+ <div contentEditable="true" id="editable2"><span>value</span></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_text_alg.html b/accessible/tests/mochitest/events/test_text_alg.html
new file mode 100644
index 0000000000..f9b55c8b23
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_text_alg.html
@@ -0,0 +1,249 @@
+<html>
+
+<head>
+ <title>Accessible text update algorithm testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ const kRemoval = false;
+ const kInsertion = true;
+ const kUnexpected = true;
+
+ function changeText(aContainerID, aValue, aEventList) {
+ this.containerNode = getNode(aContainerID);
+ this.textNode = this.containerNode.firstChild;
+ this.textData = this.textNode.data;
+
+ this.eventSeq = [ ];
+ this.unexpectedEventSeq = [ ];
+
+ for (var i = 0; i < aEventList.length; i++) {
+ var event = aEventList[i];
+
+ var isInserted = event[0];
+ var str = event[1];
+ var offset = event[2];
+ var checker = new textChangeChecker(this.containerNode, offset,
+ offset + str.length, str,
+ isInserted);
+
+ if (event[3] == kUnexpected)
+ this.unexpectedEventSeq.push(checker);
+ else
+ this.eventSeq.push(checker);
+ }
+
+ this.invoke = function changeText_invoke() {
+ this.textNode.data = aValue;
+ };
+
+ this.getID = function changeText_getID() {
+ return "change text '" + shortenString(this.textData) + "' -> '" +
+ shortenString(this.textNode.data) + "' for " +
+ prettyName(this.containerNode);
+ };
+ }
+
+ function expStr(x, doublings) {
+ for (var i = 0; i < doublings; ++i)
+ x = x + x;
+ return x;
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTests() {
+ gQueue = new eventQueue();
+
+ // ////////////////////////////////////////////////////////////////////////
+ // wqrema -> tqb: substitution coalesced with removal
+
+ var events = [
+ [ kRemoval, "w", 0 ], // wqrema -> qrema
+ [ kInsertion, "t", 0], // qrema -> tqrema
+ [ kRemoval, "rema", 2 ], // tqrema -> tq
+ [ kInsertion, "b", 2], // tq -> tqb
+ ];
+ gQueue.push(new changeText("p1", "tqb", events));
+
+ // ////////////////////////////////////////////////////////////////////////
+ // b -> insa: substitution coalesced with insertion (complex substitution)
+
+ events = [
+ [ kRemoval, "b", 0 ], // b ->
+ [ kInsertion, "insa", 0], // -> insa
+ ];
+ gQueue.push(new changeText("p2", "insa", events));
+
+ // ////////////////////////////////////////////////////////////////////////
+ // abc -> def: coalesced substitutions
+
+ events = [
+ [ kRemoval, "abc", 0 ], // abc ->
+ [ kInsertion, "def", 0], // -> def
+ ];
+ gQueue.push(new changeText("p3", "def", events));
+
+ // ////////////////////////////////////////////////////////////////////////
+ // abcabc -> abcDEFabc: coalesced insertions
+
+ events = [
+ [ kInsertion, "DEF", 3], // abcabc -> abcDEFabc
+ ];
+ gQueue.push(new changeText("p4", "abcDEFabc", events));
+
+ // ////////////////////////////////////////////////////////////////////////
+ // abc -> defabc: insertion into begin
+
+ events = [
+ [ kInsertion, "def", 0], // abc -> defabc
+ ];
+ gQueue.push(new changeText("p5", "defabc", events));
+
+ // ////////////////////////////////////////////////////////////////////////
+ // abc -> abcdef: insertion into end
+
+ events = [
+ [ kInsertion, "def", 3], // abc -> abcdef
+ ];
+ gQueue.push(new changeText("p6", "abcdef", events));
+
+ // ////////////////////////////////////////////////////////////////////////
+ // defabc -> abc: removal from begin
+
+ events = [
+ [ kRemoval, "def", 0], // defabc -> abc
+ ];
+ gQueue.push(new changeText("p7", "abc", events));
+
+ // ////////////////////////////////////////////////////////////////////////
+ // abcdef -> abc: removal from the end
+
+ events = [
+ [ kRemoval, "def", 3], // abcdef -> abc
+ ];
+ gQueue.push(new changeText("p8", "abc", events));
+
+ // ////////////////////////////////////////////////////////////////////////
+ // abcDEFabc -> abcabc: coalesced removals
+
+ events = [
+ [ kRemoval, "DEF", 3], // abcDEFabc -> abcabc
+ ];
+ gQueue.push(new changeText("p9", "abcabc", events));
+
+ // ////////////////////////////////////////////////////////////////////////
+ // !abcdef@ -> @axbcef!: insertion, deletion and substitutions
+
+ events = [
+ [ kRemoval, "!", 0 ], // !abcdef@ -> abcdef@
+ [ kInsertion, "@", 0], // abcdef@ -> @abcdef@
+ [ kInsertion, "x", 2 ], // @abcdef@ -> @axbcdef@
+ [ kRemoval, "d", 5], // @axbcdef@ -> @axbcef@
+ [ kRemoval, "@", 7 ], // @axbcef@ -> @axbcef
+ [ kInsertion, "!", 7 ], // @axbcef -> @axbcef!
+ ];
+ gQueue.push(new changeText("p10", "@axbcef!", events));
+
+ // ////////////////////////////////////////////////////////////////////////
+ // meilenstein -> levenshtein: insertion, complex and simple substitutions
+
+ events = [
+ [ kRemoval, "m", 0 ], // meilenstein -> eilenstein
+ [ kInsertion, "l", 0], // eilenstein -> leilenstein
+ [ kRemoval, "il", 2 ], // leilenstein -> leenstein
+ [ kInsertion, "v", 2], // leenstein -> levenstein
+ [ kInsertion, "h", 6 ], // levenstein -> levenshtein
+ ];
+ gQueue.push(new changeText("p11", "levenshtein", events));
+
+ // ////////////////////////////////////////////////////////////////////////
+ // long strings, remove/insert pair as the old string was replaced on
+ // new one
+
+ var longStr1 = expStr("x", 16);
+ var longStr2 = expStr("X", 16);
+
+ var newStr = "a" + longStr1 + "b", insStr = longStr1, rmStr = "";
+ events = [
+ [ kRemoval, rmStr, 1, kUnexpected ],
+ [ kInsertion, insStr, 1 ],
+ ];
+ gQueue.push(new changeText("p12", newStr, events));
+
+ newStr = "a" + longStr2 + "b";
+ insStr = longStr2;
+ rmStr = longStr1;
+ events = [
+ [ kRemoval, rmStr, 1 ],
+ [ kInsertion, insStr, 1],
+ ];
+ gQueue.push(new changeText("p12", newStr, events));
+
+ newStr = "ab";
+ insStr = "";
+ rmStr = longStr2;
+ events = [
+ [ kRemoval, rmStr, 1 ],
+ [ kInsertion, insStr, 1, kUnexpected ],
+ ];
+ gQueue.push(new changeText("p12", newStr, events));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660"
+ title="Cache rendered text on a11y side">
+ Mozilla Bug 626660
+ </a>
+ <br>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <!-- Note: only editable text gets diffed this way. -->
+ <div contenteditable="true">
+ <p id="p1">wqrema</p>
+ <p id="p2">b</p>
+ <p id="p3">abc</p>
+ <p id="p4">abcabc</p>
+ <p id="p5">abc</p>
+ <p id="p6">abc</p>
+ <p id="p7">defabc</p>
+ <p id="p8">abcdef</p>
+ <p id="p9">abcDEFabc</p>
+ <p id="p10">!abcdef@</p>
+ <p id="p11">meilenstein</p>
+ <p id="p12">ab</p>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_textattrchange.html b/accessible/tests/mochitest/events/test_textattrchange.html
new file mode 100644
index 0000000000..6282ceb5c2
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_textattrchange.html
@@ -0,0 +1,105 @@
+<html>
+
+<head>
+ <title>Text attribute changed event for misspelled text</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+
+ const {InlineSpellChecker} = ChromeUtils.import("resource://gre/modules/InlineSpellChecker.jsm");
+
+ function spelledTextInvoker(aID) {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_TEXT_ATTRIBUTE_CHANGED, this.DOMNode),
+ ];
+
+ this.invoke = function spelledTextInvoker_invoke() {
+ var editor = this.DOMNode.editor;
+ var spellChecker = new InlineSpellChecker(editor);
+ spellChecker.enabled = true;
+
+ // var spellchecker = editor.getInlineSpellChecker(true);
+ // spellchecker.enableRealTimeSpell = true;
+
+ this.DOMNode.value = "valid text inalid tixt";
+ };
+
+ this.finalCheck = function spelledTextInvoker_finalCheck() {
+ var defAttrs = buildDefaultTextAttrs(this.DOMNode, kInputFontSize,
+ kNormalFontWeight,
+ kInputFontFamily);
+ testDefaultTextAttrs(aID, defAttrs);
+
+ var attrs = { };
+ var misspelledAttrs = {
+ "invalid": "spelling",
+ };
+
+ testTextAttrs(aID, 0, attrs, defAttrs, 0, 11);
+ testTextAttrs(aID, 11, misspelledAttrs, defAttrs, 11, 17);
+ testTextAttrs(aID, 17, attrs, defAttrs, 17, 18);
+ testTextAttrs(aID, 18, misspelledAttrs, defAttrs, 18, 22);
+ };
+
+ this.getID = function spelledTextInvoker_getID() {
+ return "text attribute change for misspelled text";
+ };
+ }
+
+ /**
+ * Do tests.
+ */
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTests() {
+ // Synth focus before spellchecking turning on to make sure editor
+ // gets a time for initialization.
+
+ gQueue = new eventQueue();
+ gQueue.push(new synthFocus("input"));
+ gQueue.push(new spelledTextInvoker("input"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=345759"
+ title="Implement text attributes">
+ Mozilla Bug 345759
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input"/>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_textselchange.html b/accessible/tests/mochitest/events/test_textselchange.html
new file mode 100644
index 0000000000..3dce0760eb
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_textselchange.html
@@ -0,0 +1,82 @@
+<html>
+
+<head>
+ <title>Accessible text selection change events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ var gQueue = null;
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ function getOnclickSeq(aID) {
+ return [
+ new caretMoveChecker(0, true, aID),
+ new unexpectedInvokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID),
+ ];
+ }
+
+ function doTests() {
+ // test caret move events and caret offsets
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthClick("c1_p1", getOnclickSeq("c1_p1")));
+ gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 1, "c1_p1", 0, "c1_p2", 0), { shiftKey: true }));
+ gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 2, "c1_p1", 0, "c1_p2", 9), { shiftKey: true }));
+
+ gQueue.push(new synthClick("ta1", getOnclickSeq("ta1")));
+ gQueue.push(new synthRightKey("ta1",
+ new textSelectionChecker("ta1", 0, 1, "ta1", 0, "ta1", 1),
+ { shiftKey: true }));
+ gQueue.push(new synthLeftKey("ta1",
+ [new textSelectionChecker("ta1", 0, 0, "ta1", 0, "ta1", 0),
+ new caretMoveChecker(0, true, "ta1")]));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=762934"
+ title="Text selection change event has a wrong target when selection is spanned through several objects">
+ Bug 762934
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=956032"
+ title="Text selection change event missed when selected text becomes unselected">
+ Bug 956032
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="c1" contentEditable="true">
+ <p id="c1_p1">paragraph</p>
+ <p id="c1_p2">paragraph</p>
+ </div>
+
+ <textarea id="ta1">Hello world</textarea>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_tree.xhtml b/accessible/tests/mochitest/events/test_tree.xhtml
new file mode 100644
index 0000000000..af7feafde8
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_tree.xhtml
@@ -0,0 +1,358 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="DOM TreeRowCountChanged and a11y name change events.">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ var gView;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invoker's checkers
+
+ /**
+ * Check TreeRowCountChanged event.
+ */
+ function rowCountChangedChecker(aMsg, aIdx, aCount)
+ {
+ this.type = "TreeRowCountChanged";
+ this.target = gTree;
+ this.check = function check(aEvent)
+ {
+ var propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2);
+ var index = propBag.getPropertyAsInt32("index");
+ is(index, aIdx, "Wrong 'index' data of 'treeRowCountChanged' event.");
+
+ var count = propBag.getPropertyAsInt32("count");
+ is(count, aCount, "Wrong 'count' data of 'treeRowCountChanged' event.");
+ }
+ this.getID = function getID()
+ {
+ return aMsg + "TreeRowCountChanged";
+ }
+ }
+
+ /**
+ * Check TreeInvalidated event.
+ */
+ function treeInvalidatedChecker(aMsg, aStartRow, aEndRow, aStartCol, aEndCol)
+ {
+ this.type = "TreeInvalidated";
+ this.target = gTree;
+ this.check = function check(aEvent)
+ {
+ var propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2);
+ try {
+ var startRow = propBag.getPropertyAsInt32("startrow");
+ } catch (e) {
+ if (e.name != 'NS_ERROR_NOT_AVAILABLE') {
+ throw e;
+ }
+ startRow = null;
+ }
+ is(startRow, aStartRow,
+ "Wrong 'startrow' of 'treeInvalidated' event on " + aMsg);
+
+ try {
+ var endRow = propBag.getPropertyAsInt32("endrow");
+ } catch (e) {
+ if (e.name != 'NS_ERROR_NOT_AVAILABLE') {
+ throw e;
+ }
+ endRow = null;
+ }
+ is(endRow, aEndRow,
+ "Wrong 'endrow' of 'treeInvalidated' event on " + aMsg);
+
+ try {
+ var startCol = propBag.getPropertyAsInt32("startcolumn");
+ } catch (e) {
+ if (e.name != 'NS_ERROR_NOT_AVAILABLE') {
+ throw e;
+ }
+ startCol = null;
+ }
+ is(startCol, aStartCol,
+ "Wrong 'startcolumn' of 'treeInvalidated' event on " + aMsg);
+
+ try {
+ var endCol = propBag.getPropertyAsInt32("endcolumn");
+ } catch (e) {
+ if (e.name != 'NS_ERROR_NOT_AVAILABLE') {
+ throw e;
+ }
+ endCol = null;
+ }
+ is(endCol, aEndCol,
+ "Wrong 'endcolumn' of 'treeInvalidated' event on " + aMsg);
+ }
+ this.getID = function getID()
+ {
+ return "TreeInvalidated on " + aMsg;
+ }
+ }
+
+ /**
+ * Check name changed a11y event.
+ */
+ function nameChangeChecker(aMsg, aRow, aCol)
+ {
+ this.type = EVENT_NAME_CHANGE;
+
+ function targetGetter()
+ {
+ var acc = getAccessible(gTree);
+
+ var tableAcc = getAccessible(acc, [nsIAccessibleTable]);
+ return tableAcc.getCellAt(aRow, aCol);
+ }
+ Object.defineProperty(this, "target", { get: targetGetter });
+
+ this.getID = function getID()
+ {
+ return aMsg + "name changed";
+ }
+ }
+
+ /**
+ * Check name changed a11y event for a row.
+ */
+ function rowNameChangeChecker(aMsg, aRow)
+ {
+ this.type = EVENT_NAME_CHANGE;
+
+ function targetGetter()
+ {
+ var acc = getAccessible(gTree);
+ return acc.getChildAt(aRow + 1);
+ }
+ Object.defineProperty(this, "target", { get: targetGetter });
+
+ this.getID = function getID()
+ {
+ return aMsg + "name changed";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ /**
+ * Set tree view.
+ */
+ function setTreeView()
+ {
+ this.invoke = function setTreeView_invoke()
+ {
+ gTree.view = gView;
+ }
+
+ this.getID = function setTreeView_getID() { return "set tree view"; }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, gTree)
+ ];
+ };
+
+ /**
+ * Insert row at 0 index and checks TreeRowCountChanged and TreeInvalidated
+ * event.
+ */
+ function insertRow()
+ {
+ this.invoke = function insertRow_invoke()
+ {
+ gView.appendItem("last");
+ gTree.rowCountChanged(0, 1);
+ }
+
+ this.eventSeq =
+ [
+ new rowCountChangedChecker("insertRow: ", 0, 1),
+ new treeInvalidatedChecker("insertRow", 0, 5, null, null)
+ ];
+
+ this.getID = function insertRow_getID()
+ {
+ return "insert row";
+ }
+ }
+
+ /**
+ * Invalidates first column and checks six name changed events for each
+ * treeitem plus TreeInvalidated event.
+ */
+ function invalidateColumn()
+ {
+ this.invoke = function invalidateColumn_invoke()
+ {
+ // Make sure accessible subtree of XUL tree is created otherwise no
+ // name change events for cell accessibles are emitted.
+ var tree = getAccessible(gTree);
+ var child = tree.firstChild;
+ var walkDown = true;
+ while (child != tree) {
+ if (walkDown) {
+ var grandChild = child.firstChild;
+ if (grandChild) {
+ child = grandChild;
+ continue;
+ }
+ }
+
+ var sibling = child.nextSibling;
+ if (sibling) {
+ child = sibling;
+ walkDown = true;
+ continue;
+ }
+
+ child = child.parent;
+ walkDown = false;
+ }
+
+ // Fire 'TreeInvalidated' event by InvalidateColumn()
+ var firstCol = gTree.columns.getFirstColumn();
+ for (var i = 0; i < gView.rowCount; i++)
+ gView.setCellText(i, firstCol, "hey " + String(i) + "x0");
+
+ gTree.invalidateColumn(firstCol);
+ }
+
+ this.eventSeq =
+ [
+ new nameChangeChecker("invalidateColumn: ", 0, 0),
+ new nameChangeChecker("invalidateColumn: ", 1, 0),
+ new nameChangeChecker("invalidateColumn: ", 2, 0),
+ new nameChangeChecker("invalidateColumn: ", 3, 0),
+ new nameChangeChecker("invalidateColumn: ", 4, 0),
+ new nameChangeChecker("invalidateColumn: ", 5, 0),
+ new treeInvalidatedChecker("invalidateColumn", null, null, 0, 0)
+ ];
+
+ this.getID = function invalidateColumn_getID()
+ {
+ return "invalidate column";
+ }
+ }
+
+ /**
+ * Invalidates second row and checks name changed event for first treeitem
+ * (note, there are two name changed events on linux due to different
+ * accessible tree for xul:tree element) plus TreeInvalidated event.
+ */
+ function invalidateRow()
+ {
+ this.invoke = function invalidateRow_invoke()
+ {
+ // Fire 'TreeInvalidated' event by InvalidateRow()
+ // eslint-disable-next-line no-unused-vars
+ var colCount = gTree.columns.count;
+ var column = gTree.columns.getFirstColumn();
+ while (column) {
+ gView.setCellText(1, column, "aloha 1x" + String(column.index));
+ column = column.getNext();
+ }
+
+ gTree.invalidateRow(1);
+ }
+
+ this.eventSeq =
+ [
+ new nameChangeChecker("invalidateRow: ", 1, 0),
+ new nameChangeChecker("invalidateRow: ", 1, 1),
+ new rowNameChangeChecker("invalidateRow: ", 1),
+ new treeInvalidatedChecker("invalidateRow", 1, 1, null, null)
+ ];
+
+ this.getID = function invalidateRow_getID()
+ {
+ return "invalidate row";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ var gTree = null;
+ var gTreeView = null;
+ var gQueue = null;
+
+ // gA11yEventDumpID = "debug";
+ gA11yEventDumpToConsole = true; // debuggin
+
+ function doTest()
+ {
+ // Initialize the tree
+ gTree = document.getElementById("tree");
+ gView = new nsTableTreeView(5);
+
+ // Perform actions
+ gQueue = new eventQueue();
+
+ gQueue.push(new setTreeView());
+ gQueue.push(new insertRow());
+ gQueue.push(new invalidateColumn());
+ gQueue.push(new invalidateRow());
+
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=368835"
+ title="Fire TreeViewChanged/TreeRowCountChanged events.">
+ Mozilla Bug 368835
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=308564"
+ title="No accessibility events when data in a tree row changes.">
+ Mozilla Bug 308564
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=739524"
+ title="replace TreeViewChanged DOM event on direct call from XUL tree.">
+ Mozilla Bug 739524
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=743568"
+ title="Thunderbird message list tree emitting incorrect focus signals after message deleted.">
+ Mozilla Bug 743568
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="debug"/>
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col1" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/events/test_valuechange.html b/accessible/tests/mochitest/events/test_valuechange.html
new file mode 100644
index 0000000000..1ad3c0359d
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_valuechange.html
@@ -0,0 +1,315 @@
+<html>
+
+<head>
+ <title>Accessible value change events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript"
+ src="../value.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ // Value change invoker
+ function changeARIAValue(aNodeOrID, aValuenow, aValuetext) {
+ this.DOMNode = getNode(aNodeOrID);
+ this.eventSeq = [ new invokerChecker(aValuetext ?
+ EVENT_TEXT_VALUE_CHANGE :
+ EVENT_VALUE_CHANGE, this.DOMNode),
+ ];
+
+ this.invoke = function changeARIAValue_invoke() {
+ // Note: this should not fire an EVENT_VALUE_CHANGE when aria-valuetext
+ // is not empty
+ if (aValuenow != undefined)
+ this.DOMNode.setAttribute("aria-valuenow", aValuenow);
+
+ // Note: this should always fire an EVENT_VALUE_CHANGE
+ if (aValuetext != undefined)
+ this.DOMNode.setAttribute("aria-valuetext", aValuetext);
+ };
+
+ this.check = function changeARIAValue_check() {
+ var acc = getAccessible(aNodeOrID, [nsIAccessibleValue]);
+ if (!acc)
+ return;
+
+ // Note: always test against valuetext first because the existence of
+ // aria-valuetext takes precedence over aria-valuenow in gecko.
+ is(acc.value, (aValuetext != undefined) ? aValuetext : aValuenow,
+ "Wrong value of " + prettyName(aNodeOrID));
+ };
+
+ this.getID = function changeARIAValue_getID() {
+ return prettyName(aNodeOrID) + " value changed";
+ };
+ }
+
+ function changeValue(aID, aValue) {
+ this.DOMNode = getNode(aID);
+ this.eventSeq = [new invokerChecker(EVENT_TEXT_VALUE_CHANGE,
+ this.DOMNode),
+ ];
+
+ this.invoke = function changeValue_invoke() {
+ this.DOMNode.value = aValue;
+ };
+
+ this.check = function changeValue_check() {
+ var acc = getAccessible(this.DOMNode);
+ is(acc.value, aValue, "Wrong value for " + prettyName(aID));
+ };
+
+ this.getID = function changeValue_getID() {
+ return prettyName(aID) + " value changed";
+ };
+ }
+
+ function changeProgressValue(aID, aValue) {
+ this.DOMNode = getNode(aID);
+ this.eventSeq = [new invokerChecker(EVENT_VALUE_CHANGE, this.DOMNode)];
+
+ this.invoke = function changeProgressValue_invoke() {
+ this.DOMNode.value = aValue;
+ };
+
+ this.check = function changeProgressValue_check() {
+ var acc = getAccessible(this.DOMNode);
+ is(acc.value, aValue + "%", "Wrong value for " + prettyName(aID));
+ };
+
+ this.getID = function changeProgressValue_getID() {
+ return prettyName(aID) + " value changed";
+ };
+ }
+
+ function changeRangeValueWithMouse(aID) {
+ this.DOMNode = getNode(aID);
+ this.eventSeq = [new invokerChecker(EVENT_VALUE_CHANGE, this.DOMNode)];
+
+ this.invoke = function changeRangeValue_invoke() {
+ synthesizeMouse(getNode(aID), 5, 5, { });
+ };
+
+ this.finalCheck = function changeRangeValue_finalCheck() {
+ var acc = getAccessible(this.DOMNode);
+ is(acc.value, "0", "Wrong value for " + prettyName(aID));
+ };
+
+ this.getID = function changeRangeValue_getID() {
+ return prettyName(aID) + " range value changed";
+ };
+ }
+
+ function changeRangeValueWithA11yAPI(aID) {
+ this.DOMNode = getNode(aID);
+ let inputChecker = new invokerChecker("input", this.DOMNode);
+ inputChecker.eventTarget = "element";
+
+ let changeChecker = new invokerChecker("change", this.DOMNode);
+ changeChecker.eventTarget = "element";
+
+ this.eventSeq = [
+ inputChecker,
+ changeChecker,
+ new invokerChecker(EVENT_VALUE_CHANGE, this.DOMNode),
+ ];
+
+ this.invoke = function changeRangeValue_invoke() {
+ this.DOMNode.focus();
+ let acc = getAccessible(this.DOMNode, [nsIAccessibleValue]);
+ acc.currentValue = 0;
+ this.DOMNode.blur();
+ };
+
+ this.finalCheck = function changeRangeValue_finalCheck() {
+ var acc = getAccessible(this.DOMNode);
+ is(acc.value, "0", "Wrong value for " + prettyName(aID));
+ };
+
+ this.getID = function changeRangeValue_getID() {
+ return prettyName(aID) + " range value changed";
+ };
+ }
+
+ function changeSelectValue(aID, aKey, aValue) {
+ this.eventSeq =
+ [ new invokerChecker(EVENT_TEXT_VALUE_CHANGE, getAccessible(aID)) ];
+
+ this.invoke = function changeSelectValue_invoke() {
+ getAccessible(aID).takeFocus();
+ synthesizeKey(aKey, {}, window);
+ };
+
+ this.finalCheck = function changeSelectValue_finalCheck() {
+ is(getAccessible(aID).value, aValue, "Wrong value for " + prettyName(aID));
+ };
+
+ this.getID = function changeSelectValue_getID() {
+ return `${prettyName(aID)} closed select value change on '${aKey}'' key press`;
+ };
+ }
+
+ // enableLogging("DOMEvents");
+ // gA11yEventDumpToConsole = true;
+ function doTests() {
+
+ // Test initial values
+ testValue("slider_vn", "5", 5, 0, 1000, 0);
+ testValue("slider_vnvt", "plain", 0, 0, 5, 0);
+ testValue("slider_vt", "hi", 1.5, 0, 3, 0);
+ testValue("scrollbar", "5", 5, 0, 1000, 0);
+ testValue("splitter", "5", 5, 0, 1000, 0);
+ testValue("progress", "22%", 22, 0, 100, 0);
+ testValue("range", "6", 6, 0, 10, 1);
+ testValue("range2", "6", 6, 0, 10, 1);
+
+ // Test that elements which should not expose values do not
+ let separatorVal = getAccessible("separator", [nsIAccessibleValue], null, DONOTFAIL_IF_NO_INTERFACE);
+ ok(!separatorVal, "value interface is not exposed for separator");
+ let separatorAcc = getAccessible("separator");
+ ok(!separatorAcc.value, "Value text is not exposed for separator");
+
+ // Test value change events
+ gQueue = new eventQueue();
+
+ gQueue.push(new changeARIAValue("slider_vn", "6", undefined));
+ gQueue.push(new changeARIAValue("slider_vt", undefined, "hey!"));
+ gQueue.push(new changeARIAValue("slider_vnvt", "3", "sweet"));
+ gQueue.push(new changeARIAValue("scrollbar", "6", undefined));
+ gQueue.push(new changeARIAValue("splitter", "6", undefined));
+
+ gQueue.push(new changeValue("combobox", "hello"));
+
+ gQueue.push(new changeProgressValue("progress", "50"));
+ gQueue.push(new changeRangeValueWithMouse("range"));
+ gQueue.push(new changeRangeValueWithA11yAPI("range2"));
+
+ gQueue.push(new changeSelectValue("select", "VK_DOWN", "2nd"));
+ gQueue.push(new changeSelectValue("select", "3", "3rd"));
+
+ let iframeSelect = getAccessible("selectIframe").firstChild.firstChild;
+ gQueue.push(new changeSelectValue(iframeSelect, "VK_DOWN", "2"));
+
+ let shadowSelect = getAccessible("selectShadow").firstChild;
+ gQueue.push(new changeSelectValue(shadowSelect, "VK_DOWN", "2"));
+ gQueue.push(new changeValue("number", "2"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=478032"
+ title=" Fire delayed value changed event for aria-valuetext changes">
+ Mozilla Bug 478032
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=529289"
+ title="We dont expose new aria role 'scrollbar' and property aria-orientation">
+ Mozilla Bug 529289
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764"
+ title="Make HTML5 input@type=range element accessible">
+ Mozilla Bug 559764
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=703202"
+ title="ARIA comboboxes don't fire value change events">
+ Mozilla Bug 703202
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=761901"
+ title=" HTML5 progress accessible should fire value change event">
+ Mozilla Bug 761901
+ </a>
+
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <!-- ARIA sliders -->
+ <div id="slider_vn" role="slider" aria-valuenow="5"
+ aria-valuemin="0" aria-valuemax="1000">slider</div>
+
+ <div id="slider_vt" role="slider" aria-valuetext="hi"
+ aria-valuemin="0" aria-valuemax="3">greeting slider</div>
+
+ <div id="slider_vnvt" role="slider" aria-valuenow="0" aria-valuetext="plain"
+ aria-valuemin="0" aria-valuemax="5">sweetness slider</div>
+
+ <!-- ARIA scrollbar -->
+ <div id="scrollbar" role="scrollbar" aria-valuenow="5"
+ aria-valuemin="0" aria-valuemax="1000">slider</div>
+
+ <!-- ARIA separator which is focusable (i.e. a splitter) -->
+ <div id="splitter" role="separator" tabindex="0" aria-valuenow="5"
+ aria-valuemin="0" aria-valuemax="1000">splitter</div>
+
+ <!-- ARIA separator which is not focusable and should not expose values -->
+ <div id="separator" role="separator" aria-valuenow="5"
+ aria-valuemin="0" aria-valuemax="1000">splitter</div>
+
+ <!-- ARIA combobox -->
+ <input id="combobox" role="combobox" aria-autocomplete="inline">
+
+ <!-- progress bar -->
+ <progress id="progress" value="22" max="100"></progress>
+
+ <!-- input@type="range" -->
+ <input type="range" id="range" min="0" max="10" value="6">
+
+ <!-- input@type="range" -->
+ <input type="range" id="range2" min="0" max="10" value="6">
+
+ <select id="select">
+ <option>1st</option>
+ <option>2nd</option>
+ <option>3rd</option>
+ </select>
+
+ <iframe id="selectIframe"
+ src="data:text/html,<select id='iframeSelect'><option>1</option><option>2</option></select>">
+ </iframe>
+
+ <div id="selectShadow"></div>
+ <script>
+ let host = document.getElementById("selectShadow");
+ let shadow = host.attachShadow({mode: "open"});
+ let select = document.createElement("select");
+ select.id = "shadowSelect";
+ let option = document.createElement("option");
+ option.textContent = "1";
+ select.appendChild(option);
+ option = document.createElement("option");
+ option.textContent = "2";
+ select.appendChild(option);
+ shadow.appendChild(select);
+ </script>
+
+ <input type="number" id="number" value="1">
+</body>
+</html>
diff --git a/accessible/tests/mochitest/focus/a11y.ini b/accessible/tests/mochitest/focus/a11y.ini
new file mode 100644
index 0000000000..905d38882c
--- /dev/null
+++ b/accessible/tests/mochitest/focus/a11y.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_focus_radio.xhtml]
+[test_focusedChild.html]
+skip-if = (os == 'win' && os_version != '6.1') # bug 845134
+[test_takeFocus.html]
+[test_takeFocus.xhtml]
diff --git a/accessible/tests/mochitest/focus/test_focus_radio.xhtml b/accessible/tests/mochitest/focus/test_focus_radio.xhtml
new file mode 100644
index 0000000000..717d9976b6
--- /dev/null
+++ b/accessible/tests/mochitest/focus/test_focus_radio.xhtml
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Tests for Accessible TakeFocus on Radio Elements">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../promisified-events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ async function doTests() {
+ let radio1 = getAccessible("radio1");
+ let focused = waitForEvent(EVENT_FOCUS, radio1);
+ radio1.takeFocus();
+ await focused;
+ // radio1 wasn't selected. Ensure that takeFocus didn't change that.
+ testStates(radio1, STATE_FOCUSED, 0, STATE_CHECKED);
+
+ // Test focusing another radio in the group while the group is still
+ // focused.
+ let radio2 = getAccessible("radio2");
+ focused = waitForEvent(EVENT_FOCUS, radio2);
+ radio2.takeFocus();
+ await focused;
+ testStates(radio2, STATE_FOCUSED | STATE_CHECKED);
+
+ let groupEl = document.getElementById("radiogroup");
+ // Selecting an item also focuses it.
+ focused = waitForEvent(EVENT_FOCUS, radio1);
+ groupEl.value = "1";
+ await focused;
+ testStates(radio1, STATE_FOCUSED | STATE_CHECKED);
+
+ // If an item is already selected but not focused, selecting it again
+ // focuses it.
+ focused = waitForEvent(EVENT_FOCUS, radio2);
+ radio2.takeFocus();
+ await focused;
+ testStates(radio2, STATE_FOCUSED, 0, STATE_CHECKED);
+ // radio1 is selected but not focused.
+ // Select radio1 again, which should focus it.
+ focused = waitForEvent(EVENT_FOCUS, radio1);
+ groupEl.value = "1";
+ await focused;
+ testStates(radio1, STATE_FOCUSED | STATE_CHECKED);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <radiogroup id="radiogroup" value="2">
+ <radio id="radio1" value="1"/>
+ <radio id="radio2" value="2"/>
+ </radiogroup>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/focus/test_focusedChild.html b/accessible/tests/mochitest/focus/test_focusedChild.html
new file mode 100644
index 0000000000..d12e229b48
--- /dev/null
+++ b/accessible/tests/mochitest/focus/test_focusedChild.html
@@ -0,0 +1,81 @@
+<html>
+
+<head>
+ <title>nsIAccessible::focusedChild testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function openWnd() {
+ this.eventSeq = [ new invokerChecker(EVENT_FOCUS,
+ getDialogAccessible,
+ this) ];
+
+ this.invoke = function openWnd_invoke() {
+ this.dialog = window.browsingContext.topChromeWindow
+ .openDialog("about:mozilla",
+ "AboutMozilla",
+ "chrome,width=600,height=600");
+ };
+
+ this.finalCheck = function openWnd_finalCheck() {
+ var app = getApplicationAccessible();
+ is(app.focusedChild, getDialogAccessible(this),
+ "Wrong focused child");
+
+ this.dialog.close();
+ };
+
+ this.getID = function openWnd_getID() {
+ return "focusedChild for application accessible";
+ };
+
+ function getDialogAccessible(aInvoker) {
+ return getAccessible(aInvoker.dialog.document);
+ }
+ }
+
+ gA11yEventDumpToConsole = true;
+ var gQueue = null;
+
+ function doTest() {
+ enableLogging("focus,doclifecycle");
+ gQueue = new eventQueue();
+
+ gQueue.push(new openWnd());
+
+ gQueue.onFinish = function() { disableLogging(); };
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=677467"
+ title="focusedChild crashes on application accessible">
+ Mozilla Bug 677467
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/focus/test_takeFocus.html b/accessible/tests/mochitest/focus/test_takeFocus.html
new file mode 100644
index 0000000000..752ed66b36
--- /dev/null
+++ b/accessible/tests/mochitest/focus/test_takeFocus.html
@@ -0,0 +1,109 @@
+<html>
+
+<head>
+ <title>nsIAccessible::takeFocus testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function takeFocusInvoker(aID) {
+ this.accessible = getAccessible(aID);
+
+ this.eventSeq = [ new focusChecker(this.accessible) ];
+
+ this.invoke = function takeFocusInvoker_invoke() {
+ this.accessible.takeFocus();
+ };
+
+ this.getID = function takeFocusInvoker_getID() {
+ return "takeFocus for " + prettyName(aID);
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest() {
+ disableLogging(); // from test_focusedChild
+ gQueue = new eventQueue();
+
+ gQueue.push(new takeFocusInvoker("aria-link"));
+ gQueue.push(new takeFocusInvoker("aria-link2"));
+ gQueue.push(new takeFocusInvoker("link"));
+ gQueue.push(new takeFocusInvoker("item2"));
+ gQueue.push(new takeFocusInvoker(document));
+ gQueue.push(new takeFocusInvoker("lb_item2"));
+ gQueue.push(new takeFocusInvoker(document));
+ gQueue.push(new takeFocusInvoker("lb_item3.2"));
+ gQueue.push(new takeFocusInvoker(document));
+ gQueue.push(new takeFocusInvoker("lb_item3.1"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=429547"
+ title="Support aria-activedescendant usage in nsIAccesible::TakeFocus()">
+ Mozilla Bug 429547
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=452710"
+ title="nsIAccessible::takeFocus testing">
+ Mozilla Bug 452710
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=706067"
+ title="Make takeFocus work on widget items">
+ Mozilla Bug 706067
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <span id="aria-link" role="link" tabindex="0">link</span>
+ <span id="aria-link2" role="link" tabindex="0">link</span>
+
+ <a id="link" href="">link</a>
+
+ <div role="listbox" aria-activedescendant="item1" id="container" tabindex="1">
+ <div role="option" id="item1">item1</div>
+ <div role="option" id="item2">item2</div>
+ <div role="option" id="item3">item3</div>
+ </div>
+
+ <select id="listbox" size="5">
+ <option id="lb_item1">item1</option>
+ <option id="lb_item2">item2</option>
+ <optgroup>
+ <option id="lb_item3.1">item 3.1</option>
+ <option id="lb_item3.2">item 3.2</option>
+ </optgroup>
+ </select>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/focus/test_takeFocus.xhtml b/accessible/tests/mochitest/focus/test_takeFocus.xhtml
new file mode 100644
index 0000000000..127f47d067
--- /dev/null
+++ b/accessible/tests/mochitest/focus/test_takeFocus.xhtml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible focus testing">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function takeFocusInvoker(aID, aArgConverterFunc)
+ {
+ this.targetFunc = aArgConverterFunc ? aArgConverterFunc : getAccessible;
+
+ this.eventSeq = [ new focusChecker(this.targetFunc, aID) ];
+
+ this.invoke = function takeFocusInvoker_invoke()
+ {
+ this.targetFunc.call(null, aID).takeFocus();
+ }
+
+ this.getID = function takeFocusInvoker_getID()
+ {
+ return "takeFocus for " + prettyName(aID);
+ }
+ }
+
+ function getLastChild(aID)
+ {
+ return getAccessible(aID).lastChild;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTests()
+ {
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ gQueue.push(new takeFocusInvoker("tree", getLastChild));
+ gQueue.push(new takeFocusInvoker("listitem2"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTests, "tree", new nsTableTreeView(5));
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=706067"
+ title="Make takeFocus work on widget items">
+ Mozilla Bug 706067
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col1" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+
+ <richlistbox id="listbox">
+ <richlistitem id="listitem1"><label value="item1"/></richlistitem>
+ <richlistitem id="listitem2"><label value="item2"/></richlistitem>
+ </richlistbox>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/formimage.png b/accessible/tests/mochitest/formimage.png
new file mode 100644
index 0000000000..10e44bf920
--- /dev/null
+++ b/accessible/tests/mochitest/formimage.png
Binary files differ
diff --git a/accessible/tests/mochitest/grid.js b/accessible/tests/mochitest/grid.js
new file mode 100644
index 0000000000..4a2e01ee9b
--- /dev/null
+++ b/accessible/tests/mochitest/grid.js
@@ -0,0 +1,142 @@
+/* import-globals-from common.js */
+
+/**
+ * Create grid object based on HTML table.
+ */
+function grid(aTableIdentifier) {
+ this.getRowCount = function getRowCount() {
+ return this.table.rows.length - (this.table.tHead ? 1 : 0);
+ };
+ this.getColsCount = function getColsCount() {
+ return this.table.rows[0].cells.length;
+ };
+
+ this.getRowAtIndex = function getRowAtIndex(aIndex) {
+ return this.table.rows[this.table.tHead ? aIndex + 1 : aIndex];
+ };
+
+ this.getMaxIndex = function getMaxIndex() {
+ return this.getRowCount() * this.getColsCount() - 1;
+ };
+
+ this.getCellAtIndex = function getCellAtIndex(aIndex) {
+ var colsCount = this.getColsCount();
+
+ var rowIdx = Math.floor(aIndex / colsCount);
+ var colIdx = aIndex % colsCount;
+
+ var row = this.getRowAtIndex(rowIdx);
+ return row.cells[colIdx];
+ };
+
+ this.getIndexByCell = function getIndexByCell(aCell) {
+ var colIdx = aCell.cellIndex;
+
+ var rowIdx = aCell.parentNode.rowIndex;
+ if (this.table.tHead) {
+ rowIdx -= 1;
+ }
+
+ var colsCount = this.getColsCount();
+ return rowIdx * colsCount + colIdx;
+ };
+
+ this.getCurrentCell = function getCurrentCell() {
+ var rowCount = this.table.rows.length;
+ var colsCount = this.getColsCount();
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ var cell = this.table.rows[rowIdx].cells[colIdx];
+ if (cell.hasAttribute("tabindex")) {
+ return cell;
+ }
+ }
+ }
+ return null;
+ };
+
+ this.initGrid = function initGrid() {
+ this.table.addEventListener("keypress", this);
+ this.table.addEventListener("click", this);
+ };
+
+ this.handleEvent = function handleEvent(aEvent) {
+ if (aEvent instanceof KeyboardEvent) {
+ this.handleKeyEvent(aEvent);
+ } else {
+ this.handleClickEvent(aEvent);
+ }
+ };
+
+ this.handleKeyEvent = function handleKeyEvent(aEvent) {
+ if (aEvent.target.localName != "td") {
+ return;
+ }
+
+ var cell = aEvent.target;
+ switch (aEvent.keyCode) {
+ case KeyboardEvent.DOM_VK_UP: {
+ let colsCount = this.getColsCount();
+ let idx = this.getIndexByCell(cell);
+ var upidx = idx - colsCount;
+ if (upidx >= 0) {
+ cell.removeAttribute("tabindex");
+ var upcell = this.getCellAtIndex(upidx);
+ upcell.setAttribute("tabindex", "0");
+ upcell.focus();
+ }
+ break;
+ }
+ case KeyboardEvent.DOM_VK_DOWN: {
+ let colsCount = this.getColsCount();
+ let idx = this.getIndexByCell(cell);
+ var downidx = idx + colsCount;
+ if (downidx <= this.getMaxIndex()) {
+ cell.removeAttribute("tabindex");
+ var downcell = this.getCellAtIndex(downidx);
+ downcell.setAttribute("tabindex", "0");
+ downcell.focus();
+ }
+ break;
+ }
+ case KeyboardEvent.DOM_VK_LEFT: {
+ let idx = this.getIndexByCell(cell);
+ if (idx > 0) {
+ cell.removeAttribute("tabindex");
+ var prevcell = this.getCellAtIndex(idx - 1);
+ prevcell.setAttribute("tabindex", "0");
+ prevcell.focus();
+ }
+ break;
+ }
+ case KeyboardEvent.DOM_VK_RIGHT: {
+ let idx = this.getIndexByCell(cell);
+ if (idx < this.getMaxIndex()) {
+ cell.removeAttribute("tabindex");
+ var nextcell = this.getCellAtIndex(idx + 1);
+ nextcell.setAttribute("tabindex", "0");
+ nextcell.focus();
+ }
+ break;
+ }
+ }
+ };
+
+ this.handleClickEvent = function handleClickEvent(aEvent) {
+ if (aEvent.target.localName != "td") {
+ return;
+ }
+
+ var curCell = this.getCurrentCell();
+ var cell = aEvent.target;
+
+ if (cell != curCell) {
+ curCell.removeAttribute("tabindex");
+ cell.setAttribute("tabindex", "0");
+ cell.focus();
+ }
+ };
+
+ this.table = getNode(aTableIdentifier);
+ this.initGrid();
+}
diff --git a/accessible/tests/mochitest/hittest/a11y.ini b/accessible/tests/mochitest/hittest/a11y.ini
new file mode 100644
index 0000000000..6dc059bd48
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/a11y.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+support-files = zoom_tree.xhtml
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+
+[test_browser.html]
+[test_general.html]
+[test_menu.xhtml]
+[test_shadowroot.html]
+support-files = test_shadowroot_subframe.html
+[test_zoom.html]
+[test_zoom_text.html]
+[test_zoom_tree.xhtml]
diff --git a/accessible/tests/mochitest/hittest/test_browser.html b/accessible/tests/mochitest/hittest/test_browser.html
new file mode 100644
index 0000000000..c14df7d736
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_browser.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessible::childAtPoint() from browser tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // Hit testing. See bug #726097
+ getNode("hittest").scrollIntoView(true);
+
+ var hititem = getAccessible("hititem");
+ var hittest = getAccessible("hittest");
+
+ var [hitX, hitY, hitWidth, hitHeight] = getBounds(hititem);
+ var tgtX = hitX + hitWidth / 2;
+ var tgtY = hitY + hitHeight / 2;
+
+ var rootAcc = getRootAccessible();
+ var docAcc = getAccessible(document);
+ var outerDocAcc = docAcc.parent;
+
+ var hitAcc = rootAcc.getDeepestChildAtPoint(tgtX, tgtY);
+ is(hitAcc, hititem, "Hit match at " + tgtX + "," + tgtY +
+ ". Found: " + prettyName(hitAcc));
+ var hitAcc2 = docAcc.getDeepestChildAtPoint(tgtX, tgtY);
+ is(hitAcc, hitAcc2, "Hit match at " + tgtX + "," + tgtY +
+ ". Found: " + prettyName(hitAcc2));
+
+ hitAcc = outerDocAcc.getChildAtPoint(tgtX, tgtY);
+ is(hitAcc, docAcc, "Hit match at " + tgtX + "," + tgtY +
+ ". Found: " + prettyName(hitAcc));
+ hitAcc = docAcc.getChildAtPoint(tgtX, tgtY);
+ is(hitAcc, hittest, "Hit match at " + tgtX + "," + tgtY +
+ ". Found: " + prettyName(hitAcc));
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=726097"
+ title="nsIAccessible::childAtPoint() from browser tests">Mozilla Bug 726097</a>
+
+ <div id="hittest">
+ <div id="hititem"><span role="image">img</span>item</div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hittest/test_general.html b/accessible/tests/mochitest/hittest/test_general.html
new file mode 100644
index 0000000000..f5afd18446
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_general.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessible::childAtPoint() tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function doPreTest() {
+ waitForImageMap("imgmap", doTest);
+ }
+
+ function doTest() {
+ // Not specific case, child and deepchild testing.
+ var list = getAccessible("list");
+ var listitem = getAccessible("listitem");
+ var image = getAccessible("image");
+if (!MAC) {
+ testChildAtPoint(list, 1, 1, listitem, image.firstChild);
+} else {
+ todo(false, "Bug 746974 - children must match on all platforms, disable failing test on Mac");
+}
+
+ // ::MustPrune case (in this case childAtPoint doesn't look inside a
+ // textbox), point is inside of textbox.
+ var txt = getAccessible("txt");
+ testChildAtPoint(txt, 1, 1, txt, txt);
+
+ // ::MustPrune case, point is outside of textbox accessible but is in
+ // document.
+ testChildAtPoint(txt, -1, 1, null, null);
+
+ // ::MustPrune case, point is outside of root accessible.
+ testChildAtPoint(txt, -10000, 10000, null, null);
+
+ // Not specific case, point is inside of btn accessible.
+ var btn = getAccessible("btn");
+ testChildAtPoint(btn, 1, 1, btn, btn);
+
+ // Not specific case, point is outside of btn accessible.
+ testChildAtPoint(btn, -1, 1, null, null);
+
+ // Out of flow accessible testing, do not return out of flow accessible
+ // because it's not a child of the accessible even visually it is.
+ var rectArea = getNode("area").getBoundingClientRect();
+ var outOfFlow = getNode("outofflow");
+ outOfFlow.style.left = rectArea.left + "px";
+ outOfFlow.style.top = rectArea.top + "px";
+
+ testChildAtPoint("area", 1, 1, "area", "area");
+
+ // Test image maps. Their children are not in the layout tree.
+ var theLetterA = getAccessible("imgmap").firstChild;
+ hitTest("imgmap", theLetterA, theLetterA);
+ hitTest("container", "imgmap", theLetterA);
+
+ // hit testing for element contained by zero-width element
+ hitTest("container2", "container2_input", "container2_input");
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=491657"
+ title="nsIAccessible::childAtPoint() tests">Mozilla Bug 491657</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="list" id="list">
+ <div role="listitem" id="listitem"><span role="image" id="image">img</span>item</div>
+ </div>
+
+ <span role="button">button1</span><span role="button" id="btn">button2</span>
+
+ <span role="textbox">textbox1</span><span role="textbox" id="txt">textbox2</span>
+
+ <div id="outofflow" style="width: 10px; height: 10px; position: absolute; left: 0px; top: 0px; background-color: yellow;">
+ </div>
+ <div id="area" style="width: 100px; height: 100px; background-color: blue;"></div>
+
+ <map name="atoz_map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,15,15" alt="thelettera" shape="rect"/>
+ </map>
+
+ <div id="container">
+ <img id="imgmap" width="447" height="15" usemap="#atoz_map" src="../letters.gif"/>
+ </div>
+
+ <div id="container2" style="width: 0px">
+ <input id="container2_input">
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hittest/test_menu.xhtml b/accessible/tests/mochitest/hittest/test_menu.xhtml
new file mode 100644
index 0000000000..d80b31305d
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_menu.xhtml
@@ -0,0 +1,133 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Hit testing for XUL menus">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../layout.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function openMenu(aMenuID, aMenuPopupID, aMenuItemID)
+ {
+ this.menuNode = getNode(aMenuID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.menuNode)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ this.menuNode.open = true;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ hitTest(aMenuPopupID, aMenuItemID, aMenuItemID);
+ }
+
+ this.getID = function openMenu_invoke()
+ {
+ return "open menu '" + aMenuID + "' and do hit testing";
+ }
+ }
+
+ function closeMenu(aID, aSubID, aSub2ID)
+ {
+ this.menuNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, document)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ this.menuNode.open = false;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ testStates(aID, 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates(aSubID, STATE_INVISIBLE, 0, STATE_OFFSCREEN);
+ testStates(aSub2ID, STATE_INVISIBLE, 0, STATE_OFFSCREEN);
+ }
+
+ this.getID = function openMenu_invoke()
+ {
+ return "open menu and test states";
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ if (LINUX) {
+ ok(true, "No tests is running on Linux");
+ SimpleTest.finish();
+ return;
+ }
+
+ getNode("mi_file1").scrollIntoView(true);
+
+ gQueue = new eventQueue();
+ gQueue.push(new openMenu("mi_file1", "mp_file1", "mi_file1.1"));
+ gQueue.push(new openMenu("mi_file1.2", "mp_file1.2", "mi_file1.2.1"));
+ gQueue.push(new closeMenu("mi_file1", "mi_file1.1", "mi_file1.2.1"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=670087"
+ title="AccessibleObjectFromPoint returns incorrect accessible for popup menus">
+ Bug 670087
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <menubar>
+ <menu label="File" id="mi_file1">
+ <menupopup id="mp_file1">
+ <menuitem label="SubFile" id="mi_file1.1"/>
+ <menu label="SubFile2" id="mi_file1.2">
+ <menupopup style="max-height: 5em;" id="mp_file1.2">
+ <menuitem label="SubSubFile" id="mi_file1.2.1"/>
+ <menuitem label="SubSubFile2" id="mi_file1.2.2"/>
+ <menuitem label="SubSubFile3" id="mi_file1.2.3"/>
+ <menuitem label="SubSubFile4" id="mi_file1.2.4"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menubar>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/hittest/test_shadowroot.html b/accessible/tests/mochitest/hittest/test_shadowroot.html
new file mode 100644
index 0000000000..6acdc47987
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_shadowroot.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>ShadowRoot hit tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ title="Test getChildAtPoint works for shadow DOM content"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1027315">
+ Mozilla Bug 1027315
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ window.onload = () => {
+ var iframe = document.createElement("iframe");
+ iframe.src = "test_shadowroot_subframe.html";
+ document.body.appendChild(iframe);
+ };
+
+ </script>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hittest/test_shadowroot_subframe.html b/accessible/tests/mochitest/hittest/test_shadowroot_subframe.html
new file mode 100644
index 0000000000..7a365e7b93
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_shadowroot_subframe.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>ShadowRoot hit tests</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../layout.js"></script>
+
+ <script type="application/javascript">
+ let SimpleTest = window.parent.SimpleTest;
+ let ok = window.parent.ok;
+ let is = window.parent.is;
+
+ function doTest() {
+ var componentAcc = getAccessible("component1");
+ testChildAtPoint(componentAcc, 1, 1, componentAcc.firstChild,
+ componentAcc.firstChild);
+
+ componentAcc = getAccessible("component2");
+ testChildAtPoint(componentAcc, 1, 1, componentAcc.firstChild,
+ componentAcc.firstChild);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+<body>
+ <div role="group" class="components" id="component1" style="display: inline-block;">
+ <!--
+ <div role="button" id="component-child"
+ style="width: 100px; height: 100px; background-color: pink;">
+ </div>
+ -->
+ </div>
+ <div role="group" class="components" id="component2" style="display: inline-block;">
+ <!--
+ <button>Hello world</button>
+ -->
+ </div>
+ <script>
+ // This routine adds the comment children of each 'component' to its
+ // shadow root.
+ var components = document.querySelectorAll(".components");
+ for (var i = 0; i < components.length; i++) {
+ var component = components[i];
+ var shadow = component.attachShadow({mode: "open"});
+ for (var child = component.firstChild; child; child = child.nextSibling) {
+ if (child.nodeType === 8)
+ // eslint-disable-next-line no-unsanitized/property
+ shadow.innerHTML = child.data;
+ }
+ }
+ </script>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hittest/test_zoom.html b/accessible/tests/mochitest/hittest/test_zoom.html
new file mode 100644
index 0000000000..70e71f7a6d
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_zoom.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>childAtPoint when page is zoomed</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+if (!MAC) {
+ var tabDocument = currentTabDocument();
+ var p1 = tabDocument.body.firstElementChild;
+ var p2 = tabDocument.body.lastElementChild;
+
+ hitTest(tabDocument, p1, p1.firstChild);
+ hitTest(tabDocument, p2, p2.firstChild);
+
+ zoomDocument(tabDocument, 2.0);
+
+ hitTest(tabDocument, p1, p1.firstChild);
+ hitTest(tabDocument, p2, p2.firstChild);
+
+ closeBrowserWindow();
+} else {
+ todo(false, "Bug 746974 - deepest child must be correct on all platforms, disabling on Mac!");
+}
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTest,
+ "data:text/html,<html><body><p>para 1</p><p>para 2</p></body></html>",
+ { left: 100, top: 100 });
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942"
+ title="childAtPoint may return incorrect accessibles when page zoomed">
+ Mozilla Bug 727942
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hittest/test_zoom_text.html b/accessible/tests/mochitest/hittest/test_zoom_text.html
new file mode 100644
index 0000000000..4dc92b9639
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_zoom_text.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>getOffsetAtPoint when page is zoomed</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var hyperText = getNode("paragraph");
+ var textNode = hyperText.firstChild;
+ let [x, y, width, height] = getBounds(textNode);
+ testOffsetAtPoint(hyperText, x + width / 2, y + height / 2,
+ COORDTYPE_SCREEN_RELATIVE,
+ hyperText.textContent.length / 2);
+
+ zoomDocument(document, 2.0);
+
+ document.body.offsetTop; // getBounds doesn't flush layout on its own, looks like.
+
+ [x, y, width, height] = getBounds(textNode);
+ testOffsetAtPoint(hyperText, x + width / 2, y + height / 2,
+ COORDTYPE_SCREEN_RELATIVE,
+ hyperText.textContent.length / 2);
+
+ zoomDocument(document, 1.0);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942"
+ title="getOffsetAtPoint returns incorrect value when page is zoomed">
+ Mozilla Bug 727942
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <p id="paragraph" style="font-family: monospace;">Болтали две сороки</p>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hittest/test_zoom_tree.xhtml b/accessible/tests/mochitest/hittest/test_zoom_tree.xhtml
new file mode 100644
index 0000000000..54cb37c871
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_zoom_tree.xhtml
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="nsIAccessible::getChildAtPoint and getDeepestChildAtPoint">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/chrome-harness.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../layout.js" />
+ <script type="application/javascript"
+ src="../browser.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ var tabDocument = currentTabDocument();
+ var tabWindow = currentTabWindow();
+
+ var tree = tabDocument.getElementById("tree");
+ var treecols = tabDocument.getElementById("treecols");
+ var treecol1 = tabDocument.getElementById("treecol1");
+
+ // tree columns
+ hitTest(tree, treecols, treecol1);
+
+ // tree rows and cells
+ var treeRect = tree.treeBody.getBoundingClientRect();
+ var rect = tree.getCoordsForCellItem(1, tree.columns[0], "cell");
+
+ var treeAcc = getAccessible(tree, [nsIAccessibleTable]);
+ var cellAcc = treeAcc.getCellAt(1, 0);
+ var rowAcc = cellAcc.parent;
+
+ var cssX = rect.x + treeRect.x;
+ var cssY = rect.y + treeRect.y;
+ var [x, y] = CSSToDevicePixels(tabWindow, cssX, cssY);
+
+ testChildAtPoint(treeAcc, x, y, rowAcc, cellAcc);
+ testChildAtPoint(rowAcc, x, y, cellAcc, cellAcc);
+
+ // do zoom
+ zoomDocument(tabDocument, 1.5);
+
+ // tree columns
+ hitTest(tree, treecols, treecol1);
+
+ // tree rows and cells
+ [x, y] = CSSToDevicePixels(tabWindow, cssX, cssY);
+ testChildAtPoint(treeAcc, x, y, rowAcc, cellAcc);
+ testChildAtPoint(rowAcc, x, y, cellAcc, cellAcc);
+
+ closeBrowserWindow();
+ SimpleTest.finish();
+ }
+
+ function prepareTest()
+ {
+ var tabDocument = currentTabDocument();
+ var tree = tabDocument.getElementById("tree");
+ loadXULTreeAndDoTest(doTest, tree, new nsTableTreeView(5));
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(prepareTest,
+ getRootDirectory(window.location.href) + "zoom_tree.xhtml",
+ { left: 100, top: 100 });
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=471493"
+ title=" crash [@ nsPropertyTable::GetPropertyInternal]">
+ Mozilla Bug 471493
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/hittest/zoom_tree.xhtml b/accessible/tests/mochitest/hittest/zoom_tree.xhtml
new file mode 100644
index 0000000000..52ec0932ab
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/zoom_tree.xhtml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="nsIAccessible::getChildAtPoint and getDeepestChildAtPoint for XUL trees">
+
+ <tree id="tree" flex="1">
+ <treecols id="treecols">
+ <treecol id="treecol1" flex="1" primary="true" label="column"/>
+ <treecol id="treecol2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+
+</window>
+
diff --git a/accessible/tests/mochitest/hyperlink/a11y.ini b/accessible/tests/mochitest/hyperlink/a11y.ini
new file mode 100644
index 0000000000..60804a70c8
--- /dev/null
+++ b/accessible/tests/mochitest/hyperlink/a11y.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files = hyperlink.js
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+
+[test_general.html]
+[test_general.xhtml]
diff --git a/accessible/tests/mochitest/hyperlink/hyperlink.js b/accessible/tests/mochitest/hyperlink/hyperlink.js
new file mode 100644
index 0000000000..93caa9090c
--- /dev/null
+++ b/accessible/tests/mochitest/hyperlink/hyperlink.js
@@ -0,0 +1,46 @@
+/* import-globals-from ../common.js */
+/* import-globals-from ../events.js */
+/* import-globals-from ../states.js */
+
+/**
+ * Focus hyperlink invoker.
+ *
+ * @param aID [in] hyperlink identifier
+ * @param aSelectedAfter [in] specifies if hyperlink is selected/focused after
+ * the focus
+ */
+function focusLink(aID, aSelectedAfter) {
+ this.node = getNode(aID);
+ this.accessible = getAccessible(this.node);
+
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [];
+
+ var checker = new invokerChecker(EVENT_FOCUS, this.accessible);
+ if (aSelectedAfter) {
+ this.eventSeq.push(checker);
+ } else {
+ this.unexpectedEventSeq.push(checker);
+ }
+
+ this.invoke = function focusLink_invoke() {
+ var expectedStates = aSelectedAfter ? STATE_FOCUSABLE : 0;
+ var unexpectedStates =
+ (!aSelectedAfter ? STATE_FOCUSABLE : 0) | STATE_FOCUSED;
+ testStates(aID, expectedStates, 0, unexpectedStates, 0);
+
+ this.node.focus();
+ };
+
+ this.finalCheck = function focusLink_finalCheck() {
+ var expectedStates = aSelectedAfter ? STATE_FOCUSABLE | STATE_FOCUSED : 0;
+ var unexpectedStates = !aSelectedAfter
+ ? STATE_FOCUSABLE | STATE_FOCUSED
+ : 0;
+ testStates(aID, expectedStates, 0, unexpectedStates, 0);
+ };
+
+ this.getID = function focusLink_getID() {
+ return "focus hyperlink " + prettyName(aID);
+ };
+}
diff --git a/accessible/tests/mochitest/hyperlink/test_general.html b/accessible/tests/mochitest/hyperlink/test_general.html
new file mode 100644
index 0000000000..c652f1d962
--- /dev/null
+++ b/accessible/tests/mochitest/hyperlink/test_general.html
@@ -0,0 +1,279 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418368
+-->
+<head>
+ <title>nsIHyperLinkAccessible chrome tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript"
+ src="hyperlink.js"></script>
+
+ <script type="application/javascript">
+ function testThis(aID, aAcc, aRole, aAnchors, aName, aValid, aStartIndex,
+ aEndIndex) {
+ testRole(aAcc, aRole);
+ is(aAcc.anchorCount, aAnchors, "Wrong number of anchors for ID "
+ + aID + "!");
+ is(aAcc.getAnchor(0).name, aName, "Wrong name for ID "
+ + aID + "!");
+ is(aAcc.valid, aValid, "No correct valid state for ID "
+ + aID + "!");
+ is(aAcc.startIndex, aStartIndex, "Wrong startIndex value for ID "
+ + aID + "!");
+ is(aAcc.endIndex, aEndIndex, "Wrong endIndex value for ID "
+ + aID + "!");
+ }
+
+ function testAction(aId, aAcc, aActionName) {
+ var actionCount = aActionName ? 1 : 0;
+ is(aAcc.actionCount, actionCount,
+ "Wrong actions number for ID " + aId);
+ try {
+ is(aAcc.getActionName(0), aActionName,
+ "Wrong action name for ID " + aId);
+ } catch (e) {
+ if (actionCount)
+ ok(false, "Exception on action name getting for ID " + aId);
+ else
+ ok(true, "Correct action name for ID " + aId);
+ }
+ }
+
+ // gA11yEventDumpToConsole = true; // debug stuff
+ function doPreTest() {
+ waitForImageMap("imgmap", doTest);
+ }
+
+ var gQueue = null;
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // normal hyperlink
+ var normalHyperlinkAcc = getAccessible("NormalHyperlink",
+ [nsIAccessibleHyperLink]);
+ testThis("NormalHyperlink", normalHyperlinkAcc, ROLE_LINK, 1,
+ "Mozilla Foundation", true, 17, 18);
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ is(normalHyperlinkAcc.getURI(0).spec, "http://www.mozilla.org/",
+ "URI wrong for normalHyperlinkElement!");
+ testStates(normalHyperlinkAcc, STATE_LINKED, 0);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA hyperlink
+ var ariaHyperlinkAcc = getAccessible("AriaHyperlink",
+ [nsIAccessibleHyperLink]);
+ testThis("AriaHyperlink", ariaHyperlinkAcc, ROLE_LINK, 1,
+ "Mozilla Foundation Home", true, 30, 31);
+ testStates(ariaHyperlinkAcc, STATE_LINKED, 0);
+ testAction("AriaHyperlink", ariaHyperlinkAcc, "click");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA hyperlink with status invalid
+ var invalidAriaHyperlinkAcc = getAccessible("InvalidAriaHyperlink",
+ [nsIAccessibleHyperLink]);
+ is(invalidAriaHyperlinkAcc.valid, false, "Should not be valid!");
+ testStates(invalidAriaHyperlinkAcc, STATE_LINKED, 0);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // image map and its link children
+
+ var imageMapHyperlinkAcc = getAccessible("imgmap",
+ [nsIAccessibleHyperLink]);
+ testThis("imgmap", imageMapHyperlinkAcc, ROLE_IMAGE_MAP, 2, "b", true,
+ 79, 80);
+ is(imageMapHyperlinkAcc.getURI(0).spec,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#b", "URI wrong!");
+ is(imageMapHyperlinkAcc.getURI(1).spec,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#a", "URI wrong!");
+ testStates(imageMapHyperlinkAcc, 0, 0);
+
+ var area1 = getAccessible(imageMapHyperlinkAcc.firstChild,
+ [nsIAccessibleHyperLink]);
+ testThis("Area1", area1, ROLE_LINK, 1, "b", true, 0, 1);
+ is(area1.getURI(0).spec,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#b", "URI wrong!");
+ testStates(area1, (STATE_LINKED));
+
+ var area2 = getAccessible(area1.nextSibling,
+ [nsIAccessibleHyperLink]);
+ testThis("Area2", area2, ROLE_LINK, 1, "a", true, 1, 2);
+ is(area2.getURI(0).spec,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#a", "URI wrong!");
+ testStates(area2, (STATE_LINKED));
+
+ // ////////////////////////////////////////////////////////////////////////
+ // empty hyperlink
+ var EmptyHLAcc = getAccessible("emptyLink",
+ [nsIAccessibleHyperLink]);
+ testThis("emptyLink", EmptyHLAcc, ROLE_LINK, 1, null, true, 93, 94);
+ testStates(EmptyHLAcc, (STATE_FOCUSABLE | STATE_LINKED), 0);
+ testAction("emptyLink", EmptyHLAcc, "jump");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // normal hyperlink with embedded span
+ var hyperlinkWithSpanAcc = getAccessible("LinkWithSpan",
+ [nsIAccessibleHyperLink]);
+ testThis("LinkWithSpan", hyperlinkWithSpanAcc, ROLE_LINK, 1,
+ "Heise Online", true, 119, 120);
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ is(hyperlinkWithSpanAcc.getURI(0).spec, "http://www.heise.de/",
+ "URI wrong for hyperlinkElementWithSpan!");
+ testStates(hyperlinkWithSpanAcc, STATE_LINKED, 0);
+ testAction("LinkWithSpan", hyperlinkWithSpanAcc, "jump");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Named anchor, should never have state_linked
+ var namedAnchorAcc = getAccessible("namedAnchor",
+ [nsIAccessibleHyperLink]);
+ testThis("namedAnchor", namedAnchorAcc, ROLE_LINK, 1,
+ "This should never be of state_linked", true, 196, 197);
+ testStates(namedAnchorAcc, STATE_SELECTABLE,
+ 0, (STATE_FOCUSABLE | STATE_LINKED));
+ testAction("namedAnchor", namedAnchorAcc, "");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // No link (hasn't any attribute), should never have state_linked
+ var noLinkAcc = getAccessible("noLink",
+ [nsIAccessibleHyperLink]);
+ testThis("noLink", noLinkAcc, ROLE_LINK, 1,
+ "This should never be of state_linked", true, 254, 255);
+ testStates(noLinkAcc, 0, 0, (STATE_FOCUSABLE | STATE_LINKED));
+ testAction("noLink", noLinkAcc, "");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Link with registered 'click' event, should have state_linked
+ var linkWithClickAcc = getAccessible("linkWithClick",
+ [nsIAccessibleHyperLink]);
+ testThis("linkWithClick", linkWithClickAcc, ROLE_LINK, 1,
+ "This should have state_linked", true, 292, 293);
+ testStates(linkWithClickAcc, STATE_LINKED, 0);
+ testAction("linkWithClick", linkWithClickAcc, "click");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Maps to group links (bug 431615).
+ // var linksMapAcc = getAccessible("linksmap");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Link with title attribute, no name from the subtree (bug 438325).
+ var id = "linkWithTitleNoNameFromSubtree";
+ var linkAcc = getAccessible(id, [nsIAccessibleHyperLink]);
+ testThis(id, linkAcc, ROLE_LINK, 1, "Link with title", true, 344, 345);
+ testStates(linkAcc, STATE_LINKED, 0);
+ testAction(id, linkAcc, "jump");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Link with title attribute, name from the subtree - onscreen name
+ // (bug 438325).
+ id = "linkWithTitleNameFromSubtree";
+ linkAcc = getAccessible(id, [nsIAccessibleHyperLink]);
+ testThis(id, linkAcc, ROLE_LINK, 1, "the name from subtree", true, 393,
+ 394);
+ testStates(linkAcc, STATE_LINKED, 0);
+ testAction(id, linkAcc, "jump");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Link with title attribute, name from the nested html:img (bug 438325).
+ id = "linkWithTitleNameFromImg";
+ linkAcc = getAccessible(id, [nsIAccessibleHyperLink]);
+ testThis(id, linkAcc, ROLE_LINK, 1, "The title for link", true, 447,
+ 448);
+ testStates(linkAcc, STATE_LINKED, 0);
+ testAction(id, linkAcc, "jump");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Text accessible shouldn't implement nsIAccessibleHyperLink
+ var res = isAccessible(getNode("namedAnchor").firstChild,
+ [nsIAccessibleHyperLink]);
+ ok(!res, "Text accessible shouldn't implement nsIAccessibleHyperLink");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Test focus
+ gQueue = new eventQueue();
+
+ gQueue.push(new focusLink("NormalHyperlink", true));
+ gQueue.push(new focusLink("AriaHyperlink", true));
+ gQueue.push(new focusLink("InvalidAriaHyperlink", false));
+ gQueue.push(new focusLink("LinkWithSpan", true));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+
+</head>
+<body><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418368">Mozilla Bug 418368</a
+ ><p id="display"></p
+ ><div id="content" style="display: none"></div
+ ><pre id="test">
+ </pre
+ ><br
+ >Simple link:<br
+ ><a id="NormalHyperlink" href="http://www.mozilla.org">Mozilla Foundation</a
+ ><br>ARIA link:<br
+ ><span id="AriaHyperlink" role="link"
+ onclick="window.open('http://www.mozilla.org/');"
+ tabindex="0">Mozilla Foundation Home</span
+ ><br
+ >Invalid, non-focusable hyperlink:<br
+ ><span id="InvalidAriaHyperlink" role="link" aria-invalid="true"
+ onclick="window.open('http:/www.mozilla.org/');">Invalid link</span
+ ><br>Image map:<br
+ ><map name="atoz_map"
+ ><area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14"
+ alt="b"
+ shape="rect"></area
+ ><area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,13,14"
+ alt="a"
+ shape="rect"></area
+ ></map
+ ><img width="447" id="imgmap"
+ height="15"
+ usemap="#atoz_map"
+ src="../letters.gif"><br>Empty link:<br
+ ><a id="emptyLink" href=""><img src=""></a
+ ><br>Link with embedded span<br
+ ><a id="LinkWithSpan" href="http://www.heise.de/"><span lang="de">Heise Online</span></a
+ ><br>Named anchor, must not have "linked" state for it to be exposed correctly:<br
+ ><a id="namedAnchor" name="named_anchor">This should never be of state_linked</a
+ ><br>Link having no attributes, must not have "linked" state:<a id="noLink"
+ >This should never be of state_linked</a
+ ><br>Link with registered 'click' event: <a id="linkWithClick" onclick="var clicked = true;"
+ >This should have state_linked</a
+ ><br>Link with title attribute (no name from subtree): <a
+ id="linkWithTitleNoNameFromSubtree" href="http://www.heise.de/"
+ title="Link with title"><img src=""/></a
+ ><br>Link with title attribute (name from subtree): <a
+ id="linkWithTitleNameFromSubtree" href="http://www.heise.de/"
+ title="Link with title">the name from subtree</a
+ ><br>Link with title attribute (name from nested image): <a
+ id="linkWithTitleNameFromImg" href="http://www.heise.de/"
+ title="Link with title"><img src="" alt="The title for link"/></a
+ ><br><br>Map that is used to group links (www.w3.org/TR/WCAG10-HTML-TECHS/#group-bypass), also see the bug 431615:<br
+ ><map id="linksmap" title="Site navigation"><ul
+ ><li><a href="http://mozilla.org">About the project</a></li
+ ><li><a href="http://mozilla.org">Sites and sounds</a></li
+ ></ul
+ ></map
+></body>
+</html>
diff --git a/accessible/tests/mochitest/hyperlink/test_general.xhtml b/accessible/tests/mochitest/hyperlink/test_general.xhtml
new file mode 100644
index 0000000000..b006e58ef4
--- /dev/null
+++ b/accessible/tests/mochitest/hyperlink/test_general.xhtml
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="test for nsIAccessibleHyperLink interface on XUL:label elements">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript"
+ src="hyperlink.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function testThis(aID, aAcc, aRole, aAnchorCount, aAnchorName, aURI,
+ aStartIndex, aEndIndex, aValid)
+ {
+ testRole(aID, aRole);
+ is(aAcc.anchorCount, aAnchorCount, "Wrong number of anchors for ID "
+ + aID + "!");
+ is(aAcc.getAnchor(0).name, aAnchorName, "Wrong name for ID " + aID + "!");
+ is(aAcc.getURI(0).spec, aURI, "URI wrong for ID " + aID + "!");
+ is(aAcc.startIndex, aStartIndex, "Wrong startIndex value for ID " + aID
+ + "!");
+ is(aAcc.endIndex, aEndIndex, "Wrong endIndex value for ID " + aID + "!");
+ is(aAcc.valid, aValid, "Wrong valid state for ID " + aID + "!");
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ var linkedLabelAcc = getAccessible("linkedLabel",
+ [nsIAccessibleHyperLink]);
+ testThis("linkedLabel", linkedLabelAcc, ROLE_LINK, 1,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "Mozilla Foundation home", "http://www.mozilla.org/", 1, 2,
+ true);
+ testStates(linkedLabelAcc, STATE_LINKED, 0);
+
+ var labelWithValueAcc = getAccessible("linkLabelWithValue",
+ [nsIAccessibleHyperLink]);
+ testThis("linkLabelWithValue", labelWithValueAcc, ROLE_LINK, 1,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "Mozilla Foundation", "http://www.mozilla.org/", 2, 3, true,
+ false, true);
+ testStates(labelWithValueAcc, STATE_LINKED, 0);
+
+ var normalLabelAcc = getAccessible("normalLabel");
+ testRole(normalLabelAcc, ROLE_LABEL);
+ is(normalLabelAcc.name, "This label should not be a link",
+ "Wrong name for normal label!");
+ testStates(normalLabelAcc, 0, 0, (STATE_FOCUSABLE | STATE_LINKED));
+
+ //////////////////////////////////////////////////////////////////////////
+ // Test focus
+
+ gQueue = new eventQueue();
+
+ gQueue.push(new focusLink("linkedLabel", true));
+ gQueue.push(new focusLink("linkLabelWithValue", true));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=421066"
+ title="Implement Mochitests for the nsIAccessibleHyperLink interface on XUL:label elements">
+ Mozilla Bug 421066
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <label id="linkedLabel" href="http://www.mozilla.org/" is="text-link">
+ Mozilla Foundation home</label>
+ <label id="linkLabelWithValue" value="Mozilla Foundation" is="text-link"
+ href="http://www.mozilla.org/" />
+ <label id="normalLabel" value="This label should not be a link" />
+</window>
diff --git a/accessible/tests/mochitest/hypertext/a11y.ini b/accessible/tests/mochitest/hypertext/a11y.ini
new file mode 100644
index 0000000000..27f878f743
--- /dev/null
+++ b/accessible/tests/mochitest/hypertext/a11y.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+
+[test_general.html]
+[test_update.html]
diff --git a/accessible/tests/mochitest/hypertext/test_general.html b/accessible/tests/mochitest/hypertext/test_general.html
new file mode 100644
index 0000000000..a89be54f95
--- /dev/null
+++ b/accessible/tests/mochitest/hypertext/test_general.html
@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=428248
+-->
+<head>
+ <title>nsIHyper>TextAccessible chrome tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ var gParagraphAcc;
+
+ function testLinkIndexAtOffset(aID, aOffset, aIndex) {
+ var htAcc = getAccessible(aID, [nsIAccessibleHyperText]);
+ is(htAcc.getLinkIndexAtOffset(aOffset), aIndex,
+ "Wrong link index at offset " + aOffset + " for ID " + aID + "!");
+ }
+
+ function testThis(aID, aCharIndex, aExpectedLinkIndex, aName) {
+ testLinkIndexAtOffset(gParagraphAcc, aCharIndex, aExpectedLinkIndex);
+
+ var linkAcc = gParagraphAcc.getLinkAt(aExpectedLinkIndex);
+ ok(linkAcc, "No accessible for link " + aID + "!");
+
+ var linkIndex = gParagraphAcc.getLinkIndex(linkAcc);
+ is(linkIndex, aExpectedLinkIndex, "Wrong link index for " + aID + "!");
+
+ // Just test the link's name to make sure we get the right one.
+ is(linkAcc.getAnchor(0).name, aName, "Wrong name for " + aID + "!");
+ }
+
+ // gA11yEventDumpToConsole = true;
+ function doPreTest() {
+ waitForImageMap("imgmap", doTest);
+ }
+
+ function doTest() {
+ // Test link count
+ gParagraphAcc = getAccessible("testParagraph", [nsIAccessibleHyperText]);
+ is(gParagraphAcc.linkCount, 7, "Wrong link count for paragraph!");
+
+ // normal hyperlink
+ testThis("NormalHyperlink", 14, 0, "Mozilla Foundation");
+
+ // ARIA hyperlink
+ testThis("AriaHyperlink", 27, 1, "Mozilla Foundation Home");
+
+ // ARIA hyperlink with status invalid
+ testThis("InvalidAriaHyperlink", 63, 2, "Invalid link");
+
+ // image map, but not its link children. They are not part of hypertext.
+ testThis("imgmap", 76, 3, "b");
+
+ // empty hyperlink
+ testThis("emptyLink", 90, 4, null);
+
+ // normal hyperlink with embedded span
+ testThis("LinkWithSpan", 116, 5, "Heise Online");
+
+ // Named anchor
+ testThis("namedAnchor", 193, 6, "This should never be of state_linked");
+
+ // Paragraph with link
+ var p2 = getAccessible("p2", [nsIAccessibleHyperText]);
+ var link = p2.getLinkAt(0);
+ is(link, p2.getChildAt(0), "Wrong link for p2");
+ is(p2.linkCount, 1, "Wrong link count for p2");
+
+ // getLinkIndexAtOffset, causes the offsets to be cached;
+ testLinkIndexAtOffset("p4", 0, 0); // 1st 'mozilla' link
+ testLinkIndexAtOffset("p4", 1, 1); // 2nd 'mozilla' link
+ testLinkIndexAtOffset("p4", 2, -1); // ' ' of ' te' text node
+ testLinkIndexAtOffset("p4", 3, -1); // 't' of ' te' text node
+ testLinkIndexAtOffset("p4", 5, -1); // 'x' of 'xt ' text node
+ testLinkIndexAtOffset("p4", 7, -1); // ' ' of 'xt ' text node
+ testLinkIndexAtOffset("p4", 8, 2); // 3d 'mozilla' link
+ testLinkIndexAtOffset("p4", 9, 2); // the end, latest link
+
+ // the second pass to make sure link indexes are calculated propertly from
+ // cached offsets.
+ testLinkIndexAtOffset("p4", 0, 0); // 1st 'mozilla' link
+ testLinkIndexAtOffset("p4", 1, 1); // 2nd 'mozilla' link
+ testLinkIndexAtOffset("p4", 2, -1); // ' ' of ' te' text node
+ testLinkIndexAtOffset("p4", 3, -1); // 't' of ' te' text node
+ testLinkIndexAtOffset("p4", 5, -1); // 'x' of 'xt ' text node
+ testLinkIndexAtOffset("p4", 7, -1); // ' ' of 'xt ' text node
+ testLinkIndexAtOffset("p4", 8, 2); // 3d 'mozilla' link
+ testLinkIndexAtOffset("p4", 9, 2); // the end, latest link
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ title="Create tests for NSIAccessibleHyperlink interface"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=418368">
+ Mozilla Bug 418368
+ </a><br>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <p id="testParagraph"><br
+ >Simple link:<br
+ ><a id="NormalHyperlink" href="http://www.mozilla.org">Mozilla Foundation</a><br
+ >ARIA link:<br
+ ><span id="AriaHyperlink" role="link"
+ onclick="window.open('http://www.mozilla.org/');"
+ tabindex="0">Mozilla Foundation Home</span><br
+ >Invalid, non-focusable hyperlink:<br
+ ><span id="InvalidAriaHyperlink" role="link" aria-invalid="true"
+ onclick="window.open('http:/www.mozilla.org/');">Invalid link</span><br
+ >Image map:<br
+ ><map name="atoz_map"><area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14"
+ alt="b"
+ shape="rect"></area
+ ><area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,13,14"
+ alt="a"
+ shape="rect"></area></map
+ ><img width="447" id="imgmap"
+ height="15"
+ usemap="#atoz_map"
+ src="../letters.gif"></img><br
+ >Empty link:<br
+ ><a id="emptyLink" href=""><img src=""></img></a><br
+ >Link with embedded span<br
+ ><a id="LinkWithSpan" href="http://www.heise.de/"><span lang="de">Heise Online</span></a><br
+ >Named anchor, must not have "linked" state for it to be exposed correctly:<br
+ ><a id="namedAnchor" name="named_anchor">This should never be of state_linked</a>
+ </p>
+ <p id="p2"><a href="http://mozilla.org">mozilla.org</a></p>
+ <p id="p4"><a href="www">mozilla</a><a href="www">mozilla</a><span> te</span><span>xt </span><a href="www">mozilla</a></p>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hypertext/test_update.html b/accessible/tests/mochitest/hypertext/test_update.html
new file mode 100644
index 0000000000..f3407bea64
--- /dev/null
+++ b/accessible/tests/mochitest/hypertext/test_update.html
@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIHyper>TextAccessible in dynamic tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ const kLinksCount = 128;
+ function addLinks(aContainerID) {
+ this.containerNode = getNode(aContainerID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.containerNode),
+ ];
+
+ this.invoke = function addLinks_invoke() {
+ for (var jdx = 0; jdx < kLinksCount; jdx++) {
+ var a = document.createElement("a");
+ a.setAttribute("href", "mozilla.org");
+ a.textContent = "mozilla";
+ this.containerNode.appendChild(a);
+
+ var span = document.createElement("span");
+ span.textContent = " text ";
+ this.containerNode.appendChild(span);
+ }
+ };
+
+ this.finalCheck = function addLinks_finalCheck() {
+ // getLinkAt and getLinkIndex.
+ var htAcc = getAccessible(this.containerNode, [nsIAccessibleHyperText]);
+ for (var jdx = 0; jdx < kLinksCount; jdx++) {
+ var link = htAcc.getLinkAt(jdx);
+ ok(link, "No link at index " + jdx + " for '" + aContainerID + "'");
+
+ var linkIdx = htAcc.getLinkIndex(link);
+ is(linkIdx, jdx, "Wrong link index for '" + aContainerID + "'!");
+ }
+ };
+
+ this.getID = function addLinks_getID() {
+ return "Add links for '" + aContainerID + "'";
+ };
+ }
+
+ function updateText(aContainerID) {
+ this.containerNode = getNode(aContainerID);
+ this.container = getAccessible(this.containerNode, nsIAccessibleHyperText);
+ this.text = this.container.firstChild;
+ this.textNode = this.text.DOMNode;
+ this.textLen = this.textNode.data.length;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_TEXT_INSERTED, this.containerNode),
+ ];
+
+ this.invoke = function updateText_invoke() {
+ is(this.container.getLinkIndexAtOffset(this.textLen), 0,
+ "Wrong intial text offsets!");
+
+ this.text.DOMNode.appendData(" my");
+ };
+
+ this.finalCheck = function updateText_finalCheck() {
+ is(this.container.getLinkIndexAtOffset(this.textLen), -1,
+ "Text offsets weren't updated!");
+ };
+
+ this.getID = function updateText_getID() {
+ return "update text for '" + aContainerID + "'";
+ };
+ }
+
+ /**
+ * Text offsets must be updated when hypertext child is removed.
+ */
+ function removeChild(aContainerID, aChildID, aInitialText, aFinalText) {
+ this.containerNode = getNode(aContainerID);
+ this.container = getAccessible(this.containerNode, nsIAccessibleText);
+ this.childNode = getNode(aChildID);
+
+ // Call first to getText so offsets are cached
+ is(this.container.getText(0, -1), aInitialText,
+ "Wrong text before child removal");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.containerNode),
+ ];
+
+ this.invoke = function removeChild_invoke() {
+ this.containerNode.removeChild(this.childNode);
+ };
+
+ this.finalCheck = function removeChild_finalCheck() {
+ is(this.container.getText(0, -1), aFinalText,
+ "Wrong text after child removal");
+ is(this.container.characterCount, aFinalText.length,
+ "Wrong text after child removal");
+ };
+
+ this.getID = function removeChild_getID() {
+ return "check text after removing child from '" + aContainerID + "'";
+ };
+ }
+
+ function removeFirstChild(aContainer) {
+ this.ht = getAccessible(aContainer, [ nsIAccessibleHyperText ]);
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aContainer),
+ ];
+
+ this.invoke = function removeFirstChild_invoke() {
+ is(this.ht.linkCount, 2, "Wrong embedded objects count before removal");
+
+ getNode(aContainer).removeChild(getNode(aContainer).firstElementChild);
+ };
+
+ this.finalCheck = function removeFirstChild_finalCheck() {
+ // check list index before link count
+ is(this.ht.getLinkIndex(this.ht.firstChild), 0, "Wrong child index");
+ is(this.ht.linkCount, 1, "Wrong embedded objects count after removal");
+ };
+
+ this.getID = function removeFirstChild_getID() {
+ return "Remove first child and check embedded object indeces";
+ };
+ }
+
+ function removeLastChild(aContainer) {
+ this.ht = getAccessible(aContainer, [ nsIAccessibleHyperText ]);
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aContainer),
+ ];
+
+ this.invoke = function removeLastChild_invoke() {
+ is(this.ht.linkCount, 1, "Wrong embedded objects count before removal");
+
+ getNode(aContainer).removeChild(getNode(aContainer).lastElementChild);
+ };
+
+ this.finalCheck = function removeLastChild_finalCheck() {
+ is(this.ht.linkCount, 0, "Wrong embedded objects count after removal");
+
+ var link = null;
+ try {
+ link = this.ht.getLinkAt(0);
+ } catch (e) { }
+ ok(!link, "No embedded object is expected");
+ };
+
+ this.getID = function removeLastChild_getID() {
+ return "Remove last child and try its embedded object";
+ };
+ }
+
+ // gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest() {
+ gQueue = new eventQueue();
+ gQueue.push(new addLinks("p1"));
+ gQueue.push(new updateText("p2"));
+ gQueue.push(new removeChild("div1", "div2",
+ "hello my good friend", "hello friend"));
+ gQueue.push(new removeFirstChild("c4"));
+ gQueue.push(new removeLastChild("c5"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Cache links within hypertext accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=572394">
+ Mozilla Bug 572394
+ </a>
+ <a target="_blank"
+ title="Text offsets don't get updated when text of first child text accessible is changed"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=625009">
+ Mozilla Bug 625009
+ </a>
+ <a target="_blank"
+ title="Crash in nsHyperTextAccessible::GetText()"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630841">
+ Mozilla Bug 630841
+ </a><br>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="p1"></p>
+ <p id="p2"><b>hello</b><a>friend</a></p>
+ <div id="div1">hello<span id="div2"> my<span id="div3"> good</span></span> friend</span></div>
+ <form id="c4">
+ <label for="c4_input">label</label>
+ <input id="c4_input">
+ </form>
+ <div id="c5">TextLeaf<input id="c5_input"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/layout.js b/accessible/tests/mochitest/layout.js
new file mode 100644
index 0000000000..1467d5fe7f
--- /dev/null
+++ b/accessible/tests/mochitest/layout.js
@@ -0,0 +1,390 @@
+/* import-globals-from common.js */
+
+/**
+ * Tests if the given child and grand child accessibles at the given point are
+ * expected.
+ *
+ * @param aID [in] accessible identifier
+ * @param aX [in] x coordinate of the point relative accessible
+ * @param aY [in] y coordinate of the point relative accessible
+ * @param aChildID [in] expected child accessible
+ * @param aGrandChildID [in] expected child accessible
+ */
+function testChildAtPoint(aID, aX, aY, aChildID, aGrandChildID) {
+ var child = getChildAtPoint(aID, aX, aY, false);
+ var expectedChild = getAccessible(aChildID);
+
+ var msg =
+ "Wrong direct child accessible at the point (" +
+ aX +
+ ", " +
+ aY +
+ ") of " +
+ prettyName(aID);
+ isObject(child, expectedChild, msg);
+
+ var grandChild = getChildAtPoint(aID, aX, aY, true);
+ var expectedGrandChild = getAccessible(aGrandChildID);
+
+ msg =
+ "Wrong deepest child accessible at the point (" +
+ aX +
+ ", " +
+ aY +
+ ") of " +
+ prettyName(aID);
+ isObject(grandChild, expectedGrandChild, msg);
+}
+
+/**
+ * Test if getChildAtPoint returns the given child and grand child accessibles
+ * at coordinates of child accessible (direct and deep hit test).
+ */
+function hitTest(aContainerID, aChildID, aGrandChildID) {
+ var container = getAccessible(aContainerID);
+ var child = getAccessible(aChildID);
+ var grandChild = getAccessible(aGrandChildID);
+
+ var [x, y] = getBoundsForDOMElm(child);
+
+ var actualChild = container.getChildAtPoint(x + 1, y + 1);
+ isObject(
+ actualChild,
+ child,
+ "Wrong direct child of " + prettyName(aContainerID)
+ );
+
+ var actualGrandChild = container.getDeepestChildAtPoint(x + 1, y + 1);
+ isObject(
+ actualGrandChild,
+ grandChild,
+ "Wrong deepest child of " + prettyName(aContainerID)
+ );
+}
+
+/**
+ * Test if getOffsetAtPoint returns the given text offset at given coordinates.
+ */
+function testOffsetAtPoint(aHyperTextID, aX, aY, aCoordType, aExpectedOffset) {
+ var hyperText = getAccessible(aHyperTextID, [nsIAccessibleText]);
+ var offset = hyperText.getOffsetAtPoint(aX, aY, aCoordType);
+ is(
+ offset,
+ aExpectedOffset,
+ "Wrong offset at given point (" +
+ aX +
+ ", " +
+ aY +
+ ") for " +
+ prettyName(aHyperTextID)
+ );
+}
+
+/**
+ * Zoom the given document.
+ */
+function zoomDocument(aDocument, aZoom) {
+ SpecialPowers.setFullZoom(aDocument.defaultView, aZoom);
+}
+
+/**
+ * Set the relative resolution of this document. This is what apz does.
+ * On non-mobile platforms you won't see a visible change.
+ */
+function setResolution(aDocument, aZoom) {
+ var windowUtils = aDocument.defaultView.windowUtils;
+
+ windowUtils.setResolutionAndScaleTo(aZoom);
+}
+
+/**
+ * Return child accessible at the given point.
+ *
+ * @param aIdentifier [in] accessible identifier
+ * @param aX [in] x coordinate of the point relative accessible
+ * @param aY [in] y coordinate of the point relative accessible
+ * @param aFindDeepestChild [in] points whether deepest or nearest child should
+ * be returned
+ * @return the child accessible at the given point
+ */
+function getChildAtPoint(aIdentifier, aX, aY, aFindDeepestChild) {
+ var acc = getAccessible(aIdentifier);
+ if (!acc) {
+ return null;
+ }
+
+ var [screenX, screenY] = getBoundsForDOMElm(acc.DOMNode);
+
+ var x = screenX + aX;
+ var y = screenY + aY;
+
+ try {
+ if (aFindDeepestChild) {
+ return acc.getDeepestChildAtPoint(x, y);
+ }
+ return acc.getChildAtPoint(x, y);
+ } catch (e) {}
+
+ return null;
+}
+
+/**
+ * Test the accessible position.
+ */
+function testPos(aID, aPoint) {
+ var [expectedX, expectedY] =
+ aPoint != undefined ? aPoint : getBoundsForDOMElm(aID);
+
+ var [x, y] = getBounds(aID);
+ is(x, expectedX, "Wrong x coordinate of " + prettyName(aID));
+ is(y, expectedY, "Wrong y coordinate of " + prettyName(aID));
+}
+
+/**
+ * Test the accessible boundaries.
+ */
+function testBounds(aID, aRect) {
+ var [expectedX, expectedY, expectedWidth, expectedHeight] =
+ aRect != undefined ? aRect : getBoundsForDOMElm(aID);
+
+ var [x, y, width, height] = getBounds(aID);
+ is(x, expectedX, "Wrong x coordinate of " + prettyName(aID));
+ is(y, expectedY, "Wrong y coordinate of " + prettyName(aID));
+ is(width, expectedWidth, "Wrong width of " + prettyName(aID));
+ is(height, expectedHeight, "Wrong height of " + prettyName(aID));
+}
+
+/**
+ * Test text position at the given offset.
+ */
+function testTextPos(aID, aOffset, aPoint, aCoordOrigin) {
+ var [expectedX, expectedY] = aPoint;
+
+ var xObj = {},
+ yObj = {};
+ var hyperText = getAccessible(aID, [nsIAccessibleText]);
+ hyperText.getCharacterExtents(aOffset, xObj, yObj, {}, {}, aCoordOrigin);
+ is(
+ xObj.value,
+ expectedX,
+ "Wrong x coordinate at offset " + aOffset + " for " + prettyName(aID)
+ );
+ ok(
+ yObj.value - expectedY <= 2 && expectedY - yObj.value <= 2,
+ "Wrong y coordinate at offset " +
+ aOffset +
+ " for " +
+ prettyName(aID) +
+ " - got " +
+ yObj.value +
+ ", expected " +
+ expectedY +
+ "The difference doesn't exceed 1."
+ );
+}
+
+/**
+ * Test text bounds that is enclosed betwene the given offsets.
+ */
+function testTextBounds(aID, aStartOffset, aEndOffset, aRect, aCoordOrigin) {
+ var [expectedX, expectedY, expectedWidth, expectedHeight] = aRect;
+
+ var xObj = {},
+ yObj = {},
+ widthObj = {},
+ heightObj = {};
+ var hyperText = getAccessible(aID, [nsIAccessibleText]);
+ hyperText.getRangeExtents(
+ aStartOffset,
+ aEndOffset,
+ xObj,
+ yObj,
+ widthObj,
+ heightObj,
+ aCoordOrigin
+ );
+
+ // x
+ isWithin(
+ expectedX,
+ xObj.value,
+ 1,
+ "Wrong x coordinate of text between offsets (" +
+ aStartOffset +
+ ", " +
+ aEndOffset +
+ ") for " +
+ prettyName(aID)
+ );
+
+ // y
+ isWithin(
+ expectedY,
+ yObj.value,
+ 1,
+ `y coord of text between offsets (${aStartOffset}, ${aEndOffset}) ` +
+ `for ${prettyName(aID)}`
+ );
+
+ // Width
+ var msg =
+ "Wrong width of text between offsets (" +
+ aStartOffset +
+ ", " +
+ aEndOffset +
+ ") for " +
+ prettyName(aID) +
+ " - Got " +
+ widthObj.value +
+ " Expected " +
+ expectedWidth;
+ if (!WIN) {
+ isWithin(expectedWidth, widthObj.value, 1, msg);
+ } else {
+ // fails on some windows machines
+ todo(false, msg);
+ }
+
+ // Height
+ isWithin(
+ expectedHeight,
+ heightObj.value,
+ 1,
+ `height of text between offsets (${aStartOffset}, ${aEndOffset}) ` +
+ `for ${prettyName(aID)}`
+ );
+}
+
+/**
+ * Return the accessible coordinates relative to the screen in device pixels.
+ */
+function getPos(aID) {
+ var accessible = getAccessible(aID);
+ var x = {},
+ y = {};
+ accessible.getBounds(x, y, {}, {});
+ return [x.value, y.value];
+}
+
+/**
+ * Return the accessible coordinates and size relative to the screen in device
+ * pixels. This methods also retrieves coordinates in CSS pixels and ensures that they
+ * match Dev pixels with a given device pixel ratio.
+ */
+function getBounds(aID, aDPR = window.devicePixelRatio) {
+ const accessible = getAccessible(aID);
+ let x = {},
+ y = {},
+ width = {},
+ height = {};
+ let xInCSS = {},
+ yInCSS = {},
+ widthInCSS = {},
+ heightInCSS = {};
+ accessible.getBounds(x, y, width, height);
+ accessible.getBoundsInCSSPixels(xInCSS, yInCSS, widthInCSS, heightInCSS);
+
+ info(`DPR is: ${aDPR}`);
+ isWithin(
+ xInCSS.value,
+ x.value / aDPR,
+ 1,
+ "X in CSS pixels is calculated correctly"
+ );
+ isWithin(
+ yInCSS.value,
+ y.value / aDPR,
+ 1,
+ "Y in CSS pixels is calculated correctly"
+ );
+ isWithin(
+ widthInCSS.value,
+ width.value / aDPR,
+ 1,
+ "Width in CSS pixels is calculated correctly"
+ );
+ isWithin(
+ heightInCSS.value,
+ height.value / aDPR,
+ 1,
+ "Height in CSS pixels is calculated correctly"
+ );
+
+ return [x.value, y.value, width.value, height.value];
+}
+
+function getRangeExtents(aID, aStartOffset, aEndOffset, aCoordOrigin) {
+ var hyperText = getAccessible(aID, [nsIAccessibleText]);
+ var x = {},
+ y = {},
+ width = {},
+ height = {};
+ hyperText.getRangeExtents(
+ aStartOffset,
+ aEndOffset,
+ x,
+ y,
+ width,
+ height,
+ aCoordOrigin
+ );
+ return [x.value, y.value, width.value, height.value];
+}
+
+/**
+ * Return DOM node coordinates relative the screen and its size in device
+ * pixels.
+ */
+function getBoundsForDOMElm(aID) {
+ var x = 0,
+ y = 0,
+ width = 0,
+ height = 0;
+
+ var elm = getNode(aID);
+ if (elm.localName == "area") {
+ var mapName = elm.parentNode.getAttribute("name");
+ var selector = "[usemap='#" + mapName + "']";
+ var img = elm.ownerDocument.querySelector(selector);
+
+ var areaCoords = elm.coords.split(",");
+ var areaX = parseInt(areaCoords[0]);
+ var areaY = parseInt(areaCoords[1]);
+ var areaWidth = parseInt(areaCoords[2]) - areaX;
+ var areaHeight = parseInt(areaCoords[3]) - areaY;
+
+ let rect = img.getBoundingClientRect();
+ x = rect.left + areaX;
+ y = rect.top + areaY;
+ width = areaWidth;
+ height = areaHeight;
+ } else {
+ let rect = elm.getBoundingClientRect();
+ x = rect.left;
+ y = rect.top;
+ width = rect.width;
+ height = rect.height;
+ }
+
+ var elmWindow = elm.ownerGlobal;
+ return CSSToDevicePixels(
+ elmWindow,
+ x + elmWindow.mozInnerScreenX,
+ y + elmWindow.mozInnerScreenY,
+ width,
+ height
+ );
+}
+
+function CSSToDevicePixels(aWindow, aX, aY, aWidth, aHeight) {
+ var ratio = aWindow.devicePixelRatio;
+
+ // CSS pixels and ratio can be not integer. Device pixels are always integer.
+ // Do our best and hope it works.
+ return [
+ Math.round(aX * ratio),
+ Math.round(aY * ratio),
+ Math.round(aWidth * ratio),
+ Math.round(aHeight * ratio),
+ ];
+}
diff --git a/accessible/tests/mochitest/letters.gif b/accessible/tests/mochitest/letters.gif
new file mode 100644
index 0000000000..299b91784a
--- /dev/null
+++ b/accessible/tests/mochitest/letters.gif
Binary files differ
diff --git a/accessible/tests/mochitest/longdesc_src.html b/accessible/tests/mochitest/longdesc_src.html
new file mode 100644
index 0000000000..37248795dd
--- /dev/null
+++ b/accessible/tests/mochitest/longdesc_src.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<title>Mozilla logo</title>
+</head>
+<body>
+<p>This file would contain a longer description of the Mozilla logo, if I knew what it looked like.</p>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/moz.build b/accessible/tests/mochitest/moz.build
new file mode 100644
index 0000000000..6276f6ced9
--- /dev/null
+++ b/accessible/tests/mochitest/moz.build
@@ -0,0 +1,36 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+A11Y_MANIFESTS += [
+ "a11y.ini",
+ "actions/a11y.ini",
+ "aom/a11y.ini",
+ "attributes/a11y.ini",
+ "bounds/a11y.ini",
+ "elm/a11y.ini",
+ "events/a11y.ini",
+ "events/docload/a11y.ini",
+ "focus/a11y.ini",
+ "hittest/a11y.ini",
+ "hyperlink/a11y.ini",
+ "hypertext/a11y.ini",
+ "name/a11y.ini",
+ "pivot/a11y.ini",
+ "relations/a11y.ini",
+ "role/a11y.ini",
+ "scroll/a11y.ini",
+ "selectable/a11y.ini",
+ "states/a11y.ini",
+ "table/a11y.ini",
+ "text/a11y.ini",
+ "textattrs/a11y.ini",
+ "textcaret/a11y.ini",
+ "textrange/a11y.ini",
+ "textselection/a11y.ini",
+ "tree/a11y.ini",
+ "treeupdate/a11y.ini",
+ "value/a11y.ini",
+]
diff --git a/accessible/tests/mochitest/moz.png b/accessible/tests/mochitest/moz.png
new file mode 100644
index 0000000000..743292dc6f
--- /dev/null
+++ b/accessible/tests/mochitest/moz.png
Binary files differ
diff --git a/accessible/tests/mochitest/name.js b/accessible/tests/mochitest/name.js
new file mode 100644
index 0000000000..48bf2d7038
--- /dev/null
+++ b/accessible/tests/mochitest/name.js
@@ -0,0 +1,38 @@
+/* import-globals-from common.js */
+
+/**
+ * Test accessible name for the given accessible identifier.
+ */
+function testName(aAccOrElmOrID, aName, aMsg, aTodo) {
+ var msg = aMsg ? aMsg : "";
+
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc) {
+ return "";
+ }
+
+ var func = aTodo ? todo_is : is;
+ var txtID = prettyName(aAccOrElmOrID);
+ try {
+ func(acc.name, aName, msg + "Wrong name of the accessible for " + txtID);
+ } catch (e) {
+ ok(false, msg + "Can't get name of the accessible for " + txtID);
+ }
+ return acc;
+}
+
+/**
+ * Test accessible description for the given accessible.
+ */
+function testDescr(aAccOrElmOrID, aDescr) {
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc) {
+ return;
+ }
+
+ is(
+ acc.description,
+ aDescr,
+ "Wrong description for " + prettyName(aAccOrElmOrID)
+ );
+}
diff --git a/accessible/tests/mochitest/name/a11y.ini b/accessible/tests/mochitest/name/a11y.ini
new file mode 100644
index 0000000000..cfcb49d816
--- /dev/null
+++ b/accessible/tests/mochitest/name/a11y.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+support-files =
+ markup.js
+ markuprules.xml
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/moz.png
+
+[test_ARIACore_examples.html]
+[test_browserui.xhtml]
+[test_counterstyle.html]
+[test_general.html]
+[test_general.xhtml]
+[test_link.html]
+[test_list.html]
+[test_markup.html]
+skip-if = (debug && os == 'win') # Bug 1296784
+[test_svg.html]
+[test_tree.xhtml]
diff --git a/accessible/tests/mochitest/name/markup.js b/accessible/tests/mochitest/name/markup.js
new file mode 100644
index 0000000000..a267bd4f7b
--- /dev/null
+++ b/accessible/tests/mochitest/name/markup.js
@@ -0,0 +1,425 @@
+/* import-globals-from ../attributes.js */
+/* import-globals-from ../common.js */
+/* import-globals-from ../events.js */
+/* import-globals-from ../name.js */
+
+// //////////////////////////////////////////////////////////////////////////////
+// Name tests described by "markuprules.xml" file.
+
+var gNameRulesFileURL = "markuprules.xml";
+
+var gRuleDoc = null;
+
+// Debuggin stuff.
+var gDumpToConsole = false;
+
+/**
+ * Start name tests. Run through markup elements and test names for test
+ * element (see namerules.xml for details).
+ */
+function testNames() {
+ // enableLogging("tree,stack"); // debugging
+
+ var request = new XMLHttpRequest();
+ request.open("get", gNameRulesFileURL, false);
+ request.send();
+
+ gRuleDoc = request.responseXML;
+
+ var markupElms = evaluateXPath(gRuleDoc, "//rules/rulesample/markup");
+ gTestIterator.iterateMarkups(markupElms);
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Private section.
+
+/**
+ * Helper class to interate through name tests.
+ */
+var gTestIterator = {
+ iterateMarkups: function gTestIterator_iterateMarkups(aMarkupElms) {
+ this.markupElms = aMarkupElms;
+
+ this.iterateNext();
+ },
+
+ iterateRules: function gTestIterator_iterateRules(
+ aElm,
+ aContainer,
+ aRuleSetElm,
+ aRuleElms,
+ aTestID
+ ) {
+ this.ruleSetElm = aRuleSetElm;
+ this.ruleElms = aRuleElms;
+ this.elm = aElm;
+ this.container = aContainer;
+ this.testID = aTestID;
+
+ this.iterateNext();
+ },
+
+ iterateNext: function gTestIterator_iterateNext() {
+ if (this.markupIdx == -1) {
+ this.markupIdx++;
+ testNamesForMarkup(this.markupElms[this.markupIdx]);
+ return;
+ }
+
+ this.ruleIdx++;
+ if (this.ruleIdx == this.ruleElms.length) {
+ // When test is finished then name is empty and no explict-name.
+ var defaultName = this.ruleSetElm.hasAttribute("defaultName")
+ ? this.ruleSetElm.getAttribute("defaultName")
+ : null;
+ testName(
+ this.elm,
+ defaultName,
+ "Default name test (" + gTestIterator.testID + "). "
+ );
+ testAbsentAttrs(this.elm, { "explicit-name": "true" });
+
+ this.markupIdx++;
+ if (this.markupIdx == this.markupElms.length) {
+ // disableLogging("tree"); // debugging
+ SimpleTest.finish();
+ return;
+ }
+
+ this.ruleIdx = -1;
+
+ if (gDumpToConsole) {
+ dump(
+ "\nPend next markup processing. Wait for reorder event on " +
+ prettyName(document) +
+ "'\n"
+ );
+ }
+ waitForEvent(
+ EVENT_REORDER,
+ document,
+ testNamesForMarkup,
+ null,
+ this.markupElms[this.markupIdx]
+ );
+
+ document.body.removeChild(this.container);
+ return;
+ }
+
+ testNameForRule(this.elm, this.ruleElms[this.ruleIdx]);
+ },
+
+ markupElms: null,
+ markupIdx: -1,
+ rulesetElm: null,
+ ruleElms: null,
+ ruleIdx: -1,
+ elm: null,
+ container: null,
+ testID: "",
+};
+
+/**
+ * Process every 'markup' element and test names for it. Used by testNames
+ * function.
+ */
+function testNamesForMarkup(aMarkupElm) {
+ if (gDumpToConsole) {
+ dump("\nProcessing markup '" + aMarkupElm.getAttribute("id") + "'\n");
+ }
+
+ var div = document.createElement("div");
+ div.setAttribute("id", "test");
+
+ var child = aMarkupElm.firstChild;
+ while (child) {
+ var newChild = document.importNode(child, true);
+ div.appendChild(newChild);
+ child = child.nextSibling;
+ }
+
+ if (gDumpToConsole) {
+ dump(
+ "\nProcessing markup. Wait for reorder event on " +
+ prettyName(document) +
+ "'\n"
+ );
+ }
+ waitForEvent(
+ EVENT_REORDER,
+ document,
+ testNamesForMarkupRules,
+ null,
+ aMarkupElm,
+ div
+ );
+
+ document.body.appendChild(div);
+}
+
+function testNamesForMarkupRules(aMarkupElm, aContainer) {
+ var testID = aMarkupElm.getAttribute("id");
+ if (gDumpToConsole) {
+ dump("\nProcessing markup rules '" + testID + "'\n");
+ }
+
+ var expr = "//html/body/div[@id='test']/" + aMarkupElm.getAttribute("ref");
+ var elm = evaluateXPath(document, expr, htmlDocResolver)[0];
+
+ var ruleId = aMarkupElm.getAttribute("ruleset");
+ var ruleElm = gRuleDoc.querySelector("[id='" + ruleId + "']");
+ var ruleElms = getRuleElmsByRulesetId(ruleId);
+
+ var processMarkupRules = gTestIterator.iterateRules.bind(
+ gTestIterator,
+ elm,
+ aContainer,
+ ruleElm,
+ ruleElms,
+ testID
+ );
+
+ // Images may be recreated after we append them into subtree. We need to wait
+ // in this case. If we are on profiling enabled build then stack tracing
+ // works and thus let's log instead. Note, that works if you enabled logging
+ // (refer to testNames() function).
+ if (isAccessible(elm) || isLogged("stack")) {
+ processMarkupRules();
+ } else {
+ waitForEvent(EVENT_SHOW, elm, processMarkupRules);
+ }
+}
+
+/**
+ * Test name for current rule and current 'markup' element. Used by
+ * testNamesForMarkup function.
+ */
+function testNameForRule(aElm, aRuleElm) {
+ if (aRuleElm.hasAttribute("attr")) {
+ if (gDumpToConsole) {
+ dump(
+ "\nProcessing rule { attr: " + aRuleElm.getAttribute("attr") + " }\n"
+ );
+ }
+
+ testNameForAttrRule(aElm, aRuleElm);
+ } else if (aRuleElm.hasAttribute("elm")) {
+ if (gDumpToConsole) {
+ dump(
+ "\nProcessing rule { elm: " +
+ aRuleElm.getAttribute("elm") +
+ ", elmattr: " +
+ aRuleElm.getAttribute("elmattr") +
+ " }\n"
+ );
+ }
+
+ testNameForElmRule(aElm, aRuleElm);
+ } else if (aRuleElm.getAttribute("fromsubtree") == "true") {
+ if (gDumpToConsole) {
+ dump(
+ "\nProcessing rule { fromsubtree: " +
+ aRuleElm.getAttribute("fromsubtree") +
+ " }\n"
+ );
+ }
+
+ testNameForSubtreeRule(aElm, aRuleElm);
+ }
+}
+
+function testNameForAttrRule(aElm, aRule) {
+ var name = "";
+
+ var attr = aRule.getAttribute("attr");
+ var attrValue = aElm.getAttribute(attr);
+
+ var type = aRule.getAttribute("type");
+ if (type == "string") {
+ name = attrValue;
+ } else if (type == "ref" && attrValue) {
+ var ids = attrValue.split(/\s+/);
+ for (var idx = 0; idx < ids.length; idx++) {
+ var labelElm = getNode(ids[idx]);
+ if (name != "") {
+ name += " ";
+ }
+
+ name += labelElm.getAttribute("textequiv");
+ }
+ }
+
+ var msg = "Attribute '" + attr + "' test (" + gTestIterator.testID + "). ";
+ testName(aElm, name, msg);
+
+ if (aRule.getAttribute("explict-name") != "false") {
+ testAttrs(aElm, { "explicit-name": "true" }, true);
+ } else {
+ testAbsentAttrs(aElm, { "explicit-name": "true" });
+ }
+
+ waitForEvent(
+ EVENT_NAME_CHANGE,
+ aElm,
+ gTestIterator.iterateNext,
+ gTestIterator
+ );
+
+ aElm.removeAttribute(attr);
+}
+
+function testNameForElmRule(aElm, aRule) {
+ var labelElm;
+
+ var tagname = aRule.getAttribute("elm");
+ var attrname = aRule.getAttribute("elmattr");
+ if (attrname) {
+ var filter = {
+ acceptNode: function filter_acceptNode(aNode) {
+ if (
+ aNode.localName == this.mLocalName &&
+ aNode.getAttribute(this.mAttrName) == this.mAttrValue
+ ) {
+ return NodeFilter.FILTER_ACCEPT;
+ }
+
+ return NodeFilter.FILTER_SKIP;
+ },
+
+ mLocalName: tagname,
+ mAttrName: attrname,
+ mAttrValue: aElm.getAttribute("id"),
+ };
+
+ var treeWalker = document.createTreeWalker(
+ document.body,
+ NodeFilter.SHOW_ELEMENT,
+ filter
+ );
+ labelElm = treeWalker.nextNode();
+ } else {
+ // if attrname is empty then look for the element in subtree.
+ labelElm = aElm.getElementsByTagName(tagname)[0];
+ if (!labelElm) {
+ labelElm = aElm.getElementsByTagName("html:" + tagname)[0];
+ }
+ }
+
+ if (!labelElm) {
+ ok(false, msg + " Failed to find '" + tagname + "' element.");
+ gTestIterator.iterateNext();
+ return;
+ }
+
+ var msg = "Element '" + tagname + "' test (" + gTestIterator.testID + ").";
+ testName(aElm, labelElm.getAttribute("textequiv"), msg);
+ testAttrs(aElm, { "explicit-name": "true" }, true);
+
+ var parentNode = labelElm.parentNode;
+
+ if (gDumpToConsole) {
+ dump(
+ "\nProcessed elm rule. Wait for name change event on " +
+ prettyName(aElm) +
+ "\n"
+ );
+ }
+ waitForEvent(
+ EVENT_NAME_CHANGE,
+ aElm,
+ gTestIterator.iterateNext,
+ gTestIterator
+ );
+
+ parentNode.removeChild(labelElm);
+}
+
+function testNameForSubtreeRule(aElm, aRule) {
+ var msg = "From subtree test (" + gTestIterator.testID + ").";
+ testName(aElm, aElm.getAttribute("textequiv"), msg);
+ testAbsentAttrs(aElm, { "explicit-name": "true" });
+
+ if (gDumpToConsole) {
+ dump(
+ "\nProcessed from subtree rule. Wait for reorder event on " +
+ prettyName(aElm) +
+ "\n"
+ );
+ }
+ waitForEvent(
+ EVENT_NAME_CHANGE,
+ aElm,
+ gTestIterator.iterateNext,
+ gTestIterator
+ );
+
+ while (aElm.firstChild) {
+ aElm.firstChild.remove();
+ }
+}
+
+/**
+ * Return array of 'rule' elements. Used in conjunction with
+ * getRuleElmsFromRulesetElm() function.
+ */
+function getRuleElmsByRulesetId(aRulesetId) {
+ var expr = "//rules/ruledfn/ruleset[@id='" + aRulesetId + "']";
+ var rulesetElm = evaluateXPath(gRuleDoc, expr);
+ return getRuleElmsFromRulesetElm(rulesetElm[0]);
+}
+
+function getRuleElmsFromRulesetElm(aRulesetElm) {
+ var rulesetId = aRulesetElm.getAttribute("ref");
+ if (rulesetId) {
+ return getRuleElmsByRulesetId(rulesetId);
+ }
+
+ var ruleElms = [];
+
+ var child = aRulesetElm.firstChild;
+ while (child) {
+ if (child.localName == "ruleset") {
+ ruleElms = ruleElms.concat(getRuleElmsFromRulesetElm(child));
+ }
+ if (child.localName == "rule") {
+ ruleElms.push(child);
+ }
+
+ child = child.nextSibling;
+ }
+
+ return ruleElms;
+}
+
+/**
+ * Helper method to evaluate xpath expression.
+ */
+function evaluateXPath(aNode, aExpr, aResolver) {
+ var xpe = new XPathEvaluator();
+
+ var resolver = aResolver;
+ if (!resolver) {
+ var node =
+ aNode.ownerDocument == null
+ ? aNode.documentElement
+ : aNode.ownerDocument.documentElement;
+ resolver = xpe.createNSResolver(node);
+ }
+
+ var result = xpe.evaluate(aExpr, aNode, resolver, 0, null);
+ var found = [];
+ var res;
+ while ((res = result.iterateNext())) {
+ found.push(res);
+ }
+
+ return found;
+}
+
+function htmlDocResolver(aPrefix) {
+ var ns = {
+ html: "http://www.w3.org/1999/xhtml",
+ };
+ return ns[aPrefix] || null;
+}
diff --git a/accessible/tests/mochitest/name/markuprules.xml b/accessible/tests/mochitest/name/markuprules.xml
new file mode 100644
index 0000000000..045a25b437
--- /dev/null
+++ b/accessible/tests/mochitest/name/markuprules.xml
@@ -0,0 +1,367 @@
+<?xml version="1.0"?>
+
+<!--
+ This XML file is used to create sequence of accessible name tests. It consist
+ of two sections. The first section 'ruledfn' declares name computation rules.
+ The second section 'rulesample' defines markup samples we need to check name
+ computation rules for.
+
+ <ruledfn>
+ <ruleset>
+ <rule>
+
+ Section 'ruledfn' contains 'ruleset' elements. Every 'ruleset' element is
+ presented by 'rule' elements so that sequence of 'rule' elements gives the
+ sequence of name computations rules. Every 'rule' element can be one of four
+ types.
+
+ * <rule attr='' type='string'/> used when name is equal to the value of
+ attribute presented on the element.
+
+ Example, 'aria-label' attribute. In this case 'rule' element has 'attr'
+ attribute pointing to attribute name and 'type' attribute with 'string'
+ value. For example, <rule attr="aria-label" type="string"/>.
+
+ * <rule attr='' type='ref'/> used when name is calculated from elements that
+ are pointed to by attribute value on the element.
+
+ Example is 'aria-labelledby'. In this case 'rule' element has 'attr'
+ attribute holding the sequence of IDs of elements used to compute the name,
+ in addition the 'rule' element has 'type' attribute with 'ref' value.
+ For example, <rule attr="aria-labelledby" type="ref"/>.
+
+ * <rule elm='' elmattr=''/> used when name is calculated from another
+ element. These attributes are used to find an element by tagname and
+ attribute with value equaled to ID of the element. If 'elmattr' is missed
+ then element from subtree with the given tagname is used.
+
+ Example, html:label@for element, <rule elm="label" elmattr="for"/>.
+ Example, html:caption element, <rule elm="caption"/>
+
+ * <rule fromsubtree='true'/> used when name is computed from subtree.
+
+ Example, html:button. In this case 'rule' element has 'fromsubtree'
+ attribute with 'true' value.
+
+ <rulesample>
+ <markup ruleset=''>
+
+ Section 'rulesample' provides set of markup samples ('markup' elements). Every
+ 'markup' element contains an element that accessible name will be computed for
+ (let's call it test element). In addition the 'markup' element contains some
+ other elements from native markup used in name calculation process for test
+ element. Test element is pointed to by 'ref' attribute on 'markup' element.
+ Also 'markup' element has 'ruleset' attribute to indicate ruleset for the test
+ element.
+
+ How does it work? Let's consider simple example:
+ <ruledfn>
+ <ruleset id="aria">
+ <rule attr="aria-label" type="string"/>
+ <rule attr="aria-labelledby" type="ref"/>
+ </ruleset>
+ </ruledfn>
+ <rulesample>
+ <markup ref="html:div" ruleset="aria">
+ <html:span id="label" textequiv="test2">test2</html:span>
+ <html:div aria-label="test1"
+ aria-labelledby="label">it's a div</html:div>
+ </markup>
+ </rulesample>
+
+ Initially 'markup' element holds markup for all rules specified by 'ruleset'
+ attribute. This allows us to check if the sequence of name computation rules
+ is correct. Here 'ruleset' element defines two rules. We get the first rule
+ which means accessible name is computed from value of 'aria-label' attribute.
+ Then we check accessible name for the test element and remove 'aria-label'
+ attribute. After we get the second rule which means we should get IDs from
+ 'aria-labelledby' attribute and compose accessible name from values of
+ 'textequiv' attributes (that are supposed to give the desired name for each
+ element that is being pointed to by aria-labelledby). Check accessible name
+ and finish test.
+-->
+
+<rules xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <ruledfn>
+
+ <!-- bricks -->
+ <ruleset id="ARIA">
+ <rule attr="aria-labelledby" type="ref"/>
+ <rule attr="aria-label" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLControl:Head">
+ <ruleset ref="ARIA"/>
+ <rule elm="label" elmattr="for"/>
+ </ruleset>
+
+ <!-- general -->
+ <ruleset id="HTMLControl">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule fromsubtree="true"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLElm">
+ <ruleset ref="ARIA"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <!-- specific -->
+ <ruleset id="HTMLARIAGridCell">
+ <ruleset ref="ARIA"/>
+ <rule fromsubtree="true"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputButton">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="value" type="string" explict-name="false" reordered="true"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputSubmit" defaultName="Submit Query">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="value" type="string" explict-name="false"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputReset" defaultName="Reset">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="value" type="string" explict-name="false"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputImage">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="alt" type="string"/>
+ <rule attr="value" type="string"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputImageNoValidSrc" defaultName="Submit Query">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="alt" type="string" explict-name="false"/>
+ <rule attr="value" type="string" explict-name="false"/>
+ </ruleset>
+
+ <ruleset id="HTMLOption">
+ <ruleset ref="ARIA"/>
+ <rule attr="label" type="string"/>
+ <rule fromsubtree="true"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLImg">
+ <ruleset ref="ARIA"/>
+ <rule attr="alt" type="string"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLTable">
+ <ruleset ref="ARIA"/>
+ <rule elm="caption"/>
+ <rule attr="summary" type="string"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+ </ruledfn>
+
+ <rulesample>
+
+ <markup id="HTMLButtonTest"
+ ref="html:button" ruleset="HTMLControl">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="btn" textequiv="test4">test4</html:label>
+ <html:button id="btn"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test5"
+ textequiv="press me">press me</html:button>
+ </markup>
+
+ <markup id="HTMLInputButtonTest"
+ ref="html:input" ruleset="HTMLInputButton">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="btn" textequiv="test4">test4</html:label>
+ <html:input id="btn"
+ type="button"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from al"
+ src="no name from src"
+ data="no name from data"
+ title="name from title"/>
+ </markup>
+
+ <markup id="HTMLInputSubmitTest"
+ ref="html:input" ruleset="HTMLInputSubmit">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="btn-submit" textequiv="test4">test4</html:label>
+ <html:input id="btn-submit"
+ type="submit"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from atl"
+ src="no name from src"
+ data="no name from data"
+ title="no name from title"/>
+ </markup>
+
+ <markup id="HTMLInputResetTest"
+ ref="html:input" ruleset="HTMLInputReset">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="btn-reset" textequiv="test4">test4</html:label>
+ <html:input id="btn-reset"
+ type="reset"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from alt"
+ src="no name from src"
+ data="no name from data"
+ title="no name from title"/>
+ </markup>
+
+ <!--
+ Disabled due to intermittent failures (bug 1436323) which became more
+ frequent due to the landing of bug 1383682. The latter bug made loading
+ of images from cache much more consistent, which appears to have impacted
+ the timing for this test case. If the image is switched to a unique
+ image (e.g. always decoding since there is no cache), the failure rate
+ increases, presumably because the test is dependent on a specific ordering
+ of events, and implicitly assumes the image is loaded immediately.
+ -->
+
+ <!--
+ <markup id="HTMLInputImageTest"
+ ref="html:input" ruleset="HTMLInputImage">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="btn-image" textequiv="test4">test4</html:label>
+ <html:input id="btn-image"
+ type="image"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ alt="name from alt"
+ value="name from value"
+ src="../moz.png"
+ data="no name from data"
+ title="name from title"/>
+ </markup>
+ -->
+
+ <markup id="HTMLInputImageNoValidSrcTest"
+ ref="html:input" ruleset="HTMLInputImageNoValidSrc">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="btn-image" textequiv="test4">test4</html:label>
+ <html:input id="btn-image"
+ type="image"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ alt="name from alt"
+ value="name from value"
+ data="no name from data"
+ title="no name from title"/>
+ </markup>
+
+ <markup id="HTMLOptionTest"
+ ref="html:select/html:option[1]" ruleset="HTMLOption">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:select>
+ <html:option id="opt"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ label="test4"
+ title="test5"
+ textequiv="option1">option1</html:option>
+ <html:option>option2</html:option>
+ </html:select>
+ </markup>
+
+ <markup id="HTMLImageTest"
+ ref="html:img" ruleset="HTMLImg">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:img id="img"
+ aria-label="Logo of Mozilla"
+ aria-labelledby="l1 l2"
+ alt="Mozilla logo"
+ title="This is a logo"
+ src="../moz.png"/>
+ </markup>
+
+ <markup id="HTMLTdTest"
+ ref="html:table/html:tr/html:td" ruleset="HTMLElm">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="tc" textequiv="test4">test4</html:label>
+ <html:table>
+ <html:tr>
+ <html:td id="tc"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test5">
+ <html:p>This is a paragraph</html:p>
+ <html:a href="#">This is a link</html:a>
+ <html:ul>
+ <html:li>This is a list</html:li>
+ </html:ul>
+ </html:td>
+ </html:tr>
+ </html:table>
+ </markup>
+
+ <markup id="HTMLTdARIAGridCellTest"
+ ref="html:table/html:tr/html:td" ruleset="HTMLARIAGridCell">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="gc" textequiv="test4">test4</html:label>
+ <html:table>
+ <html:tr>
+ <html:td id="gc"
+ role="gridcell"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ textequiv="This is a paragraph This is a link • Listitem1 • Listitem2"
+ title="This is a paragraph This is a link This is a list">
+ <html:p>This is a paragraph</html:p>
+ <html:a href="#">This is a link</html:a>
+ <html:ul>
+ <html:li>Listitem1</html:li>
+ <html:li>Listitem2</html:li>
+ </html:ul>
+ </html:td>
+ </html:tr>
+ </html:table>
+ </markup>
+
+ <markup id="HTMLTableTest"
+ ref="html:table" ruleset="HTMLTable">
+ <html:span id="l1" textequiv="lby_tst6_1">lby_tst6_1</html:span>
+ <html:span id="l2" textequiv="lby_tst6_2">lby_tst6_2</html:span>
+ <html:label for="t" textequiv="label_tst6">label_tst6</html:label>
+ <!-- layout frame are recreated due to varous reasons, here's text frame
+ placed after caption frame triggres table frame recreation when
+ caption element is removed from DOM; get rid text node after caption
+ node to make the test working -->
+ <html:table id="t" aria-label="arialabel_tst6"
+ aria-labelledby="l1 l2"
+ summary="summary_tst6"
+ title="title_tst6">
+ <html:caption textequiv="caption_tst6">caption_tst6</html:caption><html:tr>
+ <html:td>cell1</html:td>
+ <html:td>cell2</html:td>
+ </html:tr>
+ </html:table>
+ </markup>
+
+ </rulesample>
+</rules>
diff --git a/accessible/tests/mochitest/name/test_ARIACore_examples.html b/accessible/tests/mochitest/name/test_ARIACore_examples.html
new file mode 100644
index 0000000000..a15fee78f8
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_ARIACore_examples.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Testing a11y name ccomputation testcases</title>
+
+ <link rel="stylesheet"
+ type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // All test cases taken from https://www.w3.org/TR/accname-1.1/
+ // These were especially called out to demonstrate edge cases.
+
+ // Example 1 from section 4.3.1 under 2.B.
+ // Element1 should get its name from the text in element3.
+ // Element2 should not gets name from element1 because that already
+ // gets its name from another element.
+ testName("el1", "hello");
+ testName("el2", null);
+
+ // Example 2 from section 4.3.1 under 2.C.
+ // The buttons should get their name from their labels and the links.
+ testName("del_row1", "Delete Documentation.pdf");
+ testName("del_row2", "Delete HolidayLetter.pdf");
+
+ // Example 3 from section 4.3.1 under 2.F.
+ // Name should be own content text plus the value of the input plus
+ // more own inner text, separated by 1 space.
+ testName("chkbx", "Flash the screen 5 times");
+
+ // Example 4 from section 4.3.1 under 2.F.
+ // Name from content should include all the child nodes, including
+ // table cells.
+ testName("input_with_html_label", "foo bar baz");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- el1 should be labeled, el2 should not. -->
+ <div id="el1" aria-labelledby="el3"></div>
+ <div id="el2" aria-labelledby="el1"></div>
+ <div id="el3"> hello </div>
+
+ <!-- The buttons should be labeled by themselves and the referenced link -->
+ <ul>
+ <li>
+ <a id="file_row1" href="./files/Documentation.pdf">Documentation.pdf</a>
+ <span role="button" tabindex="0" id="del_row1" aria-label="Delete"
+ aria-labelledby="del_row1 file_row1"></span>
+ </li>
+ <li>
+ <a id="file_row2" href="./files/HolidayLetter.pdf">HolidayLetter.pdf</a>
+ <span role="button" tabindex="0" id="del_row2" aria-label="Delete"
+ aria-labelledby="del_row2 file_row2"></span>
+ </li>
+ </ul>
+
+ <!-- Label from combined text and subtree -->
+ <div id="chkbx" role="checkbox" aria-checked="false">Flash the screen
+ <span role="textbox" aria-multiline="false"> 5 </span> times</div>
+
+ <!-- Label with name from content should include table -->
+ <input id="input_with_html_label" />
+ <label for="input_with_html_label" id="label">
+ <div>foo</div>
+ <table><tr><td>bar</td></tr></table>
+ <div>baz</div>
+ </label>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_browserui.xhtml b/accessible/tests/mochitest/name/test_browserui.xhtml
new file mode 100644
index 0000000000..348cb89202
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_browserui.xhtml
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Name Calculating Test.">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ const { BrowserTestUtils } = ChromeUtils.import(
+ "resource://testing-common/BrowserTestUtils.jsm");
+ const ABOUT_MOZILLA_URL = "about:mozilla";
+ const ABOUT_LICENSE_URL = "about:license";
+
+ SimpleTest.waitForExplicitFinish();
+
+ (async () => {
+ info("Opening a new browser window.");
+ const win = await BrowserTestUtils.openNewBrowserWindow({
+ remote: false,
+ fission: false,
+ });
+ const winFocused = SimpleTest.promiseFocus(win);
+ const loaded = BrowserTestUtils.browserLoaded(
+ win.gBrowser.selectedBrowser);
+ let docLoaded = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, event =>
+ event.accessible.QueryInterface(nsIAccessibleDocument).URL === ABOUT_LICENSE_URL,
+ `Loaded tab: ${ABOUT_LICENSE_URL}`);
+ BrowserTestUtils.loadURIString(win.gBrowser.selectedBrowser,
+ "about:license");
+ await loaded;
+ await docLoaded;
+ await winFocused;
+
+ info(`Loading a new tab: ${ABOUT_MOZILLA_URL}.`);
+ docLoaded = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, event =>
+ event.accessible.QueryInterface(nsIAccessibleDocument).URL === ABOUT_MOZILLA_URL,
+ `Added tab: ${ABOUT_MOZILLA_URL}`);
+ const tab = win.gBrowser.addTrustedTab(ABOUT_MOZILLA_URL);
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await docLoaded;
+
+ info("Focusing on the newly opened tab.");
+ const focused = waitForEvent(EVENT_FOCUS, event =>
+ event.DOMNode === win.gBrowser.getBrowserAtIndex(1).contentDocument);
+ await BrowserTestUtils.synthesizeKey("VK_TAB", { ctrlKey: true },
+ win.gBrowser.selectedBrowser);
+ const focusEvent = await focused;
+
+ const title = getAccessible(win.document).name;
+ const accName = focusEvent.accessible.name;
+ isnot(title.indexOf(accName), -1,
+ `Window title contains the name of active tab document (Is "${accName}" in "${title}"?)`);
+
+ await BrowserTestUtils.closeWindow(win);
+ SimpleTest.finish();
+ })();
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=507382"
+ title="focus is fired earlier than root accessible name is changed when switching between tabs">
+ Mozilla Bug
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="eventdump"></vbox>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/name/test_counterstyle.html b/accessible/tests/mochitest/name/test_counterstyle.html
new file mode 100644
index 0000000000..a20aca23f1
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_counterstyle.html
@@ -0,0 +1,150 @@
+<html>
+
+<head>
+ <title>nsIAccessible::name calculation for @counter-style</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <style id="counterstyles" type="text/css">
+ @counter-style system-alphabetic {
+ system: alphabetic;
+ symbols: x y z;
+ }
+ @counter-style system-cyclic {
+ system: cyclic;
+ symbols: x y z;
+ }
+ @counter-style system-numeric {
+ system: numeric;
+ symbols: x y z;
+ }
+ @counter-style speak-as-bullets {
+ system: extends decimal;
+ speak-as: bullets;
+ }
+ @counter-style speak-as-numbers {
+ system: extends system-alphabetic;
+ speak-as: numbers;
+ }
+ @counter-style speak-as-words {
+ system: additive;
+ additive-symbols: 20 "twenty ", 9 "nine", 7 "seven", 1 "one";
+ speak-as: words;
+ }
+ @counter-style speak-as-spell-out {
+ system: extends system-alphabetic;
+ speak-as: spell-out;
+ }
+ @counter-style speak-as-other {
+ system: extends decimal;
+ speak-as: speak-as-words;
+ }
+ @counter-style speak-as-loop {
+ system: extends upper-latin;
+ speak-as: speak-as-loop0;
+ }
+ @counter-style speak-as-loop0 {
+ system: extends disc;
+ speak-as: speak-as-loop1;
+ }
+ @counter-style speak-as-loop1 {
+ system: extends decimal;
+ speak-as: speak-as-loop0;
+ }
+ @counter-style speak-as-extended0 {
+ system: extends decimal;
+ speak-as: speak-as-extended1;
+ }
+ @counter-style speak-as-extended1 {
+ system: extends speak-as-extended0;
+ speak-as: disc;
+ }
+ @counter-style speak-as-extended2 {
+ system: extends decimal;
+ speak-as: speak-as-extended3;
+ }
+ @counter-style speak-as-extended3 {
+ system: extends speak-as-extended2;
+ }
+ </style>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ function testRule(aRule, aNames, aTodo) {
+ testName(aRule + "-1", aNames[0], null, aTodo);
+ testName(aRule + "-7", aNames[1], null, aTodo);
+ testName(aRule + "-29", aNames[2], null, aTodo);
+ }
+
+ var spellOutNames = ["X. 1", "Y X. 7", "Y Z Y. 29"];
+ var bulletsNames = [kDiscBulletText + "1",
+ kDiscBulletText + "7",
+ kDiscBulletText + "29"];
+ var numbersNames = ["1. 1", "7. 7", "29. 29"];
+ var wordsNames = ["one. 1", "seven. 7", "twenty nine. 29"];
+
+ testRule("system-alphabetic", spellOutNames, true); // bug 1024178
+ testRule("system-cyclic", bulletsNames);
+ testRule("system-numeric", numbersNames);
+
+ testRule("speak-as-bullets", bulletsNames);
+ testRule("speak-as-numbers", numbersNames);
+ testRule("speak-as-words", wordsNames);
+ testRule("speak-as-spell-out", spellOutNames, true); // bug 1024178
+ testRule("speak-as-other", wordsNames);
+
+ testRule("speak-as-loop", bulletsNames);
+ testRule("speak-as-loop0", bulletsNames);
+ testRule("speak-as-loop1", numbersNames);
+
+ testRule("speak-as-extended0", bulletsNames);
+ testRule("speak-as-extended1", bulletsNames);
+ testRule("speak-as-extended2", numbersNames);
+ testRule("speak-as-extended3", numbersNames);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=966166"
+ title="Bug 966166 - Implement @counter-style rule">
+ Bug 966166
+ </a>
+
+ <ol id="list"></ol>
+
+ <script type="application/javascript">
+ var list = getNode("list");
+ var rules = getNode("counterstyles").sheet.cssRules;
+ var values = [1, 7, 29];
+ for (var i = 0; i < rules.length; i++) {
+ var rule = rules[i];
+ for (var j = 0; j < values.length; j++) {
+ var item = document.createElement("li");
+ item.id = rule.name + "-" + values[j];
+ item.value = values[j];
+ item.textContent = values[j];
+ item.setAttribute("style", "list-style-type: " + rule.name);
+ list.appendChild(item);
+ }
+ }
+ </script>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_general.html b/accessible/tests/mochitest/name/test_general.html
new file mode 100644
index 0000000000..89314712fb
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_general.html
@@ -0,0 +1,732 @@
+<html>
+
+<head>
+ <title>nsIAccessible::name calculation</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // aria-label
+
+ // Simple label provided via ARIA
+ testName("btn_simple_aria_label", "I am a button");
+
+ // aria-label and aria-labelledby, expect aria-labelledby
+ testName("btn_both_aria_labels", "text I am a button, two");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // aria-labelledby
+
+ // Single relation. The value of 'aria-labelledby' contains the ID of
+ // an element. Gets the name from text node of that element.
+ testName("btn_labelledby_text", "text");
+
+ // Multiple relations. The value of 'aria-labelledby' contains the IDs
+ // of elements. Gets the name from text nodes of those elements.
+ testName("btn_labelledby_texts", "text1 text2");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Name from named accessible
+
+ testName("input_labelledby_namedacc", "Data");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Name from subtree (single relation labelled_by).
+
+ // Gets the name from text nodes contained by nested elements
+ testName("btn_labelledby_mixed", "nomore text");
+
+ // Gets the name from text nodes contained by nested elements, ignores
+ // hidden elements (bug 443081).
+ testName("btn_labelledby_mixed_hidden_child", "nomore text2");
+
+ // Gets the name from hidden text nodes contained by nested elements,
+ // (label element is hidden entirely), (bug 443081).
+ testName("btn_labelledby_mixed_hidden", "lala more hidden text");
+
+ // Gets the name from text nodes contained by nested elements having block
+ // representation (every text node value in the name should be devided by
+ // spaces)
+ testName("btn_labelledby_mixed_block", "text more text");
+
+ // Gets the name from text nodes contained by html:td. The text nodes
+ // should not be divided by spaces.
+ testName("btn_labelledby_mixed_table", "textTEXTtext");
+
+ // Gets the name from image accessible.
+ testName("btn_labelledby_mixed_img", "text image");
+
+ // Gets the name from input accessibles
+ // Note: if input have label elements then the name isn't calculated
+ // from them.
+ testName("btn_labelledby_mixed_input",
+ "input button Submit Query Reset Submit Query");
+
+ // Gets the name from the title of object element.
+ testName("btn_labelledby_mixed_object", "object");
+
+ // Gets the name from text nodes. Element br adds space between them.
+ testName("btn_labelledby_mixed_br", "text text");
+
+ // Gets the name from label content which allows name from subtree,
+ // ignore @title attribute on label
+ testName("from_label_ignoretitle", "Country:");
+
+ // Gets the name from html:p content, which doesn't allow name from
+ // subtree, ignore @title attribute on label
+ testName("from_p_ignoretitle", "Choose country from.");
+
+ // Gets the name from html:input value, ignore @title attribute on input
+ testName("from_input_ignoretitle", "Custom country");
+
+ // Insert spaces around the control's value to not jamm sibling text nodes
+ testName("insert_spaces_around_control", "start value end");
+
+ // Gets the name from @title, ignore whitespace content
+ testName("from_label_ignore_ws_subtree", "about");
+
+ // role="alert" doesn't get name from subtree...
+ testName("alert", null);
+ // but the subtree is used if referenced by aria-labelledby.
+ testName("inputLabelledByAlert", "Error");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // label element
+
+ // The label element contains the button. The name is calculated from
+ // this button.
+ // Note: the name contains the content of the button.
+ testName("btn_label_inside", "text10text");
+
+ // The label element and the button are placed in the same form. Gets
+ // the name from the label subtree.
+ testName("btn_label_inform", "in form");
+
+ // The label element is placed outside of form where the button is.
+ // Take into account the label.
+ testName("btn_label_outform", "out form");
+
+ // The label element and the button are in the same document. Gets the
+ // name from the label subtree.
+ testName("btn_label_indocument", "in document");
+
+ // Multiple label elements for single button
+ testName("btn_label_multi", "label1label2");
+
+ // Multiple controls inside a label element
+ testName("ctrl_in_label_1", "Enable a button control");
+ testName("ctrl_in_label_2", "button");
+
+
+ // ////////////////////////////////////////////////////////////////////////
+ // name from children
+
+ // ARIA role button is presented allowing the name calculation from
+ // children.
+ testName("btn_children", "14");
+
+ // html:button, no name from content
+ testName("btn_nonamefromcontent", null);
+
+ // ARIA role option is presented allowing the name calculation from
+ // visible children (bug 443081).
+ testName("lb_opt1_children_hidden", "i am visible");
+
+ // Get the name from subtree of menuitem crossing role nothing to get
+ // the name from its children.
+ testName("tablemenuitem", "menuitem 1");
+
+ // Get the name from child acronym title attribute rather than from
+ // acronym content.
+ testName("label_with_acronym", "O A T F World Wide Web");
+
+ testName("testArticle", "Test article");
+
+ testName("h1", "heading");
+ testName("aria_heading", "aria_heading");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // title attribute
+
+ // If nothing is left. Let's try title attribute.
+ testName("btn_title", "title");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // textarea name
+
+ // textarea's name should have the value, which initially is specified by
+ // a text child.
+ testName("textareawithchild", "Story Foo is ended.");
+
+ // new textarea name should reflect the value change.
+ var elem = document.getElementById("textareawithchild");
+ elem.value = "Bar";
+
+ testName("textareawithchild", "Story Bar is ended.");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // controls having a value used as a part of computed name
+
+ testName("ctrlvalue_progressbar:input", "foo 5 baz");
+ testName("ctrlvalue_scrollbar:input", "foo 5 baz");
+ testName("ctrlvalue_slider:input", "foo 5 baz");
+ testName("ctrlvalue_spinbutton:input", "foo 5 baz");
+ testName("ctrlvalue_combobox:input", "foo 5 baz");
+ testName("ctrlvalue_meter:input", "foo 5 baz");
+
+
+ // ///////////////////////////////////////////////////////////////////////
+ // label with nested combobox (test for 'f' item of name computation guide)
+
+ testName("comboinstart", "One day(s).");
+ testName("combo3", "day(s).");
+
+ testName("textboxinstart", "Two days.");
+ testName("textbox1", "days.");
+
+ testName("comboinmiddle", "Subscribe to ATOM feed.");
+ testName("combo4", "Subscribe to ATOM feed.");
+
+ testName("comboinmiddle2", "Play the Haliluya sound when new mail arrives");
+ testName("combo5", null); // label isn't used as a name for control
+ testName("checkbox", "Play the Haliluya sound when new mail arrives");
+ testName("comboinmiddle3", "Play the Haliluya sound when new mail arrives");
+ testName("combo6", "Play the Haliluya sound when new mail arrives");
+
+ testName("comboinend", "This day was sunny");
+ testName("combo7", "This day was");
+
+ testName("textboxinend", "This day was sunny");
+ testName("textbox2", "This day was");
+
+ // placeholder
+ testName("ph_password", "a placeholder");
+ testName("ph_text", "a placeholder");
+ testName("ph_textarea", "a placeholder");
+ testName("ph_text2", "a label");
+ testName("ph_textarea2", "a label");
+ testName("ph_text3", "a label");
+
+ // Test equation image
+ testName("img_eq", "x^2 + y^2 + z^2");
+ testName("input_img_eq", "x^2 + y^2 + z^2");
+ testName("txt_eq", "x^2 + y^2 + z^2");
+
+ // //////////////////////////////////////////////////////////////////////
+ // tests for duplicate announcement of content
+
+ testName("test_note", null);
+
+ // //////////////////////////////////////////////////////////////////////
+ // Tests for name from sub tree of tr element.
+
+ // By default, we want no name.
+ testName("NoNameForTR", null);
+ testName("NoNameForNonStandardTR", null);
+
+ // But we want it if the tr has an ARIA role.
+ testName("NameForTRBecauseStrongARIA", "a b");
+
+ // But not if it is a weak (landmark) ARIA role
+ testName("NoNameForTRBecauseWeakARIA", null);
+
+ // Name from sub tree of grouping if requested by other accessible.
+ testName("grouping", null);
+ testName("requested_name_from_grouping", "label");
+ testName("listitem_containing_block_tbody", "label");
+ // Groupings shouldn't be included when calculating from the subtree of
+ // a treeitem.
+ testName("treeitem_containing_grouping", "root");
+
+ // Name from subtree of grouping labelled by an ancestor.
+ testName("grouping_labelledby_ancestor", "label");
+
+ // Name from subtree of a container containing text nodes and inline
+ // elements. There should be no spaces because everyhing is inline.
+ testName("container_text_inline", "abc");
+ // Name from subtree of a container containing text nodes and block
+ // elements. There should be a space on both sides of the block.
+ testName("container_text_block", "a b c");
+ // Name from subtree of a container containing text nodes and empty
+ // block elements. There should be space on either side of the blocks, but
+ // not a double space.
+ testName("container_text_emptyblock", "a b");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=428479"
+ title="Bug 428479 - Support ARIA role=math">
+ Bug 428479
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=429666"
+ title="Expose ROLE_DOCUMENT for ARIA landmarks that inherit from document">
+ Bug 429666
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=444279"
+ title="mochitest for accessible name calculating">
+ Bug 444279
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=459635"
+ title="nsIAccessible::name calculation for HTML buttons">
+ Bug 459635
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=530081"
+ title="Clean up our tree walker">
+ Bug 530081
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=604391"
+ title="Use placeholder as name if name is otherwise empty">
+ Bug 604391
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=669312"
+ title="Accessible name is duplicated when input has a label associated uisng for/id and is wrapped around the input">
+ Bug 669312
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=704416"
+ title="HTML acronym and abbr names should be provided by @title">
+ Bug 704416
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=812041"
+ title="ARIA slider and spinbutton don't provide a value for name computation">
+ Bug 812041
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=823927"
+ title="Text is jammed with control's text in name computation">
+ Bug 823927
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=835666"
+ title="ARIA combobox selected value is not a part of name computation">
+ Bug 835666
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=833256"
+ title="role note shouldn't pick up the name from subtree">
+ Mozilla Bug 833256
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- aria-label, simple label -->
+ <span id="btn_simple_aria_label" role="button" aria-label="I am a button"/>
+ <br/>
+ <!-- aria-label plus aria-labelledby -->
+ <span id="btn_both_aria_labels" role="button" aria-label="I am a button, two"
+ aria-labelledby="labelledby_text btn_both_aria_labels"/>
+ <br/>
+
+ <!-- aria-labelledby, single relation -->
+ <span id="labelledby_text">text</span>
+ <button id="btn_labelledby_text"
+ aria-labelledby="labelledby_text">1</button>
+ <br/>
+
+ <!-- aria-labelledby, multiple relations -->
+ <span id="labelledby_text1">text1</span>
+ <span id="labelledby_text2">text2</span>
+ <button id="btn_labelledby_texts"
+ aria-labelledby="labelledby_text1 labelledby_text2">2</button>
+ <br/>
+
+ <!-- name from named accessible -->
+ <input id="labelledby_namedacc" type="checkbox"
+ aria-label="Data" />
+ <input id="input_labelledby_namedacc"
+ aria-labelledby="labelledby_namedacc" />
+
+ <!-- the name from subtree, mixed content -->
+ <span id="labelledby_mixed">no<span>more text</span></span>
+ <button id="btn_labelledby_mixed"
+ aria-labelledby="labelledby_mixed">3</button>
+ <br/>
+
+ <!-- the name from subtree, mixed/hidden content -->
+ <span id="labelledby_mixed_hidden_child">
+ no<span>more
+ <span style="display: none;">hidden</span>
+ text2
+ <span style="visibility: hidden">hidden2</span>
+ </span>
+ </span>
+ <button id="btn_labelledby_mixed_hidden_child"
+ aria-labelledby="labelledby_mixed_hidden_child">3.1</button>
+ <br/>
+
+ <!-- the name from subtree, mixed/completely hidden content -->
+ <span id="labelledby_mixed_hidden"
+ style="display: none;">lala <span>more hidden </span>text</span></span>
+ <button id="btn_labelledby_mixed_hidden"
+ aria-labelledby="labelledby_mixed_hidden">3.2</button>
+ <br/>
+
+ <!-- the name from subtree, mixed content, block structure -->
+ <div id="labelledby_mixed_block"><div>text</div>more text</div></div>
+ <button id="btn_labelledby_mixed_block"
+ aria-labelledby="labelledby_mixed_block">4</button>
+ <br/>
+
+ <!-- the name from subtree, mixed content, table structure -->
+ <table><tr>
+ <td id="labelledby_mixed_table">text<span>TEXT</span>text</td>
+ </tr></table>
+ <button id="btn_labelledby_mixed_table"
+ aria-labelledby="labelledby_mixed_table">5</button>
+ <br/>
+
+ <!-- the name from subtree, child img -->
+ <span id="labelledby_mixed_img">text<img alt="image"/></span>
+ <button id="btn_labelledby_mixed_img"
+ aria-labelledby="labelledby_mixed_img">6</button>
+ <br/>
+
+ <!-- the name from subtree, child inputs -->
+ <span id="labelledby_mixed_input">
+ <input type="button" id="input_button" title="input button"/>
+ <input type="submit" id="input_submit"/>
+ <input type="reset" id="input_reset"/>
+ <input type="image" id="input_image" title="input image"/>
+ </span>
+ <button id="btn_labelledby_mixed_input"
+ aria-labelledby="labelledby_mixed_input">7</button>
+ <br/>
+
+ <!-- the name from subtree, child object -->
+ <span id="labelledby_mixed_object">
+ <object data="about:blank" title="object"></object>
+ </span>
+ <button id="btn_labelledby_mixed_object"
+ aria-labelledby="labelledby_mixed_object">8</button>
+ <br/>
+
+ <!-- the name from subtree, child br -->
+ <span id="labelledby_mixed_br">text<br/>text</span>
+ <button id="btn_labelledby_mixed_br"
+ aria-labelledby="labelledby_mixed_br">9</button>
+ <br/>
+
+ <!-- the name from subtree, name from label content rather than from its title
+ attribute -->
+ <label for="from_label_ignoretitle"
+ title="Select your country of origin">Country:</label>
+ <select id="from_label_ignoretitle">
+ <option>Germany</option>
+ <option>Russia</option>
+ </select>
+
+ <!-- the name from subtree, name from html:p content rather than from its
+ title attribute -->
+ <p id="p_ignoretitle"
+ title="Select your country of origin">Choose country from.</p>
+ <select id="from_p_ignoretitle" aria-labelledby="p_ignoretitle">
+ <option>Germany</option>
+ <option>Russia</option>
+ </select>
+
+ <!-- the name from subtree, name from html:input value rather than from its
+ title attribute -->
+ <p id="from_input_ignoretitle" aria-labelledby="input_ignoretitle">Country</p>
+ <input id="input_ignoretitle"
+ value="Custom country"
+ title="Input your country of origin"/ >
+
+ <!-- name from subtree, surround control by spaces to not jamm the text -->
+ <label id="insert_spaces_around_control">
+ start<input value="value">end
+ </label>
+
+ <!-- no name from subtree because it holds whitespaces only -->
+ <a id="from_label_ignore_ws_subtree" href="about:mozilla" title="about">&nbsp;&nbsp; </a>
+
+ <!-- Don't use subtree unless referenced by aria-labelledby. -->
+ <div id="alert" role="alert">Error</div>
+ <input type="text" id="inputLabelledByAlert" aria-labelledby="alert">
+
+ <!-- label element, label contains control -->
+ <label>text<button id="btn_label_inside">10</button>text</label>
+ <br/>
+
+ <!-- label element, label and the button are in the same form -->
+ <form>
+ <label for="btn_label_inform">in form</label>
+ <button id="btn_label_inform">11</button>
+ </form>
+
+ <!-- label element, label is outside of the form of the button -->
+ <label for="btn_label_outform">out form</label>
+ <form>
+ <button id="btn_label_outform">12</button>
+ </form>
+
+ <!-- label element, label and the button are in the same document -->
+ <label for="btn_label_indocument">in document</label>
+ <button id="btn_label_indocument">13</button>
+
+ <!-- multiple label elements for single button -->
+ <label for="btn_label_multi">label1</label>
+ <label for="btn_label_multi">label2</label>
+ <button id="btn_label_multi">button</button>
+
+ <!-- a label containing more than one controls -->
+ <label>
+ Enable <input id="ctrl_in_label_1" type="checkbox"> a
+ <input id="ctrl_in_label_2" type="button" value="button"> control
+ </label>
+
+ <!-- name from children -->
+ <span id="btn_children" role="button">14</span>
+
+ <!-- no name from content, ARIA role overrides this rule -->
+ <button id="btn_nonamefromcontent" role="img">1</button>
+
+ <!-- name from children, hidden children -->
+ <div role="listbox" tabindex="0">
+ <div id="lb_opt1_children_hidden" role="option" tabindex="0">
+ <span>i am visible</span>
+ <span style="display:none">i am hidden</span>
+ </div>
+ </div>
+
+ <table role="menu">
+ <tr role="menuitem" id="tablemenuitem">
+ <td>menuitem 1</td>
+ </tr>
+ <tr role="menuitem">
+ <td>menuitem 2</td>
+ </tr>
+ </table>
+
+ <label id="label_with_acronym">
+ <acronym title="O A T F">OATF</acronym>
+ <abbr title="World Wide Web">WWW</abbr>
+ </label>
+
+ <div id="testArticle" role="article" title="Test article">
+ <p>This is a paragraph inside the article.</p>
+ </div>
+
+ <h1 id="h1" title="oops">heading</h1>
+ <div role="heading" id="aria_heading">aria_heading</div>
+
+ <!-- name from title attribute -->
+ <span id="btn_title" role="group" title="title">15</span>
+
+ <!-- A textarea nested in a label with a text child (bug #453371). -->
+ <form>
+ <label>Story
+ <textarea id="textareawithchild" name="name">Foo</textarea>
+ is ended.
+ </label>
+ </form>
+
+ <!-- controls having a value used as part of computed name -->
+ <input type="checkbox" id="ctrlvalue_progressbar:input">
+ <label for="ctrlvalue_progressbar:input">
+ foo <span role="progressbar"
+ aria-valuenow="5" aria-valuemin="1"
+ aria-valuemax="10">5</span> baz
+ </label>
+
+ <input type="checkbox" id="ctrlvalue_scrollbar:input" />
+ <label for="ctrlvalue_scrollbar:input">
+ foo <span role="scrollbar"
+ aria-valuenow="5" aria-valuemin="1"
+ aria-valuemax="10">5</span> baz
+ </label>
+
+ <input type="checkbox" id="ctrlvalue_slider:input">
+ <label for="ctrlvalue_slider:input">
+ foo <input role="slider" type="range"
+ value="5" min="1" max="10"
+ aria-valuenow="5" aria-valuemin="1"
+ aria-valuemax="10"> baz
+ </label>
+
+ <input type="checkbox" id="ctrlvalue_spinbutton:input">
+ <label for="ctrlvalue_spinbutton:input">
+ foo <input role="spinbutton" type="number"
+ value="5" min="1" max="10"
+ aria-valuenow="5" aria-valuemin="1"
+ aria-valuemax="10">
+ baz
+ </label>
+
+ <input type="checkbox" id="ctrlvalue_combobox:input">
+ <label for="ctrlvalue_combobox:input">
+ foo
+ <div role="combobox">
+ <div role="textbox"></div>
+ <ul role="listbox" style="list-style-type: none;">
+ <li role="option">1</li>
+ <li role="option" aria-selected="true">5</li>
+ <li role="option">3</li>
+ </ul>
+ </div>
+ baz
+ </label>
+
+ <input type="checkbox" id="ctrlvalue_meter:input">
+ <label for="ctrlvalue_meter:input">
+ foo
+ <meter>5</meter>
+ </div>
+ baz
+ </label>
+
+ <!-- a label with a nested control in the start, middle and end -->
+ <form>
+ <!-- at the start (without and with whitespaces) -->
+ <label id="comboinstart"><select id="combo3">
+ <option>One</option>
+ <option>Two</option>
+ </select>
+ day(s).
+ </label>
+
+ <label id="textboxinstart">
+ <input id="textbox1" value="Two">
+ days.
+ </label>
+
+ <!-- in the middle -->
+ <label id="comboinmiddle">
+ Subscribe to
+ <select id="combo4" name="occupation">
+ <option>ATOM</option>
+ <option>RSS</option>
+ </select>
+ feed.
+ </label>
+
+ <label id="comboinmiddle2" for="checkbox">Play the
+ <select id="combo5">
+ <option>Haliluya</option>
+ <option>Hurra</option>
+ </select>
+ sound when new mail arrives
+ </label>
+ <input id="checkbox" type="checkbox" />
+
+ <label id="comboinmiddle3" for="combo6">Play the
+ <select id="combo6">
+ <option>Haliluya</option>
+ <option>Hurra</option>
+ </select>
+ sound when new mail arrives
+ </label>
+
+ <!-- at the end (without and with whitespaces) -->
+ <label id="comboinend">
+ This day was
+ <select id="combo7" name="occupation">
+ <option>sunny</option>
+ <option>rainy</option>
+ </select></label>
+
+ <label id="textboxinend">
+ This day was
+ <input id="textbox2" value="sunny">
+ </label>
+ </form>
+
+ <!-- placeholder -->
+ <input id="ph_password" type="password" value="" placeholder="a placeholder" />
+ <input id="ph_text" type="text" placeholder="a placeholder" />
+ <textarea id="ph_textarea" cols="5" placeholder="a placeholder"></textarea>
+
+ <!-- placeholder does not win -->
+ <input id="ph_text2" type="text" aria-label="a label" placeholder="meh" />
+ <textarea id="ph_textarea2" cols="5" aria-labelledby="ph_text2"
+ placeholder="meh"></textarea>
+
+ <label for="ph_text3">a label</label>
+ <input id="ph_text3" placeholder="meh" />
+
+ <p>Image:
+ <img id="img_eq" role="math" src="foo" alt="x^2 + y^2 + z^2">
+ <input type="image" id="input_img_eq" src="foo" alt="x^2 + y^2 + z^2">
+ </p>
+
+ <p>Text:
+ <span id="txt_eq" role="math" title="x^2 + y^2 + z^2">x<sup>2</sup> +
+ y<sup>2</sup> + z<sup>2</sup></span>
+
+ <!-- duplicate announcement -->
+ <div id="test_note" role="note">subtree</div>
+
+ <!-- No name for tr from its sub tree -->
+ <table><tr id="NoNameForTR"><th>a</th><td>b</td></tr></table>
+ <table style="display: block;">
+ <tr id="NoNameForNonStandardTR" style="display:block;">
+ <th>a</th><td>b</td>
+ </tr>
+ </table>
+
+ <!-- Name from sub tree of tr, because it has a strong ARIA role -->
+ <table><tr id="NameForTRBecauseStrongARIA" role="row"><th>a</th><td>b</td></tr></table>
+
+ <!-- No name for tr because of weak (landmark) role -->
+ <table><tr id="NoNameForTRBecauseWeakARIA" role="main"><th>a</th><td>b</td></tr></table>
+
+ <!-- Name from subtree of grouping if requested by other object -->
+ <div id="grouping" role="group">label</div>
+ <button id="requested_name_from_grouping"aria-labelledby="grouping"></button>
+ <!-- Name from sub tree of tbody marked as display:block;, which is also a grouping -->
+ <div id="listitem_containing_block_tbody" role="listitem">
+ <table>
+ <tbody style="display: block;">
+ <tr><td>label</td></tr>
+ </tbody>
+ </table>
+ </div>
+ <!-- Name from subtree of treeitem containing grouping -->
+ <div id="treeitem_containing_grouping" role="treeitem" aria-level="1" aria-expanded="true">root
+ <div role="group">
+ <div role="treeitem" aria-level="2">sub</div>
+ </div>
+ </div>
+
+ <!-- Name from subtree of grouping labelled by an ancestor. -->
+ <div id="grouping_ancestor_label">label
+ <div id="grouping_labelledby_ancestor" role="group" aria-labelledby="grouping_ancestor_label">
+ This content should not be included in the grouping's label.
+ </div>
+ </div>
+
+ <!-- Text nodes and inline elements. -->
+ <div id="container_text_inline" role="option">a<strong>b</strong>c</div>
+ <!-- Text nodes and block elements. -->
+ <div id="container_text_block" role="option">a<p>b</p>c</div>
+ <!-- Text nodes and empty block elements. -->
+ <div id="container_text_emptyblock" role="option">a<p></p><p></p>b</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_general.xhtml b/accessible/tests/mochitest/name/test_general.xhtml
new file mode 100644
index 0000000000..a1c6f1461e
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_general.xhtml
@@ -0,0 +1,343 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Accessibility Name Calculating Test.">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ // aria-label
+
+ // Simple label provided via ARIA
+ testName("btn_simple_aria_label", "I am a button");
+
+ // aria-label and aria-labelledby, expect aria-labelledby
+ testName("btn_both_aria_labels", "text I am a button, two");
+
+ //////////////////////////////////////////////////////////////////////////
+ // aria-labelledby
+
+ // Single relation. The value of 'aria-labelledby' contains the ID of
+ // an element. Gets the name from text node of that element.
+ testName("btn_labelledby_text", "text");
+
+ // Multiple relations. The value of 'aria-labelledby' contains the IDs
+ // of elements. Gets the name from text nodes of those elements.
+ testName("btn_labelledby_texts", "text1 text2");
+
+ // Trick cases. Self and recursive referencing.
+ testName("rememberHistoryDays", "Remember 3 days");
+ testName("historyDays", "Remember 3 days");
+ testName("rememberAfter", "days");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from subtree (single relation labelled_by).
+
+ // Gets the name from text nodes contained by nested elements.
+ testName("btn_labelledby_mixed", "no more text");
+
+ // Gets the name from text nodes and selected item of menulist
+ // (other items are ignored).
+ testName("btn_labelledby_mixed_menulist",
+ "no more text selected item more text");
+
+ // Gets the name from text nodes contained by nested elements, ignores
+ // hidden elements (bug 443081).
+ testName("btn_labelledby_mixed_hidden_child", "no more text2");
+
+ // Gets the name from hidden text nodes contained by nested elements,
+ // (label element is hidden entirely), (bug 443081)
+ testName("btn_labelledby_mixed_hidden", "lala more hidden text");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from @label attribute.
+
+ // Gets the name from @label attribute.
+ testName("btn_labelattr", "labeled element");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name for nsIDOMXULSelectControlItemElement.
+
+ // Gets the name from @label attribute.
+ testName("li_nsIDOMXULSelectControlItemElement", "select control item");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name if the XUL element doesn't implement nsIDOMXULSelectControlElement
+ // and has @label attribute.
+
+ testName("box_not_nsIDOMXULSelectControlElement", "box");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from the label element.
+
+ // The label and button are placed on 2nd level relative common parent.
+ testName("btn_label_1", "label1");
+
+ // The label is on 1st, the button is on 5th level relative common parent.
+ testName("btn_label_2", "label2");
+
+ // The label and button are siblings.
+ testName("btn_label_3", "label3");
+
+ // Multiple labels for single button: XUL button takes the last one.
+ testName("btn_label_4", "label5");
+
+ // Label associated with HTML element.
+ testName("input_label", "input label");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // tooltiptext (if nothing above isn't presented then tooltiptext is used)
+ testName("box_tooltiptext", "tooltiptext label");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from the @title attribute of <toolbaritem/> (original bug 237249).
+
+ // Direct child of toolbaritem.
+ testName("toolbaritem_child", null);
+
+ // Child from subtree of toolbaritem.
+ testName("toolbaritem_hboxbutton", "button");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // name from label inside toolbar button
+ testName("toolbarbuttonwithlabel", "I am the button");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from children
+
+ // ARIA role button is presented allowing the name calculation from
+ // children.
+ testName("box_children", "14");
+
+ // Button labelled by a text child.
+ testName("button_text", "Text");
+
+ // ARIA role option is presented allowing the name calculation from
+ // the visible children (bug 443081)
+ testName("lb_opt1_children_hidden", "i am visible");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from aria-labelledby: menuitem label+ listitem label
+ testName("li_labelledby", "Show an Alert The moment the event starts");
+
+ //////////////////////////////////////////////////////////////////////////
+ // groupbox labeling from first label
+ testName("groupbox", "Some caption");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=444279"
+ title="mochitest for accessible name calculating">
+ Mozilla Bug 444279
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=441991"
+ title="nsXULListitemAccessible::GetName prefers label \
+ attribute over aria-labelledby and doesn't allow recursion">
+ Mozilla Bug 441991
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <!-- aria-label, simple label -->
+ <button id="btn_simple_aria_label" aria-label="I am a button"/>
+
+ <!-- aria-label plus aria-labelledby -->
+ <button id="btn_both_aria_labels" aria-label="I am a button, two"
+ aria-labelledby="labelledby_text btn_both_aria_labels"/>
+
+ <!-- aria-labelledby, single relation -->
+ <description id="labelledby_text">text</description>
+ <button id="btn_labelledby_text"
+ aria-labelledby="labelledby_text"/>
+
+ <!-- aria-labelledby, multiple relations -->
+ <description id="labelledby_text1">text1</description>
+ <description id="labelledby_text2">text2</description>
+ <button id="btn_labelledby_texts"
+ aria-labelledby="labelledby_text1 labelledby_text2"/>
+
+ <!-- trick aria-labelledby -->
+ <checkbox id="rememberHistoryDays"
+ label="Remember "
+ aria-labelledby="rememberHistoryDays historyDays rememberAfter"/>
+ <html:input id="historyDays" value="3"
+ aria-labelledby="rememberHistoryDays historyDays rememberAfter"/>
+ <label id="rememberAfter">days</label>
+
+ <!-- the name from subtree, mixed content -->
+ <description id="labelledby_mixed">
+ no<description>more text</description>
+ </description>
+ <button id="btn_labelledby_mixed"
+ aria-labelledby="labelledby_mixed"/>
+
+ <!-- the name from subtree, mixed/hidden content -->
+ <description id="labelledby_mixed_hidden_child">no<description>more <description hidden="true">hidden</description>text2</description></description>
+ <button id="btn_labelledby_mixed_hidden_child"
+ aria-labelledby="labelledby_mixed_hidden_child"/>
+
+ <!-- the name from subtree, mixed/completely hidden content -->
+ <description id="labelledby_mixed_hidden"
+ hidden="true">lala <description>more hidden </description>text</description>
+ <button id="btn_labelledby_mixed_hidden"
+ aria-labelledby="labelledby_mixed_hidden"/>
+ <br/>
+
+ <!-- the name from subtree, mixed content, ignore items of menulist -->
+ <description id="labelledby_mixed_menulist">
+ no<description>more text</description>
+ <menulist>
+ <menupopup>
+ <menuitem label="selected item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menulist>
+ more text
+ </description>
+ <button id="btn_labelledby_mixed_menulist"
+ aria-labelledby="labelledby_mixed_menulist"/>
+
+ <!-- @label -->
+ <button id="btn_labelattr"
+ label="labeled element"/>
+
+ <!-- nsIDOMXULSelectControlItemElement -->
+ <richlistbox>
+ <richlistitem id="li_nsIDOMXULSelectControlItemElement">
+ <label value="select control item"/>
+ </richlistitem>
+ </richlistbox>
+
+ <!-- not nsIDOMXULSelectControlElement -->
+ <box id="box_not_nsIDOMXULSelectControlElement" role="group" label="box"/>
+
+ <!-- label element -->
+ <hbox>
+ <box>
+ <label control="btn_label_1">label1</label>
+ </box>
+ <label control="btn_label_2">label2</label>
+ <box>
+ <button id="btn_label_1"/>
+ <box>
+ <box>
+ <box>
+ <button id="btn_label_2"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ <label control="btn_label_3">label3</label>
+ <button id="btn_label_3"/>
+
+ <label control="btn_label_4">label4</label>
+ <label control="btn_label_4">label5</label>
+ <button id="btn_label_4"/>
+
+ <label control="input_label">input label</label>
+ <html:input id="input_label"/>
+ </hbox>
+
+ <!-- tooltiptext -->
+ <box id="box_tooltiptext"
+ role="group"
+ tooltiptext="tooltiptext label"/>
+
+ <!-- the name from @title of toolbaritem -->
+ <!-- and the name from label of a toolbarbutton -->
+ <toolbar>
+ <toolbaritem title="ooospspss">
+ <box id="toolbaritem_child"
+ role="group"
+ flex="1">
+ <hbox role="button" id="toolbaritem_hboxbutton">
+ <description value="button"/>
+ </hbox>
+ </box>
+ </toolbaritem>
+ <toolbarbutton id="toolbarbuttonwithlabel">
+ <label flex="1">I am the button</label>
+ </toolbarbutton>
+ </toolbar>
+
+ <!-- name from children -->
+ <box id="box_children" role="button">14</box>
+ <button id="button_text">Text</button>
+
+ <!-- name from children, hidden children -->
+ <vbox role="listbox" tabindex="0">
+ <hbox id="lb_opt1_children_hidden" role="option" tabindex="0">
+ <description>i am visible</description>
+ <description style="display:none">i am hidden</description>
+ </hbox>
+
+ <!-- Name from caption sub tree -->
+ <groupbox id="groupbox">
+ <label>Some caption</label>
+ <checkbox label="some checkbox label" />
+ </groupbox>
+ </vbox>
+
+ <!-- bug 441991; create name from other menuitem label listitem's own label -->
+ <hbox>
+ <richlistbox>
+ <richlistitem id="li_labelledby"
+ aria-labelledby="menuitem-DISPLAY li_labelledby">
+ <label value="The moment the event starts"/>
+ </richlistitem>
+ </richlistbox>
+ <menulist>
+ <menupopup>
+ <menuitem id="menuitem-DISPLAY"
+ value="DISPLAY"
+ label="Show an Alert"/>
+ <menuitem id="menuitem-EMAIL"
+ value="EMAIL"
+ label="Send an E-mail"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ </vbox> <!-- close tests area -->
+ </hbox> <!-- close main area -->
+</window>
diff --git a/accessible/tests/mochitest/name/test_link.html b/accessible/tests/mochitest/name/test_link.html
new file mode 100644
index 0000000000..6a289dd44f
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_link.html
@@ -0,0 +1,87 @@
+<html>
+
+<head>
+ <title>nsIAccessible::name calculation for HTML links (html:a)</title>
+
+ <link rel="stylesheet"
+ type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // aria-label
+ testName("aria_label", "anchor label");
+
+ // aria-labelledby
+ testName("aria_labelledby", "text");
+
+ // name from content
+ testName("namefromcontent", "1");
+
+ // name from content
+ testName("namefromimg", "img title");
+
+ // no name from content
+ testName("nonamefromcontent", null);
+
+ // title
+ testName("title", "title");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=459782"
+ title="nsIAccessible::name calculation for HTML links (html:a)">
+ Mozilla Bug 459782
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- aria-label -->
+ <a id="aria_label" href="mozilla.org"
+ aria-label="anchor label">1</a>
+ <br/>
+
+ <!-- aria-labelledby, preferred to html:label -->
+ <span id="text">text</span>
+ <label for="aria_labelledby">label</label>
+ <a id="aria_labelledby" href="mozilla.org"
+ aria-labelledby="text">1</a>
+ <br/>
+
+ <!-- name from content, preferred to @title -->
+ <a id="namefromcontent" href="mozilla.org"
+ title="title">1</a>
+ <br/>
+
+ <!-- name from content, preferred to @title -->
+ <a id="namefromimg" href="mozilla.org"
+ title="title"><img alt="img title" /></a>
+
+ <!-- no name from content, ARIA role overrides this rule -->
+ <a id="nonamefromcontent" href="mozilla.org" role="img">1</a>
+ <br/>
+
+ <!-- no content, name from @title -->
+ <a id="title" href="mozilla.org"
+ title="title"></a>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_list.html b/accessible/tests/mochitest/name/test_list.html
new file mode 100644
index 0000000000..95f0c06d2a
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_list.html
@@ -0,0 +1,103 @@
+<html>
+
+<head>
+ <title>nsIAccessible::name calculation for HTML li</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Alter list item numbering and change list style type.
+ */
+ function bulletUpdate() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode("list")),
+ ];
+
+ this.invoke = function bulletUpdate_invoke() {
+ testName("li_end", "1. list end");
+
+ var li = document.createElement("li");
+ li.setAttribute("id", "li_start");
+ li.textContent = "list start";
+ getNode("list").insertBefore(li, getNode("li_end"));
+ };
+
+ this.finalCheck = function bulletUpdate_finalCheck() {
+ testName("li_start", "1. list start");
+ testName("li_end", "2. list end");
+ };
+
+ this.getID = function bulletUpdate_getID() {
+ return "insertBefore new list item";
+ };
+ }
+ function bulletUpdate2() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode("li_end")),
+ ];
+
+ this.invoke = function bulletUpdate2_invoke() {
+ // change list style type
+ var list = getNode("list");
+ list.setAttribute("style", "list-style-type: disc;");
+
+ // Flush both the style change and the resulting layout change.
+ // Flushing style on its own is not sufficient, because that can
+ // leave frames marked with NS_FRAME_IS_DIRTY, which will cause
+ // nsTextFrame::GetRenderedText to report the text of a text
+ // frame is empty.
+ list.offsetWidth; // flush layout (which also flushes style)
+ };
+
+ this.finalCheck = function bulletUpdate2_finalCheck() {
+ testName("li_start", kDiscBulletText + "list start");
+ testName("li_end", kDiscBulletText + "list end");
+ };
+
+ this.getID = function bulletUpdate2_getID() {
+ return "Update list item style";
+ };
+ }
+
+ var gQueue = null;
+ function doTest() {
+ gQueue = new eventQueue();
+ gQueue.push(new bulletUpdate());
+ gQueue.push(new bulletUpdate2());
+ gQueue.invoke(); // SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=634200"
+ title="crash [@ nsIFrame::StyleVisibility() ]">
+ Mozilla Bug 634200
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ol id="list">
+ <li id="li_end">list end</li>
+ </ol>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_markup.html b/accessible/tests/mochitest/name/test_markup.html
new file mode 100644
index 0000000000..735027f44f
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_markup.html
@@ -0,0 +1,58 @@
+<html>
+
+<head>
+ <title>nsIAccessible::name calculation for elements</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript"
+ src="markup.js"></script>
+
+ <script type="application/javascript">
+ // gA11yEventDumpID = "eventdump";
+ // gDumpToConsole = true;
+ // gA11yEventDumpToConsole = true;
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(testNames);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=459635"
+ title="nsIAccessible::name calculation for elements">
+ Bug 459635
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=666212"
+ title="summary attribute content mapped to accessible name in MSAA">
+ Bug 666212
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=786163"
+ title=" Sort out name calculation for HTML input buttons">
+ Bug 786163
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_svg.html b/accessible/tests/mochitest/name/test_svg.html
new file mode 100644
index 0000000000..535fcdbf20
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_svg.html
@@ -0,0 +1,53 @@
+<html>
+
+<head>
+ <title>Accessible name and description for SVG elements</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ testName("svg1", "A name");
+ testDescr("svg1", "A description");
+ testName("svg2", "A tooltip");
+ testDescr("svg2", "");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=459357"
+ title="Support accessible name computation for SVG">
+ Mozilla Bug 459357
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg1">
+ <title>A name</title>
+ <desc>A description</title>
+ </svg>
+
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg2">
+ <desc>A tooltip</desc>
+ </svg>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_tree.xhtml b/accessible/tests/mochitest/name/test_tree.xhtml
new file mode 100644
index 0000000000..3564481d00
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_tree.xhtml
@@ -0,0 +1,207 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Name Calculating Test.">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function treeTester(aID)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.invoke = function treeTester_invoke()
+ {
+ this.DOMNode.view = new nsTreeTreeView();
+ }
+
+ this.check = function treeTester_check(aEvent)
+ {
+ var tree = {
+ role: ROLE_OUTLINE,
+ children: [
+ {
+ role: ROLE_LIST
+ },
+ {
+ role: ROLE_OUTLINEITEM,
+ children: [],
+ name: "row1col"
+ },
+ {
+ role: ROLE_OUTLINEITEM,
+ children: [],
+ name: "row2_col"
+ },
+ {
+ role: ROLE_OUTLINEITEM,
+ children: [],
+ name: "row2.1_col"
+ },
+ {
+ role: ROLE_OUTLINEITEM,
+ children: [],
+ name: "row2.2_col"
+ },
+ {
+ role: ROLE_OUTLINEITEM,
+ children: [],
+ name: "row3_col"
+ },
+ {
+ role: ROLE_OUTLINEITEM,
+ children: [],
+ name: "row4col"
+ }
+ ]
+ };
+ testAccessibleTree(this.DOMNode, tree);
+ }
+
+ this.getID = function treeTester_getID()
+ {
+ return "Tree name testing for " + aID;
+ }
+ }
+
+ function tableTester(aID, aIsTable, aCol1ID, aCol2ID)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.invoke = function tableTester_invoke()
+ {
+ this.DOMNode.view = new nsTableTreeView(2);
+ }
+
+ this.check = function tableTester_check(aEvent)
+ {
+ var tree = {
+ role: aIsTable ? ROLE_TABLE : ROLE_TREE_TABLE,
+ children: [
+ {
+ role: ROLE_LIST
+ },
+ {
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_GRID_CELL,
+ children: [],
+ name: "row0_" + aCol1ID
+ },
+ {
+ role: ROLE_GRID_CELL,
+ children: [],
+ name: "row0_" + aCol2ID
+ }
+ ],
+ name: "row0_" + aCol1ID + " row0_" + aCol2ID
+ },
+ {
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_GRID_CELL,
+ children: [],
+ name: "row1_" + aCol1ID
+ },
+ {
+ role: ROLE_GRID_CELL,
+ children: [],
+ name: "row1_" + aCol2ID
+ }
+ ],
+ name: "row1_" + aCol1ID + " row1_" + aCol2ID
+ }
+ ]
+ };
+ testAccessibleTree(this.DOMNode, tree);
+ }
+
+ this.getID = function tableTester_getID()
+ {
+ return "Tree name testing for " + aID;
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue(EVENT_REORDER);
+
+ gQueue.push(new treeTester("tree"));
+ gQueue.push(new tableTester("table", true, "t_col1", "t_col2"));
+ gQueue.push(new tableTester("treetable", false, "tt_col1", "tt_col2"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=546812"
+ title="Treegrid row accessible shouldn't inherit name from tree accessible">
+ Mozilla Bug 546812
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=664376"
+ title="Table rows of XUL trees no longer containing cell content as accessible name">
+ Mozilla Bug 664376
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="table" flex="1">
+ <treecols>
+ <treecol id="t_col1" flex="1" label="column"/>
+ <treecol id="t_col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="treetable" flex="1">
+ <treecols>
+ <treecol id="tt_col1" flex="1" label="column" primary="true"/>
+ <treecol id="tt_col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ </vbox> <!-- close tests area -->
+ </hbox> <!-- close main area -->
+</window>
diff --git a/accessible/tests/mochitest/pivot.js b/accessible/tests/mochitest/pivot.js
new file mode 100644
index 0000000000..9e43134add
--- /dev/null
+++ b/accessible/tests/mochitest/pivot.js
@@ -0,0 +1,664 @@
+/* import-globals-from common.js */
+/* import-globals-from events.js */
+/* import-globals-from role.js */
+/* import-globals-from states.js */
+/* import-globals-from text.js */
+
+// //////////////////////////////////////////////////////////////////////////////
+// Constants
+
+const PREFILTER_INVISIBLE = nsIAccessibleTraversalRule.PREFILTER_INVISIBLE;
+const PREFILTER_TRANSPARENT = nsIAccessibleTraversalRule.PREFILTER_TRANSPARENT;
+const FILTER_MATCH = nsIAccessibleTraversalRule.FILTER_MATCH;
+const FILTER_IGNORE = nsIAccessibleTraversalRule.FILTER_IGNORE;
+const FILTER_IGNORE_SUBTREE = nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE;
+const NO_BOUNDARY = nsIAccessiblePivot.NO_BOUNDARY;
+const CHAR_BOUNDARY = nsIAccessiblePivot.CHAR_BOUNDARY;
+const WORD_BOUNDARY = nsIAccessiblePivot.WORD_BOUNDARY;
+const LINE_BOUNDARY = nsIAccessiblePivot.LINE_BOUNDARY;
+
+const NS_ERROR_NOT_IN_TREE = 0x80780026;
+const NS_ERROR_INVALID_ARG = 0x80070057;
+
+// //////////////////////////////////////////////////////////////////////////////
+// Traversal rules
+
+/**
+ * Rule object to traverse all focusable nodes and text nodes.
+ */
+var HeadersTraversalRule = {
+ getMatchRoles() {
+ return [ROLE_HEADING];
+ },
+
+ preFilter: PREFILTER_INVISIBLE,
+
+ match(aAccessible) {
+ return FILTER_MATCH;
+ },
+
+ QueryInterface: ChromeUtils.generateQI([nsIAccessibleTraversalRule]),
+};
+
+/**
+ * Traversal rule for all focusable nodes or leafs.
+ */
+var ObjectTraversalRule = {
+ getMatchRoles() {
+ return [];
+ },
+
+ preFilter: PREFILTER_INVISIBLE | PREFILTER_TRANSPARENT,
+
+ match(aAccessible) {
+ var rv = FILTER_IGNORE;
+ var role = aAccessible.role;
+ if (
+ hasState(aAccessible, STATE_FOCUSABLE) &&
+ role != ROLE_DOCUMENT &&
+ role != ROLE_INTERNAL_FRAME
+ ) {
+ rv = FILTER_IGNORE_SUBTREE | FILTER_MATCH;
+ } else if (
+ aAccessible.childCount == 0 &&
+ role != ROLE_LISTITEM_MARKER &&
+ aAccessible.name.trim()
+ ) {
+ rv = FILTER_MATCH;
+ }
+
+ return rv;
+ },
+
+ QueryInterface: ChromeUtils.generateQI([nsIAccessibleTraversalRule]),
+};
+
+// //////////////////////////////////////////////////////////////////////////////
+// Virtual state invokers and checkers
+
+/**
+ * A checker for virtual cursor changed events.
+ */
+function VCChangedChecker(
+ aDocAcc,
+ aIdOrNameOrAcc,
+ aTextOffsets,
+ aPivotMoveMethod,
+ aIsFromUserInput,
+ aBoundaryType = NO_BOUNDARY
+) {
+ this.__proto__ = new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc);
+
+ this.match = function VCChangedChecker_match(aEvent) {
+ var event = null;
+ try {
+ event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent);
+ } catch (e) {
+ return false;
+ }
+
+ var expectedReason =
+ VCChangedChecker.methodReasonMap[aPivotMoveMethod] ||
+ nsIAccessiblePivot.REASON_NONE;
+
+ return (
+ event.reason == expectedReason && event.boundaryType == aBoundaryType
+ );
+ };
+
+ this.check = function VCChangedChecker_check(aEvent) {
+ SimpleTest.info("VCChangedChecker_check");
+
+ var event = null;
+ try {
+ event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent);
+ } catch (e) {
+ SimpleTest.ok(false, "Does not support correct interface: " + e);
+ }
+
+ var position = aDocAcc.virtualCursor.position;
+ var idMatches = position && position.DOMNode.id == aIdOrNameOrAcc;
+ var nameMatches = position && position.name == aIdOrNameOrAcc;
+ var accMatches = position == aIdOrNameOrAcc;
+
+ SimpleTest.ok(
+ idMatches || nameMatches || accMatches,
+ "id or name matches - expecting " +
+ prettyName(aIdOrNameOrAcc) +
+ ", got '" +
+ prettyName(position)
+ );
+
+ SimpleTest.is(
+ aEvent.isFromUserInput,
+ aIsFromUserInput,
+ "Expected user input is " + aIsFromUserInput + "\n"
+ );
+
+ SimpleTest.is(
+ event.newAccessible,
+ position,
+ "new position in event is incorrect"
+ );
+
+ if (aTextOffsets) {
+ SimpleTest.is(
+ aDocAcc.virtualCursor.startOffset,
+ aTextOffsets[0],
+ "wrong start offset"
+ );
+ SimpleTest.is(
+ aDocAcc.virtualCursor.endOffset,
+ aTextOffsets[1],
+ "wrong end offset"
+ );
+ SimpleTest.is(
+ event.newStartOffset,
+ aTextOffsets[0],
+ "wrong start offset in event"
+ );
+ SimpleTest.is(
+ event.newEndOffset,
+ aTextOffsets[1],
+ "wrong end offset in event"
+ );
+ }
+
+ var prevPosAndOffset = VCChangedChecker.getPreviousPosAndOffset(
+ aDocAcc.virtualCursor
+ );
+
+ if (prevPosAndOffset) {
+ SimpleTest.is(
+ event.oldAccessible,
+ prevPosAndOffset.position,
+ "previous position does not match"
+ );
+ SimpleTest.is(
+ event.oldStartOffset,
+ prevPosAndOffset.startOffset,
+ "previous start offset does not match"
+ );
+ SimpleTest.is(
+ event.oldEndOffset,
+ prevPosAndOffset.endOffset,
+ "previous end offset does not match"
+ );
+ }
+ };
+}
+
+VCChangedChecker.prevPosAndOffset = {};
+
+VCChangedChecker.storePreviousPosAndOffset = function storePreviousPosAndOffset(
+ aPivot
+) {
+ VCChangedChecker.prevPosAndOffset[aPivot] = {
+ position: aPivot.position,
+ startOffset: aPivot.startOffset,
+ endOffset: aPivot.endOffset,
+ };
+};
+
+VCChangedChecker.getPreviousPosAndOffset = function getPreviousPosAndOffset(
+ aPivot
+) {
+ return VCChangedChecker.prevPosAndOffset[aPivot];
+};
+
+VCChangedChecker.methodReasonMap = {
+ moveNext: nsIAccessiblePivot.REASON_NEXT,
+ movePrevious: nsIAccessiblePivot.REASON_PREV,
+ moveFirst: nsIAccessiblePivot.REASON_FIRST,
+ moveLast: nsIAccessiblePivot.REASON_LAST,
+ setTextRange: nsIAccessiblePivot.REASON_NONE,
+ moveNextByText: nsIAccessiblePivot.REASON_NEXT,
+ movePreviousByText: nsIAccessiblePivot.REASON_PREV,
+ moveToPoint: nsIAccessiblePivot.REASON_POINT,
+};
+
+/**
+ * Set a text range in the pivot and wait for virtual cursor change event.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aTextAccessible [in] accessible to set to virtual cursor's position
+ * @param aTextOffsets [in] start and end offsets of text range to set in
+ * virtual cursor.
+ */
+function setVCRangeInvoker(aDocAcc, aTextAccessible, aTextOffsets) {
+ this.invoke = function virtualCursorChangedInvoker_invoke() {
+ VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor);
+ SimpleTest.info(prettyName(aTextAccessible) + " " + aTextOffsets);
+ aDocAcc.virtualCursor.setTextRange(
+ aTextAccessible,
+ aTextOffsets[0],
+ aTextOffsets[1]
+ );
+ };
+
+ this.getID = function setVCRangeInvoker_getID() {
+ return (
+ "Set offset in " +
+ prettyName(aTextAccessible) +
+ " to (" +
+ aTextOffsets[0] +
+ ", " +
+ aTextOffsets[1] +
+ ")"
+ );
+ };
+
+ this.eventSeq = [
+ new VCChangedChecker(
+ aDocAcc,
+ aTextAccessible,
+ aTextOffsets,
+ "setTextRange",
+ true
+ ),
+ ];
+}
+
+/**
+ * Move the pivot and wait for virtual cursor change event.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.)
+ * @param aRule [in] traversal rule object
+ * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect
+ * virtual cursor to land on after performing move method.
+ * false if no move is expected.
+ * @param aIsFromUserInput [in] set user input flag when invoking method, and
+ * expect it in the event.
+ */
+function setVCPosInvoker(
+ aDocAcc,
+ aPivotMoveMethod,
+ aRule,
+ aIdOrNameOrAcc,
+ aIsFromUserInput
+) {
+ // eslint-disable-next-line mozilla/no-compare-against-boolean-literals
+ var expectMove = aIdOrNameOrAcc != false;
+ this.invoke = function virtualCursorChangedInvoker_invoke() {
+ VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor);
+ if (aPivotMoveMethod && aRule) {
+ var moved = false;
+ switch (aPivotMoveMethod) {
+ case "moveFirst":
+ case "moveLast":
+ moved = aDocAcc.virtualCursor[aPivotMoveMethod](
+ aRule,
+ aIsFromUserInput === undefined ? true : aIsFromUserInput
+ );
+ break;
+ case "moveNext":
+ case "movePrevious":
+ moved = aDocAcc.virtualCursor[aPivotMoveMethod](
+ aRule,
+ aDocAcc.virtualCursor.position,
+ false,
+ aIsFromUserInput === undefined ? true : aIsFromUserInput
+ );
+ break;
+ }
+ SimpleTest.is(
+ !!moved,
+ !!expectMove,
+ "moved pivot with " + aPivotMoveMethod + " to " + aIdOrNameOrAcc
+ );
+ } else {
+ aDocAcc.virtualCursor.position = getAccessible(aIdOrNameOrAcc);
+ }
+ };
+
+ this.getID = function setVCPosInvoker_getID() {
+ return "Do " + (expectMove ? "" : "no-op ") + aPivotMoveMethod;
+ };
+
+ if (expectMove) {
+ this.eventSeq = [
+ new VCChangedChecker(
+ aDocAcc,
+ aIdOrNameOrAcc,
+ null,
+ aPivotMoveMethod,
+ aIsFromUserInput === undefined ? !!aPivotMoveMethod : aIsFromUserInput
+ ),
+ ];
+ } else {
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc),
+ ];
+ }
+}
+
+/**
+ * Move the pivot by text and wait for virtual cursor change event.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.)
+ * @param aBoundary [in] boundary constant
+ * @param aTextOffsets [in] start and end offsets of text range to set in
+ * virtual cursor.
+ * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect
+ * virtual cursor to land on after performing move method.
+ * false if no move is expected.
+ * @param aIsFromUserInput [in] set user input flag when invoking method, and
+ * expect it in the event.
+ */
+function setVCTextInvoker(
+ aDocAcc,
+ aPivotMoveMethod,
+ aBoundary,
+ aTextOffsets,
+ aIdOrNameOrAcc,
+ aIsFromUserInput
+) {
+ // eslint-disable-next-line mozilla/no-compare-against-boolean-literals
+ var expectMove = aIdOrNameOrAcc != false;
+ this.invoke = function virtualCursorChangedInvoker_invoke() {
+ VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor);
+ SimpleTest.info(aDocAcc.virtualCursor.position);
+ var moved = aDocAcc.virtualCursor[aPivotMoveMethod](
+ aBoundary,
+ aIsFromUserInput === undefined
+ );
+ SimpleTest.is(
+ !!moved,
+ !!expectMove,
+ "moved pivot by text with " + aPivotMoveMethod + " to " + aIdOrNameOrAcc
+ );
+ };
+
+ this.getID = function setVCPosInvoker_getID() {
+ return (
+ "Do " +
+ (expectMove ? "" : "no-op ") +
+ aPivotMoveMethod +
+ " in " +
+ prettyName(aIdOrNameOrAcc) +
+ ", " +
+ boundaryToString(aBoundary) +
+ ", [" +
+ aTextOffsets +
+ "]"
+ );
+ };
+
+ if (expectMove) {
+ this.eventSeq = [
+ new VCChangedChecker(
+ aDocAcc,
+ aIdOrNameOrAcc,
+ aTextOffsets,
+ aPivotMoveMethod,
+ aIsFromUserInput === undefined ? true : aIsFromUserInput,
+ aBoundary
+ ),
+ ];
+ } else {
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc),
+ ];
+ }
+}
+
+/**
+ * Move the pivot to the position under the point.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aX [in] screen x coordinate
+ * @param aY [in] screen y coordinate
+ * @param aIgnoreNoMatch [in] don't unset position if no object was found at
+ * point.
+ * @param aRule [in] traversal rule object
+ * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect
+ * virtual cursor to land on after performing move method.
+ * false if no move is expected.
+ */
+function moveVCCoordInvoker(
+ aDocAcc,
+ aX,
+ aY,
+ aIgnoreNoMatch,
+ aRule,
+ aIdOrNameOrAcc
+) {
+ // eslint-disable-next-line mozilla/no-compare-against-boolean-literals
+ var expectMove = aIdOrNameOrAcc != false;
+ this.invoke = function virtualCursorChangedInvoker_invoke() {
+ VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor);
+ var moved = aDocAcc.virtualCursor.moveToPoint(
+ aRule,
+ aX,
+ aY,
+ aIgnoreNoMatch
+ );
+ SimpleTest.ok(
+ (expectMove && moved) || (!expectMove && !moved),
+ "moved pivot"
+ );
+ };
+
+ this.getID = function setVCPosInvoker_getID() {
+ return (
+ "Do " + (expectMove ? "" : "no-op ") + "moveToPoint " + aIdOrNameOrAcc
+ );
+ };
+
+ if (expectMove) {
+ this.eventSeq = [
+ new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, "moveToPoint", true),
+ ];
+ } else {
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc),
+ ];
+ }
+}
+
+/**
+ * Change the pivot modalRoot
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aModalRootAcc [in] accessible of the modal root, or null
+ * @param aExpectedResult [in] error result expected. 0 if expecting success
+ */
+function setModalRootInvoker(aDocAcc, aModalRootAcc, aExpectedResult) {
+ this.invoke = function setModalRootInvoker_invoke() {
+ var errorResult = 0;
+ try {
+ aDocAcc.virtualCursor.modalRoot = aModalRootAcc;
+ } catch (x) {
+ SimpleTest.ok(
+ x.result,
+ "Unexpected exception when changing modal root: " + x
+ );
+ errorResult = x.result;
+ }
+
+ SimpleTest.is(
+ errorResult,
+ aExpectedResult,
+ "Did not get expected result when changing modalRoot"
+ );
+ };
+
+ this.getID = function setModalRootInvoker_getID() {
+ return "Set modalRoot to " + prettyName(aModalRootAcc);
+ };
+
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc),
+ ];
+}
+
+/**
+ * Add invokers to a queue to test a rule and an expected sequence of element ids
+ * or accessible names for that rule in the given document.
+ *
+ * @param aQueue [in] event queue in which to push invoker sequence.
+ * @param aDocAcc [in] the managing document of the virtual cursor we are
+ * testing
+ * @param aRule [in] the traversal rule to use in the invokers
+ * @param aModalRoot [in] a modal root to use in this traversal sequence
+ * @param aSequence [in] a sequence of accessible names or element ids to expect
+ * with the given rule in the given document
+ */
+function queueTraversalSequence(aQueue, aDocAcc, aRule, aModalRoot, aSequence) {
+ aDocAcc.virtualCursor.position = null;
+
+ // Add modal root (if any)
+ aQueue.push(new setModalRootInvoker(aDocAcc, aModalRoot, 0));
+
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0]));
+
+ for (let i = 1; i < aSequence.length; i++) {
+ let invoker = new setVCPosInvoker(aDocAcc, "moveNext", aRule, aSequence[i]);
+ aQueue.push(invoker);
+ }
+
+ // No further more matches for given rule, expect no virtual cursor changes.
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false));
+
+ for (let i = aSequence.length - 2; i >= 0; i--) {
+ let invoker = new setVCPosInvoker(
+ aDocAcc,
+ "movePrevious",
+ aRule,
+ aSequence[i]
+ );
+ aQueue.push(invoker);
+ }
+
+ // No previous more matches for given rule, expect no virtual cursor changes.
+ aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false));
+
+ aQueue.push(
+ new setVCPosInvoker(
+ aDocAcc,
+ "moveLast",
+ aRule,
+ aSequence[aSequence.length - 1]
+ )
+ );
+
+ // No further more matches for given rule, expect no virtual cursor changes.
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false));
+
+ // set isFromUserInput to false, just to test..
+ aQueue.push(
+ new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0], false)
+ );
+
+ // No previous more matches for given rule, expect no virtual cursor changes.
+ aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false));
+
+ // Remove modal root (if any).
+ aQueue.push(new setModalRootInvoker(aDocAcc, null, 0));
+}
+
+/**
+ * A checker for removing an accessible while the virtual cursor is on it.
+ */
+function removeVCPositionChecker(aDocAcc, aHiddenParentAcc) {
+ this.__proto__ = new invokerChecker(EVENT_REORDER, aHiddenParentAcc);
+
+ this.check = function removeVCPositionChecker_check(aEvent) {
+ var errorResult = 0;
+ try {
+ aDocAcc.virtualCursor.moveNext(ObjectTraversalRule);
+ } catch (x) {
+ errorResult = x.result;
+ }
+ SimpleTest.is(
+ errorResult,
+ NS_ERROR_NOT_IN_TREE,
+ "Expecting NOT_IN_TREE error when moving pivot from invalid position."
+ );
+ };
+}
+
+/**
+ * Put the virtual cursor's position on an object, and then remove it.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aPosNode [in] DOM node to hide after virtual cursor's position is
+ * set to it.
+ */
+function removeVCPositionInvoker(aDocAcc, aPosNode) {
+ this.accessible = getAccessible(aPosNode);
+ this.invoke = function removeVCPositionInvoker_invoke() {
+ aDocAcc.virtualCursor.position = this.accessible;
+ aPosNode.remove();
+ };
+
+ this.getID = function removeVCPositionInvoker_getID() {
+ return "Bring virtual cursor to accessible, and remove its DOM node.";
+ };
+
+ this.eventSeq = [
+ new removeVCPositionChecker(aDocAcc, this.accessible.parent),
+ ];
+}
+
+/**
+ * A checker for removing the pivot root and then calling moveFirst, and
+ * checking that an exception is thrown.
+ */
+function removeVCRootChecker(aPivot) {
+ this.__proto__ = new invokerChecker(EVENT_REORDER, aPivot.root.parent);
+
+ this.check = function removeVCRootChecker_check(aEvent) {
+ var errorResult = 0;
+ try {
+ aPivot.moveLast(ObjectTraversalRule);
+ } catch (x) {
+ errorResult = x.result;
+ }
+ SimpleTest.is(
+ errorResult,
+ NS_ERROR_NOT_IN_TREE,
+ "Expecting NOT_IN_TREE error when moving pivot from invalid position."
+ );
+ };
+}
+
+/**
+ * Create a pivot, remove its root, and perform an operation where the root is
+ * needed.
+ *
+ * @param aRootNode [in] DOM node of which accessible will be the root of the
+ * pivot. Should have more than one child.
+ */
+function removeVCRootInvoker(aRootNode) {
+ this.pivot = gAccService.createAccessiblePivot(getAccessible(aRootNode));
+ this.invoke = function removeVCRootInvoker_invoke() {
+ this.pivot.position = this.pivot.root.firstChild;
+ aRootNode.remove();
+ };
+
+ this.getID = function removeVCRootInvoker_getID() {
+ return "Remove root of pivot from tree.";
+ };
+
+ this.eventSeq = [new removeVCRootChecker(this.pivot)];
+}
+
+/**
+ * A debug utility for writing proper sequences for queueTraversalSequence.
+ */
+function dumpTraversalSequence(aPivot, aRule) {
+ var sequence = [];
+ if (aPivot.moveFirst(aRule)) {
+ do {
+ sequence.push("'" + prettyName(aPivot.position) + "'");
+ } while (aPivot.moveNext(aRule));
+ }
+ SimpleTest.info("\n[" + sequence.join(", ") + "]\n");
+}
diff --git a/accessible/tests/mochitest/pivot/a11y.ini b/accessible/tests/mochitest/pivot/a11y.ini
new file mode 100644
index 0000000000..8add460947
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/a11y.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ doc_virtualcursor.html
+ doc_virtualcursor_text.html
+ !/accessible/tests/mochitest/*.js
+
+[test_virtualcursor.html]
+[test_virtualcursor_text.html]
diff --git a/accessible/tests/mochitest/pivot/doc_virtualcursor.html b/accessible/tests/mochitest/pivot/doc_virtualcursor.html
new file mode 100644
index 0000000000..a456f2dfcd
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/doc_virtualcursor.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Pivot test document</title>
+ <meta charset="utf-8" />
+</head>
+<body>
+ <h1 id="heading-1-1">Main Title</h1>
+ <h2 id="heading-2-1" aria-hidden="true">First Section Title</h2>
+ <p id="paragraph-1">
+ Lorem ipsum <strong>dolor</strong> sit amet. Integer vitae urna
+ leo, id <a href="#">semper</a> nulla.
+ </p>
+ <h2 id="heading-2-2" aria-hidden="undefined">Second Section Title</h2>
+ <p id="paragraph-2" aria-hidden="">
+ Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.</p>
+ <p id="paragraph-3" aria-hidden="true">
+ <a href="#" id="hidden-link">Maybe</a> it was the other <i>George Michael</i>.
+ You know, the <a href="#">singer-songwriter</a>.
+ </p>
+ <p style="opacity: 0;" id="paragraph-4">
+ This is completely transparent
+ </p>
+ <iframe
+ src="data:text/html,<html><body>An <i>embedded</i> document.</body></html>">
+ </iframe>
+ <div id="hide-me">Hide me</div>
+ <p id="links" aria-hidden="false">
+ <a href="http://mozilla.org" title="Link 1 title">Link 1</a>
+ <a href="http://mozilla.org" title="Link 2 title">Link 2</a>
+ <a href="http://mozilla.org" title="Link 3 title">Link 3</a>
+ </p>
+ <ul>
+ <li>Hello<span> </span></li>
+ <li>World</li>
+ </ul>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html b/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html
new file mode 100644
index 0000000000..a0565058d9
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Pivot test document</title>
+ <meta charset="utf-8" />
+</head>
+<body>
+ <div id="start-block">This is the very beginning.</div>
+ <p id="paragraph-1">
+ This <b>is</b> <a id="p1-link-1" href="#">the</a> test of text.
+ </p>
+ <div id="section-1">A <a id="s1-link-1" href="#">multiword link</a> is here. <a id="s1-link-2" href="#">We</a> will traverse</div>
+ <div id="section-2">into, out, and between the subtrees.</div>
+ <p id="paragraph-2">Singularity.</p>
+ <table>
+ <tr>
+ <td id="cell-1">Magical</td>
+ <td id="cell-2">unicorns</td>
+ </tr>
+ <tr>
+ <td id="cell-3">and wizards</td>
+ <td id="cell-4">really exist.</td>
+ </tr>
+ </table>
+ <div id="section-3">Endless fun!</div>
+ <p id="paragraph-3">Objects<a id="p3-link-1" href="#">adjacent</a>to <a id="p3-link-2" href="#">each</a><a id="p3-link-3" href="#">other</a> should be separate.</p>
+ <p id="paragraph-4">Hello <strong>real</strong><a href="#"> world</p>
+ <a href="#" id="image-desc-link">
+ <img src="../moz.png" alt="">Hello
+ </a>
+ <p id="image-end-line">
+ a<img src="../moz.png" alt="b"><br>
+ c
+ </p>
+ <div id="end-block">End!</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/pivot/test_virtualcursor.html b/accessible/tests/mochitest/pivot/test_virtualcursor.html
new file mode 100644
index 0000000000..9f3225fcf6
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/test_virtualcursor.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Tests pivot functionality in virtual cursors</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script src="chrome://mochikit/content/chrome-harness.js">
+ </script>
+
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../browser.js"></script>
+ <script type="application/javascript" src="../events.js"></script>
+ <script type="application/javascript" src="../role.js"></script>
+ <script type="application/javascript" src="../states.js"></script>
+ <script type="application/javascript" src="../pivot.js"></script>
+ <script type="application/javascript" src="../layout.js"></script>
+
+ <script type="application/javascript">
+ var gBrowserWnd = null;
+ var gQueue = null;
+
+ function doTest() {
+ var rootAcc = getAccessible(browserDocument(), [nsIAccessibleDocument]);
+ ok(rootAcc.virtualCursor,
+ "root document does not have virtualCursor");
+
+ var doc = currentTabDocument();
+ var docAcc = getAccessible(doc, [nsIAccessibleDocument]);
+
+ // Test that embedded documents have their own virtual cursor.
+ is(docAcc.childDocumentCount, 1, "Expecting one child document");
+ ok(docAcc.getChildDocumentAt(0).virtualCursor,
+ "child document does not have virtualCursor");
+
+ gQueue = new eventQueue();
+
+ gQueue.onFinish = function onFinish() {
+ closeBrowserWindow();
+ };
+
+ queueTraversalSequence(gQueue, docAcc, HeadersTraversalRule, null,
+ ["heading-1-1", "heading-2-2"]);
+
+ queueTraversalSequence(
+ gQueue, docAcc, ObjectTraversalRule, null,
+ ["Main Title", "Lorem ipsum ",
+ "dolor", " sit amet. Integer vitae urna leo, id ",
+ "semper", " nulla. ", "Second Section Title",
+ "Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.",
+ "An ", "embedded", " document.", "Hide me", "Link 1", "Link 2",
+ "Link 3", "Hello", "World"]);
+
+ // Just a random smoke test to see if our setTextRange works.
+ gQueue.push(
+ new setVCRangeInvoker(
+ docAcc,
+ getAccessible(doc.getElementById("paragraph-2"), nsIAccessibleText),
+ [2, 6]));
+
+ gQueue.push(new removeVCPositionInvoker(
+ docAcc, doc.getElementById("hide-me")));
+
+ gQueue.push(new removeVCRootInvoker(
+ doc.getElementById("links")));
+
+ var [x, y] = getBounds(getAccessible(doc.getElementById("heading-1-1")));
+ gQueue.push(new moveVCCoordInvoker(docAcc, x + 1, y + 1, true,
+ HeadersTraversalRule, "heading-1-1"));
+
+ // Already on the point, so we should not get a move event.
+ gQueue.push(new moveVCCoordInvoker(docAcc, x + 1, y + 1, true,
+ HeadersTraversalRule, false));
+
+ // Attempting a coordinate outside any header, should not move.
+ gQueue.push(new moveVCCoordInvoker(docAcc, x - 1, y - 1, true,
+ HeadersTraversalRule, false));
+
+ // Attempting a coordinate outside any header, should move to null
+ gQueue.push(new moveVCCoordInvoker(docAcc, x - 1, y - 1, false,
+ HeadersTraversalRule, null));
+
+ queueTraversalSequence(
+ gQueue, docAcc, ObjectTraversalRule,
+ getAccessible(doc.getElementById("paragraph-1")),
+ ["Lorem ipsum ", "dolor", " sit amet. Integer vitae urna leo, id ",
+ "semper", " nulla. "]);
+
+ gQueue.push(new setModalRootInvoker(docAcc, docAcc.parent,
+ NS_ERROR_INVALID_ARG));
+
+ gQueue.push(new setVCPosInvoker(docAcc, "moveNext", ObjectTraversalRule, "dolor"));
+
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ /* We open a new browser because we need to test with a top-level content
+ document. */
+ openBrowserWindow(
+ doTest,
+ getRootDirectory(window.location.href) + "doc_virtualcursor.html");
+ });
+ </script>
+</head>
+<body id="body">
+
+ <a target="_blank"
+ title="Introduce virtual cursor/soft focus functionality to a11y API"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=698823">Mozilla Bug 698823</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/pivot/test_virtualcursor_text.html b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html
new file mode 100644
index 0000000000..23d46f3fe6
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html
@@ -0,0 +1,271 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Tests pivot functionality in virtual cursors</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script src="chrome://mochikit/content/chrome-harness.js">
+ </script>
+
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../text.js"></script>
+ <script type="application/javascript" src="../browser.js"></script>
+ <script type="application/javascript" src="../events.js"></script>
+ <script type="application/javascript" src="../role.js"></script>
+ <script type="application/javascript" src="../states.js"></script>
+ <script type="application/javascript" src="../pivot.js"></script>
+ <script type="application/javascript" src="../layout.js"></script>
+
+ <script type="application/javascript">
+ var gBrowserWnd = null;
+ var gQueue = null;
+
+ function doTest() {
+ var doc = currentTabDocument();
+ var docAcc = getAccessible(doc, [nsIAccessibleDocument]);
+
+ gQueue = new eventQueue();
+
+ gQueue.onFinish = function onFinish() {
+ closeBrowserWindow();
+ };
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("paragraph-1"))));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 4],
+ getAccessible(doc.getElementById("paragraph-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [4, 5],
+ getAccessible(doc.getElementById("paragraph-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", CHAR_BOUNDARY, [3, 4],
+ getAccessible(doc.getElementById("paragraph-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [5, 7],
+ getAccessible(doc.getElementById("paragraph-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 3],
+ getAccessible(doc.getElementById("p1-link-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [10, 14],
+ getAccessible(doc.getElementById("paragraph-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 3],
+ getAccessible(doc.getElementById("p1-link-1"), nsIAccessibleText)));
+ // set user input to false, and see if it works
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [5, 7],
+ getAccessible(doc.getElementById("paragraph-1"), nsIAccessibleText)),
+ false);
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("section-1"))));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 1],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 9],
+ getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText)));
+ // set user input to false, and see if it works
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [10, 14],
+ getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText),
+ false));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [4, 6],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [7, 12],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 2],
+ getAccessible(doc.getElementById("s1-link-2"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [15, 19],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [20, 28],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 5],
+ getAccessible(doc.getElementById("section-2"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [6, 10],
+ getAccessible(doc.getElementById("section-2"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 5],
+ getAccessible(doc.getElementById("section-2"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [20, 28],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [15, 19],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 2],
+ getAccessible(doc.getElementById("s1-link-2"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [7, 12],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [4, 6],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [10, 14],
+ getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 9],
+ getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 1],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+
+ gQueue.push(new setVCRangeInvoker(docAcc, getAccessible(doc.getElementById("s1-link-1")), [0, 0]));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", CHAR_BOUNDARY, [1, 2],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", CHAR_BOUNDARY, [0, 1],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [1, 2],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [0, 1],
+ getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [1, 2],
+ getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [2, 9],
+ getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [10, 14],
+ getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [3, 4],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", CHAR_BOUNDARY, [13, 14],
+ getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText)));
+
+ gQueue.push(new setVCRangeInvoker(docAcc, getAccessible(doc.getElementById("section-2")), [0, 0]));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", CHAR_BOUNDARY, [27, 28],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [0, 1],
+ getAccessible(doc.getElementById("section-2"), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("paragraph-2"))));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 12],
+ getAccessible(doc.getElementById("paragraph-2"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 7],
+ getAccessible(doc.getElementById("cell-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 8],
+ getAccessible(doc.getElementById("cell-2"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 3],
+ getAccessible(doc.getElementById("cell-3"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [4, 11],
+ getAccessible(doc.getElementById("cell-3"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 6],
+ getAccessible(doc.getElementById("cell-4"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [7, 13],
+ getAccessible(doc.getElementById("cell-4"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 7],
+ getAccessible(doc.getElementById("section-3"), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("section-3"))));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 7],
+ getAccessible(doc.getElementById("section-3"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [7, 13],
+ getAccessible(doc.getElementById("cell-4"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 6],
+ getAccessible(doc.getElementById("cell-4"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [4, 11],
+ getAccessible(doc.getElementById("cell-3"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 3],
+ getAccessible(doc.getElementById("cell-3"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 8],
+ getAccessible(doc.getElementById("cell-2"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 7],
+ getAccessible(doc.getElementById("cell-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 12],
+ getAccessible(doc.getElementById("paragraph-2"), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("paragraph-3"))));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 7],
+ getAccessible(doc.getElementById("paragraph-3"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 8],
+ getAccessible(doc.getElementById("p3-link-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [8, 10],
+ getAccessible(doc.getElementById("paragraph-3"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 4],
+ getAccessible(doc.getElementById("p3-link-2"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 5],
+ getAccessible(doc.getElementById("p3-link-3"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [14, 20],
+ getAccessible(doc.getElementById("paragraph-3"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 5],
+ getAccessible(doc.getElementById("p3-link-3"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 4],
+ getAccessible(doc.getElementById("p3-link-2"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [8, 10],
+ getAccessible(doc.getElementById("paragraph-3"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 8],
+ getAccessible(doc.getElementById("p3-link-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 7],
+ getAccessible(doc.getElementById("paragraph-3"), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("s1-link-2"))));
+ // Start with the pivot in the middle of the paragraph
+ gQueue.push(new setVCPosInvoker(docAcc, "moveNext", ObjectTraversalRule, " will traverse"));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [15, 19],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 2],
+ getAccessible(doc.getElementById("s1-link-2"), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("end-block"))));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 4],
+ getAccessible(doc.getElementById("end-block"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, null, false));
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("start-block"))));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 4],
+ getAccessible(doc.getElementById("start-block"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, null, false));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, null, false));
+ gQueue.push(new setVCRangeInvoker(docAcc, getAccessible(doc.getElementById("start-block")), [0, 0]));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, null, false));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("paragraph-3")).firstChild));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 7],
+ getAccessible(doc.getElementById("paragraph-3"), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("s1-link-1")).nextSibling));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [4, 6],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("paragraph-4")).firstChild.nextSibling));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [6, 10],
+ getAccessible(doc.getElementById("paragraph-4"), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("section-1"))));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [20, 28],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("section-1")).lastChild));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [20, 28],
+ getAccessible(doc.getElementById("section-1"), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("image-desc-link"))));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [0, 1],
+ getAccessible(doc.getElementById("image-desc-link"), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [1, 2],
+ getAccessible(doc.getElementById("image-desc-link"), nsIAccessibleText)));
+
+ gQueue.push(new setVCRangeInvoker(docAcc, getAccessible(doc.getElementById("image-end-line")), [3, 3]));
+ gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", LINE_BOUNDARY, [0, 2],
+ getAccessible(doc.getElementById("image-end-line"), nsIAccessibleText)));
+
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ /* We open a new browser because we need to test with a top-level content
+ document. */
+ openBrowserWindow(
+ doTest,
+ getRootDirectory(window.location.href) + "doc_virtualcursor_text.html");
+ });
+ </script>
+</head>
+<body id="body">
+
+ <a target="_blank"
+ title="Support Movement By Granularity"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=886076">Mozilla Bug 886076</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/promisified-events.js b/accessible/tests/mochitest/promisified-events.js
new file mode 100644
index 0000000000..71b2cf013f
--- /dev/null
+++ b/accessible/tests/mochitest/promisified-events.js
@@ -0,0 +1,328 @@
+/* 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";
+
+// This is loaded by head.js, so has the same globals, hence we import the
+// globals from there.
+/* import-globals-from common.js */
+
+/* exported EVENT_ANNOUNCEMENT, EVENT_REORDER, EVENT_SCROLLING,
+ EVENT_SCROLLING_END, EVENT_SHOW, EVENT_TEXT_INSERTED,
+ EVENT_TEXT_REMOVED, EVENT_DOCUMENT_LOAD_COMPLETE, EVENT_HIDE,
+ EVENT_TEXT_ATTRIBUTE_CHANGED, EVENT_TEXT_CARET_MOVED, EVENT_SELECTION,
+ EVENT_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_STATE_CHANGE,
+ EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE, EVENT_FOCUS,
+ EVENT_DOCUMENT_RELOAD, EVENT_VIRTUALCURSOR_CHANGED, EVENT_ALERT,
+ EVENT_OBJECT_ATTRIBUTE_CHANGED, UnexpectedEvents, waitForEvent,
+ waitForEvents, waitForOrderedEvents, waitForStateChange,
+ stateChangeEventArgs */
+
+const EVENT_ANNOUNCEMENT = nsIAccessibleEvent.EVENT_ANNOUNCEMENT;
+const EVENT_DOCUMENT_LOAD_COMPLETE =
+ nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
+const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
+const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
+const EVENT_SCROLLING = nsIAccessibleEvent.EVENT_SCROLLING;
+const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START;
+const EVENT_SCROLLING_END = nsIAccessibleEvent.EVENT_SCROLLING_END;
+const EVENT_SELECTION = nsIAccessibleEvent.EVENT_SELECTION;
+const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN;
+const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
+const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
+const EVENT_TEXT_ATTRIBUTE_CHANGED =
+ nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED;
+const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
+const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
+const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
+const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE;
+const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
+const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
+const EVENT_TEXT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_TEXT_VALUE_CHANGE;
+const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
+const EVENT_DOCUMENT_RELOAD = nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD;
+const EVENT_VIRTUALCURSOR_CHANGED =
+ nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED;
+const EVENT_ALERT = nsIAccessibleEvent.EVENT_ALERT;
+const EVENT_TEXT_SELECTION_CHANGED =
+ nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED;
+const EVENT_LIVE_REGION_ADDED = nsIAccessibleEvent.EVENT_LIVE_REGION_ADDED;
+const EVENT_LIVE_REGION_REMOVED = nsIAccessibleEvent.EVENT_LIVE_REGION_REMOVED;
+const EVENT_OBJECT_ATTRIBUTE_CHANGED =
+ nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED;
+const EVENT_INNER_REORDER = nsIAccessibleEvent.EVENT_INNER_REORDER;
+
+const EventsLogger = {
+ enabled: false,
+
+ log(msg) {
+ if (this.enabled) {
+ info(msg);
+ }
+ },
+};
+
+/**
+ * Describe an event in string format.
+ * @param {nsIAccessibleEvent} event event to strigify
+ */
+function eventToString(event) {
+ let type = eventTypeToString(event.eventType);
+ let info = `Event type: ${type}`;
+
+ if (event instanceof nsIAccessibleStateChangeEvent) {
+ let stateStr = statesToString(
+ event.isExtraState ? 0 : event.state,
+ event.isExtraState ? event.state : 0
+ );
+ info += `, state: ${stateStr}, is enabled: ${event.isEnabled}`;
+ } else if (event instanceof nsIAccessibleTextChangeEvent) {
+ let tcType = event.isInserted ? "inserted" : "removed";
+ info += `, start: ${event.start}, length: ${event.length}, ${tcType} text: ${event.modifiedText}`;
+ }
+
+ info += `. Target: ${prettyName(event.accessible)}`;
+ return info;
+}
+
+function matchEvent(event, matchCriteria) {
+ if (!matchCriteria) {
+ return true;
+ }
+
+ let acc = event.accessible;
+ switch (typeof matchCriteria) {
+ case "string":
+ let id = getAccessibleDOMNodeID(acc);
+ if (id === matchCriteria) {
+ EventsLogger.log(`Event matches DOMNode id: ${id}`);
+ return true;
+ }
+ break;
+ case "function":
+ if (matchCriteria(event)) {
+ EventsLogger.log(
+ `Lambda function matches event: ${eventToString(event)}`
+ );
+ return true;
+ }
+ break;
+ default:
+ if (matchCriteria instanceof nsIAccessible) {
+ if (acc === matchCriteria) {
+ EventsLogger.log(`Event matches accessible: ${prettyName(acc)}`);
+ return true;
+ }
+ } else if (event.DOMNode == matchCriteria) {
+ EventsLogger.log(
+ `Event matches DOM node: ${prettyName(event.DOMNode)}`
+ );
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * A helper function that returns a promise that resolves when an accessible
+ * event of the given type with the given target (defined by its id or
+ * accessible) is observed.
+ * @param {Number} eventType expected accessible event
+ * type
+ * @param {String|nsIAccessible|Function} matchCriteria expected content
+ * element id
+ * for the event
+ * @param {String} message Message to prepend to logging.
+ * @return {Promise} promise that resolves to an
+ * event
+ */
+function waitForEvent(eventType, matchCriteria, message) {
+ return new Promise(resolve => {
+ let eventObserver = {
+ observe(subject, topic, data) {
+ if (topic !== "accessible-event") {
+ return;
+ }
+
+ let event = subject.QueryInterface(nsIAccessibleEvent);
+ if (EventsLogger.enabled) {
+ // Avoid calling eventToString if the EventsLogger isn't enabled in order
+ // to avoid an intermittent crash (bug 1307645).
+ EventsLogger.log(eventToString(event));
+ }
+
+ // If event type does not match expected type, skip the event.
+ if (event.eventType !== eventType) {
+ return;
+ }
+
+ if (matchEvent(event, matchCriteria)) {
+ EventsLogger.log(
+ `Correct event type: ${eventTypeToString(eventType)}`
+ );
+ Services.obs.removeObserver(this, "accessible-event");
+ ok(
+ true,
+ `${message ? message + ": " : ""}Recieved ${eventTypeToString(
+ eventType
+ )} event`
+ );
+ resolve(event);
+ }
+ },
+ };
+ Services.obs.addObserver(eventObserver, "accessible-event");
+ });
+}
+
+class UnexpectedEvents {
+ constructor(unexpected) {
+ if (unexpected.length) {
+ this.unexpected = unexpected;
+ Services.obs.addObserver(this, "accessible-event");
+ }
+ }
+
+ observe(subject, topic, data) {
+ if (topic !== "accessible-event") {
+ return;
+ }
+
+ let event = subject.QueryInterface(nsIAccessibleEvent);
+
+ let unexpectedEvent = this.unexpected.find(
+ ([etype, criteria]) =>
+ etype === event.eventType && matchEvent(event, criteria)
+ );
+
+ if (unexpectedEvent) {
+ ok(false, `Got unexpected event: ${eventToString(event)}`);
+ }
+ }
+
+ stop() {
+ if (this.unexpected) {
+ Services.obs.removeObserver(this, "accessible-event");
+ }
+ }
+}
+
+/**
+ * A helper function that waits for a sequence of accessible events in
+ * specified order.
+ * @param {Array} events a list of events to wait (same format as
+ * waitForEvent arguments)
+ * @param {String} message Message to prepend to logging.
+ * @param {Boolean} ordered Events need to be recieved in given order.
+ * @param {Object} invokerOrWindow a local window or a special content invoker
+ * it takes a list of arguments and a task
+ * function.
+ */
+async function waitForEvents(
+ events,
+ message,
+ ordered = false,
+ invokerOrWindow = null
+) {
+ let expected = events.expected || events;
+ // Next expected event index.
+ let currentIdx = 0;
+
+ let unexpectedListener = events.unexpected
+ ? new UnexpectedEvents(events.unexpected)
+ : null;
+
+ let results = await Promise.all(
+ expected.map((evt, idx) => {
+ const [eventType, matchCriteria] = evt;
+ return waitForEvent(eventType, matchCriteria, message).then(result => {
+ return [result, idx == currentIdx++];
+ });
+ })
+ );
+
+ if (unexpectedListener) {
+ let flushQueue = async win => {
+ // Flush all notifications or queued a11y events.
+ win.windowUtils.advanceTimeAndRefresh(100);
+
+ // Flush all DOM async events.
+ await new Promise(r => win.setTimeout(r, 0));
+
+ // Flush all notifications or queued a11y events resulting from async DOM events.
+ win.windowUtils.advanceTimeAndRefresh(100);
+
+ // Flush all notifications or a11y events that may have been queued in the last tick.
+ win.windowUtils.advanceTimeAndRefresh(100);
+
+ // Return refresh to normal.
+ win.windowUtils.restoreNormalRefresh();
+ };
+
+ if (invokerOrWindow instanceof Function) {
+ await invokerOrWindow([flushQueue.toString()], async _flushQueue => {
+ // eslint-disable-next-line no-eval, no-undef
+ await eval(_flushQueue)(content);
+ });
+ } else {
+ await flushQueue(invokerOrWindow ? invokerOrWindow : window);
+ }
+
+ unexpectedListener.stop();
+ }
+
+ if (ordered) {
+ ok(
+ results.every(([, isOrdered]) => isOrdered),
+ `${message ? message + ": " : ""}Correct event order`
+ );
+ }
+
+ return results.map(([event]) => event);
+}
+
+function waitForOrderedEvents(events, message) {
+ return waitForEvents(events, message, true);
+}
+
+function stateChangeEventArgs(id, state, isEnabled, isExtra = false) {
+ return [
+ EVENT_STATE_CHANGE,
+ e => {
+ e.QueryInterface(nsIAccessibleStateChangeEvent);
+ return (
+ e.state == state &&
+ e.isExtraState == isExtra &&
+ isEnabled == e.isEnabled &&
+ (typeof id == "string"
+ ? id == getAccessibleDOMNodeID(e.accessible)
+ : getAccessible(id) == e.accessible)
+ );
+ },
+ ];
+}
+
+function waitForStateChange(id, state, isEnabled, isExtra = false) {
+ return waitForEvent(...stateChangeEventArgs(id, state, isEnabled, isExtra));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Utility functions ported from events.js.
+
+/**
+ * This function selects all text in the passed-in element if it has an editor,
+ * before setting focus to it. This simulates behavio with the keyboard when
+ * tabbing to the element. This does explicitly what synthFocus did implicitly.
+ * This should be called only if you really want this behavior.
+ * @param {string} id The element ID to focus
+ */
+function selectAllTextAndFocus(id) {
+ const elem = getNode(id);
+ if (elem.editor) {
+ elem.selectionStart = elem.selectionEnd = elem.value.length;
+ }
+
+ elem.focus();
+}
diff --git a/accessible/tests/mochitest/relations.js b/accessible/tests/mochitest/relations.js
new file mode 100644
index 0000000000..aa956649ff
--- /dev/null
+++ b/accessible/tests/mochitest/relations.js
@@ -0,0 +1,204 @@
+/* import-globals-from common.js */
+
+// //////////////////////////////////////////////////////////////////////////////
+// Constants
+
+var RELATION_CONTROLLED_BY = nsIAccessibleRelation.RELATION_CONTROLLED_BY;
+var RELATION_CONTROLLER_FOR = nsIAccessibleRelation.RELATION_CONTROLLER_FOR;
+var RELATION_DEFAULT_BUTTON = nsIAccessibleRelation.RELATION_DEFAULT_BUTTON;
+var RELATION_DESCRIBED_BY = nsIAccessibleRelation.RELATION_DESCRIBED_BY;
+var RELATION_DESCRIPTION_FOR = nsIAccessibleRelation.RELATION_DESCRIPTION_FOR;
+var RELATION_EMBEDDED_BY = nsIAccessibleRelation.RELATION_EMBEDDED_BY;
+var RELATION_EMBEDS = nsIAccessibleRelation.RELATION_EMBEDS;
+var RELATION_FLOWS_FROM = nsIAccessibleRelation.RELATION_FLOWS_FROM;
+var RELATION_FLOWS_TO = nsIAccessibleRelation.RELATION_FLOWS_TO;
+var RELATION_LABEL_FOR = nsIAccessibleRelation.RELATION_LABEL_FOR;
+var RELATION_LABELLED_BY = nsIAccessibleRelation.RELATION_LABELLED_BY;
+var RELATION_MEMBER_OF = nsIAccessibleRelation.RELATION_MEMBER_OF;
+var RELATION_NODE_CHILD_OF = nsIAccessibleRelation.RELATION_NODE_CHILD_OF;
+var RELATION_NODE_PARENT_OF = nsIAccessibleRelation.RELATION_NODE_PARENT_OF;
+var RELATION_PARENT_WINDOW_OF = nsIAccessibleRelation.RELATION_PARENT_WINDOW_OF;
+var RELATION_POPUP_FOR = nsIAccessibleRelation.RELATION_POPUP_FOR;
+var RELATION_SUBWINDOW_OF = nsIAccessibleRelation.RELATION_SUBWINDOW_OF;
+var RELATION_CONTAINING_DOCUMENT =
+ nsIAccessibleRelation.RELATION_CONTAINING_DOCUMENT;
+var RELATION_CONTAINING_TAB_PANE =
+ nsIAccessibleRelation.RELATION_CONTAINING_TAB_PANE;
+var RELATION_CONTAINING_APPLICATION =
+ nsIAccessibleRelation.RELATION_CONTAINING_APPLICATION;
+const RELATION_DETAILS = nsIAccessibleRelation.RELATION_DETAILS;
+const RELATION_DETAILS_FOR = nsIAccessibleRelation.RELATION_DETAILS_FOR;
+const RELATION_ERRORMSG = nsIAccessibleRelation.RELATION_ERRORMSG;
+const RELATION_ERRORMSG_FOR = nsIAccessibleRelation.RELATION_ERRORMSG_FOR;
+const RELATION_LINKS_TO = nsIAccessibleRelation.RELATION_LINKS_TO;
+
+// //////////////////////////////////////////////////////////////////////////////
+// General
+
+/**
+ * Test the accessible relation.
+ *
+ * @param aIdentifier [in] identifier to get an accessible, may be ID
+ * attribute or DOM element or accessible object
+ * @param aRelType [in] relation type (see constants above)
+ * @param aRelatedIdentifiers [in] identifier or array of identifiers of
+ * expected related accessibles
+ */
+function testRelation(aIdentifier, aRelType, aRelatedIdentifiers) {
+ var relation = getRelationByType(aIdentifier, aRelType);
+
+ var relDescr = getRelationErrorMsg(aIdentifier, aRelType);
+ var relDescrStart = getRelationErrorMsg(aIdentifier, aRelType, true);
+
+ if (!relation || !relation.targetsCount) {
+ if (!aRelatedIdentifiers) {
+ ok(true, "No" + relDescr);
+ return;
+ }
+
+ var msg =
+ relDescrStart +
+ "has no expected targets: '" +
+ prettyName(aRelatedIdentifiers) +
+ "'";
+
+ ok(false, msg);
+ return;
+ } else if (!aRelatedIdentifiers) {
+ ok(false, "There are unexpected targets of " + relDescr);
+ return;
+ }
+
+ var relatedIds =
+ aRelatedIdentifiers instanceof Array
+ ? aRelatedIdentifiers
+ : [aRelatedIdentifiers];
+
+ var targets = [];
+ for (let idx = 0; idx < relatedIds.length; idx++) {
+ targets.push(getAccessible(relatedIds[idx]));
+ }
+
+ if (targets.length != relatedIds.length) {
+ return;
+ }
+
+ var actualTargets = relation.getTargets();
+
+ // Check if all given related accessibles are targets of obtained relation.
+ for (let idx = 0; idx < targets.length; idx++) {
+ var isFound = false;
+ for (let relatedAcc of actualTargets.enumerate(Ci.nsIAccessible)) {
+ if (targets[idx] == relatedAcc) {
+ isFound = true;
+ break;
+ }
+ }
+
+ ok(isFound, prettyName(relatedIds[idx]) + " is not a target of" + relDescr);
+ }
+
+ // Check if all obtained targets are given related accessibles.
+ for (let relatedAcc of actualTargets.enumerate(Ci.nsIAccessible)) {
+ let idx;
+ // eslint-disable-next-line no-empty
+ for (idx = 0; idx < targets.length && relatedAcc != targets[idx]; idx++) {}
+
+ if (idx == targets.length) {
+ ok(
+ false,
+ "There is unexpected target" + prettyName(relatedAcc) + "of" + relDescr
+ );
+ }
+ }
+}
+
+/**
+ * Test that the given accessible relations don't exist.
+ *
+ * @param aIdentifier [in] identifier to get an accessible, may be ID
+ * attribute or DOM element or accessible object
+ * @param aRelType [in] relation type (see constants above)
+ * @param aUnrelatedIdentifiers [in] identifier or array of identifiers of
+ * accessibles that shouldn't exist for this
+ * relation.
+ */
+function testAbsentRelation(aIdentifier, aRelType, aUnrelatedIdentifiers) {
+ var relation = getRelationByType(aIdentifier, aRelType);
+
+ var relDescr = getRelationErrorMsg(aIdentifier, aRelType);
+
+ if (!aUnrelatedIdentifiers) {
+ ok(false, "No identifiers given for unrelated accessibles.");
+ return;
+ }
+
+ if (!relation || !relation.targetsCount) {
+ ok(true, "No relations exist.");
+ return;
+ }
+
+ var relatedIds =
+ aUnrelatedIdentifiers instanceof Array
+ ? aUnrelatedIdentifiers
+ : [aUnrelatedIdentifiers];
+
+ var targets = [];
+ for (let idx = 0; idx < relatedIds.length; idx++) {
+ targets.push(getAccessible(relatedIds[idx]));
+ }
+
+ if (targets.length != relatedIds.length) {
+ return;
+ }
+
+ var actualTargets = relation.getTargets();
+
+ // Any found targets that match given accessibles should be called out.
+ for (let idx = 0; idx < targets.length; idx++) {
+ var notFound = true;
+ for (let relatedAcc of actualTargets.enumerate(Ci.nsIAccessible)) {
+ if (targets[idx] == relatedAcc) {
+ notFound = false;
+ break;
+ }
+ }
+
+ ok(notFound, prettyName(relatedIds[idx]) + " is a target of " + relDescr);
+ }
+}
+
+/**
+ * Return related accessible for the given relation type.
+ *
+ * @param aIdentifier [in] identifier to get an accessible, may be ID attribute
+ * or DOM element or accessible object
+ * @param aRelType [in] relation type (see constants above)
+ */
+function getRelationByType(aIdentifier, aRelType) {
+ var acc = getAccessible(aIdentifier);
+ if (!acc) {
+ return null;
+ }
+
+ var relation = null;
+ try {
+ relation = acc.getRelationByType(aRelType);
+ } catch (e) {
+ ok(false, "Can't get" + getRelationErrorMsg(aIdentifier, aRelType));
+ }
+
+ return relation;
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Private implementation details
+
+function getRelationErrorMsg(aIdentifier, aRelType, aIsStartSentence) {
+ var relStr = relationTypeToString(aRelType);
+ var msg = aIsStartSentence ? "Relation of '" : " relation of '";
+ msg += relStr + "' type for '" + prettyName(aIdentifier) + "'";
+ msg += aIsStartSentence ? " " : ".";
+
+ return msg;
+}
diff --git a/accessible/tests/mochitest/relations/a11y.ini b/accessible/tests/mochitest/relations/a11y.ini
new file mode 100644
index 0000000000..89ffaffdce
--- /dev/null
+++ b/accessible/tests/mochitest/relations/a11y.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_embeds.xhtml]
+skip-if = os == 'linux' && !debug # bug 1411145
+[test_general.html]
+[test_general.xhtml]
+[test_groupInfoUpdate.html]
+[test_tabbrowser.xhtml]
+[test_tree.xhtml]
+[test_ui_modalprompt.html]
+[test_shadowdom.html]
+[test_update.html]
diff --git a/accessible/tests/mochitest/relations/test_embeds.xhtml b/accessible/tests/mochitest/relations/test_embeds.xhtml
new file mode 100644
index 0000000000..d49ce1f73c
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_embeds.xhtml
@@ -0,0 +1,128 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Embeds relation tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function loadURI(aURI)
+ {
+ this.invoke = function loadURI_invoke()
+ {
+ tabBrowser().loadURI(Services.io.newURI(aURI), {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument)
+ ];
+
+ this.finalCheck = function loadURI_finalCheck()
+ {
+ testRelation(browserDocument(), RELATION_EMBEDS,
+ getAccessible(currentTabDocument()));
+ }
+
+ this.getID = function loadURI_getID()
+ {
+ return "load uri " + aURI;
+ }
+ }
+
+ function addTab(aURI)
+ {
+ this.invoke = function addTab_invoke()
+ {
+ tabBrowser().addTab(aURI, {
+ referrerURI: null,
+ charset: null,
+ postData: null,
+ inBackground: false,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument)
+ ];
+
+ this.finalCheck = function loadURI_finalCheck()
+ {
+ testRelation(browserDocument(), RELATION_EMBEDS,
+ getAccessible(currentTabDocument()));
+ }
+
+ this.getID = function addTab_getID()
+ {
+ return "load uri '" + aURI + "' in new tab";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Testing
+
+ //gA11yEventDumpToConsole = true; // debug
+
+ var gQueue = null;
+ function doTests()
+ {
+ testRelation(browserDocument(), RELATION_EMBEDS,
+ getAccessible(currentTabDocument()));
+
+ enableLogging("docload");
+ gQueue = new eventQueue();
+
+ gQueue.push(new loadURI("about:robots"));
+ gQueue.push(new addTab("about:mozilla"));
+
+ gQueue.onFinish = function()
+ {
+ disableLogging();
+ closeBrowserWindow();
+ }
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTests, "about:license");
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=707654"
+ title="Embeds relation on root accessible can return not content document">
+ Mozilla Bug 707654
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/relations/test_general.html b/accessible/tests/mochitest/relations/test_general.html
new file mode 100644
index 0000000000..d16b7c1492
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_general.html
@@ -0,0 +1,456 @@
+<html>
+
+<head>
+ <title>nsIAccessible::getAccessibleRelated() tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // html:label@for
+ testRelation("label1_1", RELATION_LABEL_FOR, "control1_1");
+ testRelation("control1_1", RELATION_LABELLED_BY, "label1_1");
+
+ // html:label@for, multiple
+ testRelation("label1_2", RELATION_LABEL_FOR, "control1_2");
+ testRelation("label1_3", RELATION_LABEL_FOR, "control1_2");
+ testRelation("control1_2", RELATION_LABELLED_BY,
+ [ "label1_2", "label1_3" ]);
+
+ // ancestor html:label (implicit association)
+ testRelation("label1_4", RELATION_LABEL_FOR, "control1_4");
+ testRelation("control1_4", RELATION_LABELLED_BY, "label1_4");
+ testRelation("control1_4_option1", RELATION_LABELLED_BY, null);
+ testRelation("label1_5", RELATION_LABEL_FOR, "control1_5");
+ testRelation("control1_5", RELATION_LABELLED_BY, "label1_5");
+ testRelation("label1_6", RELATION_LABEL_FOR, "control1_6");
+ testRelation("control1_6", RELATION_LABELLED_BY, "label1_6");
+ testRelation("label1_7", RELATION_LABEL_FOR, "control1_7");
+ testRelation("control1_7", RELATION_LABELLED_BY, "label1_7");
+ testRelation("label1_8", RELATION_LABEL_FOR, "control1_8");
+ testRelation("control1_8", RELATION_LABELLED_BY, "label1_8");
+ testRelation("label1_9", RELATION_LABEL_FOR, "control1_9");
+ testRelation("control1_9", RELATION_LABELLED_BY, "label1_9");
+ testRelation("label1_10", RELATION_LABEL_FOR, "control1_10");
+ testRelation("control1_10", RELATION_LABELLED_BY, "label1_10");
+ testRelation("label1_11", RELATION_LABEL_FOR, "control1_11");
+ testRelation("control1_11", RELATION_LABELLED_BY, "label1_11");
+ testRelation("label1_12", RELATION_LABEL_FOR, "control1_12");
+ testRelation("control1_12", RELATION_LABELLED_BY, "label1_12");
+
+ testRelation("label1_13", RELATION_LABEL_FOR, null);
+ testRelation("control1_13", RELATION_LABELLED_BY, null);
+ testRelation("control1_14", RELATION_LABELLED_BY, "label1_14");
+
+ // aria-labelledby
+ testRelation("label2", RELATION_LABEL_FOR, "checkbox2");
+ testRelation("checkbox2", RELATION_LABELLED_BY, "label2");
+
+ // aria-labelledby, multiple relations
+ testRelation("label3", RELATION_LABEL_FOR, "checkbox3");
+ testRelation("label4", RELATION_LABEL_FOR, "checkbox3");
+ testRelation("checkbox3", RELATION_LABELLED_BY, ["label3", "label4"]);
+
+ // aria-describedby
+ testRelation("descr1", RELATION_DESCRIPTION_FOR, "checkbox4");
+ testRelation("checkbox4", RELATION_DESCRIBED_BY, "descr1");
+
+ // aria-describedby, multiple relations
+ testRelation("descr2", RELATION_DESCRIPTION_FOR, "checkbox5");
+ testRelation("descr3", RELATION_DESCRIPTION_FOR, "checkbox5");
+ testRelation("checkbox5", RELATION_DESCRIBED_BY, ["descr2", "descr3"]);
+
+ // aria_owns, multiple relations
+ testRelation("treeitem1", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem2", RELATION_NODE_CHILD_OF, "tree");
+
+ // 'node child of' relation for outlineitem role
+ testRelation("treeitem3", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem4", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem5", RELATION_NODE_CHILD_OF, "treeitem4");
+ testRelation("treeitem6", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem7", RELATION_NODE_CHILD_OF, "treeitem6");
+ testRelation("tree2_ti1", RELATION_NODE_CHILD_OF, "tree2");
+ testRelation("tree2_ti1a", RELATION_NODE_CHILD_OF, "tree2_ti1");
+ testRelation("tree2_ti1b", RELATION_NODE_CHILD_OF, "tree2_ti1");
+
+ // 'node child of' relation for row role in grid.
+ // Relation for row associated using aria-level should exist.
+ testRelation("simplegrid-row3", RELATION_NODE_CHILD_OF,
+ "simplegrid-row2");
+ // Relations for hierarchical children elements shouldn't exist.
+ testAbsentRelation("simplegrid-row1", RELATION_NODE_CHILD_OF,
+ "simplegrid");
+ testAbsentRelation("simplegrid-row2", RELATION_NODE_CHILD_OF,
+ "simplegrid");
+
+ // 'node child of' relation for row role of treegrid
+ testRelation("treegridrow1", RELATION_NODE_CHILD_OF, "treegrid");
+ testRelation("treegridrow2", RELATION_NODE_CHILD_OF, "treegrid");
+ testRelation("treegridrow3", RELATION_NODE_CHILD_OF, "treegridrow2");
+
+ // 'node child of' relation for lists organized by groups
+ testRelation("listitem1", RELATION_NODE_CHILD_OF, "list");
+ testRelation("listitem1.1", RELATION_NODE_CHILD_OF, "listitem1");
+ testRelation("listitem1.2", RELATION_NODE_CHILD_OF, "listitem1");
+
+ // 'node child of' relation for lists and trees organized by groups, with
+ // intervening generic accessibles between widget components.
+ testRelation("list2_listitem1.1", RELATION_NODE_CHILD_OF, "list2_listitem1");
+ testRelation("list2_listitem1.2", RELATION_NODE_CHILD_OF, "list2_listitem1");
+ testRelation("tree4_treeitem1.1", RELATION_NODE_CHILD_OF, "tree4_treeitem1");
+ testRelation("tree4_treeitem1.2", RELATION_NODE_CHILD_OF, "tree4_treeitem1");
+
+ // 'node child of' relation for a treeitem sibling group special case,
+ // with intervening generic accessibles. In this case, if a treeitem's
+ // parent is a group and that group has a previous treeitem sibling, the
+ // treeitem is a child of that previous treeitem sibling.
+ testRelation("tree3_treeitem1.1", RELATION_NODE_CHILD_OF, "tree3_treeitem1");
+ testRelation("tree3_treeitem1.2", RELATION_NODE_CHILD_OF, "tree3_treeitem1");
+
+ // 'node child of' relation for the document having window, returns
+ // direct accessible parent (fixed in bug 419770).
+ var iframeElmObj = {};
+ var iframeAcc = getAccessible("iframe", null, iframeElmObj);
+ var iframeDoc = iframeElmObj.value.contentDocument;
+ var iframeDocAcc = getAccessible(iframeDoc);
+ testRelation(iframeDocAcc, RELATION_NODE_CHILD_OF, iframeAcc);
+
+ // 'node parent of' relation on ARIA tree and treegrid.
+ testRelation("tree", RELATION_NODE_PARENT_OF,
+ ["treeitem1", "treeitem2", // aria-owns
+ "treeitem3", "treeitem4", "treeitem6"]); // children
+ testRelation("treeitem4", RELATION_NODE_PARENT_OF,
+ "treeitem5"); // aria-level
+ testRelation("treeitem6", RELATION_NODE_PARENT_OF,
+ "treeitem7"); // // group role
+ testRelation("tree2", RELATION_NODE_PARENT_OF, "tree2_ti1"); // group role
+ testRelation("tree2_ti1", RELATION_NODE_PARENT_OF,
+ ["tree2_ti1a", "tree2_ti1b"]); // group role
+
+ testRelation("treegridrow2", RELATION_NODE_PARENT_OF, "treegridrow3");
+ testRelation("treegrid", RELATION_NODE_PARENT_OF,
+ ["treegridrow1", "treegridrow2"]);
+
+ // 'node parent of' relation on ARIA grid.
+ // 'node parent of' relation on ARIA grid's row.
+ // Should only have relation to child through aria-level.
+ testRelation("simplegrid-row2", RELATION_NODE_PARENT_OF,
+ "simplegrid-row3");
+
+ // 'node parent of' relation on ARIA list structured by groups
+ testRelation("list", RELATION_NODE_PARENT_OF,
+ "listitem1");
+ testRelation("listitem1", RELATION_NODE_PARENT_OF,
+ [ "listitem1.1", "listitem1.2" ]);
+
+ // aria-atomic
+ testRelation(getNode("atomic").firstChild, RELATION_MEMBER_OF, "atomic");
+
+ // aria-controls
+ getAccessible("tab");
+ todo(false,
+ "Getting an accessible tab, otherwise relations for tabpanel aren't cached. Bug 606924 will fix that.");
+ testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab");
+ testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel");
+
+ // aria-controls, multiple relations
+ testRelation("lr1", RELATION_CONTROLLED_BY, "button");
+ testRelation("lr2", RELATION_CONTROLLED_BY, "button");
+ testRelation("button", RELATION_CONTROLLER_FOR, ["lr1", "lr2"]);
+
+ // aria-flowto
+ testRelation("flowto", RELATION_FLOWS_TO, "flowfrom");
+ testRelation("flowfrom", RELATION_FLOWS_FROM, "flowto");
+
+ // aria-flowto, multiple relations
+ testRelation("flowto1", RELATION_FLOWS_TO, ["flowfrom1", "flowfrom2"]);
+ testRelation("flowfrom1", RELATION_FLOWS_FROM, "flowto1");
+ testRelation("flowfrom2", RELATION_FLOWS_FROM, "flowto1");
+
+ // 'default button' relation
+ testRelation("input", RELATION_DEFAULT_BUTTON, "submit");
+
+ // output 'for' relations
+ testRelation("output", RELATION_CONTROLLED_BY, ["input", "input2"]);
+ testRelation("output2", RELATION_CONTROLLED_BY, ["input", "input2"]);
+ testRelation("input", RELATION_CONTROLLER_FOR, ["output", "output2"]);
+ testRelation("input2", RELATION_CONTROLLER_FOR, ["output", "output2"]);
+
+ // 'described by'/'description for' relation for html:table and
+ // html:caption
+ testRelation("caption", RELATION_LABEL_FOR, "table");
+ testRelation("table", RELATION_LABELLED_BY, "caption");
+
+ // 'labelled by'/'label for' relation for html:fieldset and
+ // html:legend
+ testRelation("legend", RELATION_LABEL_FOR, "fieldset");
+ testRelation("fieldset", RELATION_LABELLED_BY, "legend");
+
+ // containing relations
+ testRelation("control1_1", RELATION_CONTAINING_DOCUMENT, document);
+ testRelation("control1_1", RELATION_CONTAINING_TAB_PANE, getTabDocAccessible("control1_1"));
+ testRelation("control1_1", RELATION_CONTAINING_APPLICATION, getApplicationAccessible());
+
+ // details
+ testRelation("has_details", RELATION_DETAILS, "details");
+ testRelation("details", RELATION_DETAILS_FOR, "has_details");
+ testRelation("has_multiple_details", RELATION_DETAILS, ["details2", "details3"]);
+ testRelation("details2", RELATION_DETAILS_FOR, "has_multiple_details");
+ testRelation("details3", RELATION_DETAILS_FOR, "has_multiple_details");
+
+ // error
+ testRelation("has_error", RELATION_ERRORMSG, "error");
+ testRelation("error", RELATION_ERRORMSG_FOR, "has_error");
+
+ // finish test
+ SimpleTest.finish();
+ }
+
+ disableLogging(); // from test_embeds.xhtml
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=475298"
+ title="mochitests for accessible relations">
+ Bug 475298
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=527461"
+ title="Implement RELATION_NODE_PARENT_OF">
+ Bug 527461
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=558036"
+ title="make HTML <output> accessible">
+ Bug 558036
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=682790"
+ title="Ignore implicit label association when it's associated explicitly">
+ Bug 682790
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=687393"
+ title="HTML select options gets relation from containing label">
+ Bug 687393
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=864224"
+ title="Support nested ARIA listitems structured by role='group'">
+ Bug 864224
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <label id="label1_1" for="control1_1">label</label>
+ <input id="control1_1">
+
+ <label id="label1_2" for="control1_2">label</label>
+ <label id="label1_3" for="control1_2">label</label>
+ <input id="control1_2">
+
+ <label id="label1_4">Label
+ <select id="control1_4">
+ <option id="control1_4_option1">option</option>
+ </select>
+ </label>
+ <label id="label1_5">Label
+ <button id="control1_5">button</button>
+ </label>
+ <label id="label1_6">Label
+ <input id="control1_6">
+ </label>
+ <label id="label1_7">Label
+ <input id="control1_7" type="checkbox">
+ </label>
+ <label id="label1_8">Label
+ <input id="control1_8" type="radio">
+ </label>
+ <label id="label1_9">Label
+ <input id="control1_9" type="button" value="button">
+ </label>
+ <label id="label1_10">Label
+ <input id="control1_10" type="submit">
+ </label>
+ <label id="label1_11">Label
+ <input id="control1_11" type="image">
+ </label>
+ <label id="label1_12">Label
+ <progress id="control1_12"></progress>
+ </label>
+
+ <label id="label1_13" for="">Label
+ <input id="control1_13">
+ </label>
+ <label id="label1_14" for="control1_14">Label
+ <input id="control1_14">
+ </label>
+
+ <span id="label2">label</span>
+ <span role="checkbox" id="checkbox2" aria-labelledby="label2"></span>
+
+ <span id="label3">label1</span>
+ <span id="label4">label2</span>
+ <span role="checkbox" id="checkbox3" aria-labelledby="label3 label4"></span>
+
+ <span id="descr1">description</span>
+ <span role="checkbox" id="checkbox4" aria-describedby="descr1"></span>
+
+ <span id="descr2">description1</span>
+ <span id="descr3">description2</span>
+ <span role="checkbox" id="checkbox5" aria-describedby="descr2 descr3"></span>
+
+ <div role="treeitem" id="treeitem1">Yellow</div>
+ <div role="treeitem" id="treeitem2">Orange</div>
+ <div id="tree" role="tree" aria-owns="treeitem1 treeitem2">
+ <div role="treeitem" id="treeitem3">Blue</div>
+ <div role="treeitem" id="treeitem4" aria-level="1">Green</div>
+ <div role="treeitem" id="treeitem5" aria-level="2">Light green</div>
+ <div role="treeitem" id="treeitem6" aria-level="1">Green2</div>
+ <div role="group">
+ <div role="treeitem" id="treeitem7">Super light green</div>
+ </div>
+ </div>
+
+ <div role="grid" id="simplegrid">
+ <div role="row" id="simplegrid-row1" aria-level="1">
+ <div role="gridcell">cell 1,1</div>
+ <div role="gridcell">cell 1,2</div>
+ </div>
+ <div role="row" id="simplegrid-row2" aria-level="1">
+ <div role="gridcell">cell 2,1</div>
+ <div role="gridcell">cell 2,2</div>
+ </div>
+ <div role="row" id="simplegrid-row3" aria-level="2">
+ <div role="gridcell">cell 3,1</div>
+ <div role="gridcell">cell 3,2</div>
+ </div>
+ </div>
+
+ <ul role="tree" id="tree2">
+ <li role="treeitem" id="tree2_ti1">Item 1
+ <ul role="group">
+ <li role="treeitem" id="tree2_ti1a">Item 1A</li>
+ <li role="treeitem" id="tree2_ti1b">Item 1B</li>
+ </ul>
+ </li>
+ </ul>
+
+ <div role="tree" id="tree3">
+ <div tabindex="0">
+ <div role="treeitem" id="tree3_treeitem1">1</div>
+ </div>
+ <div tabindex="0">
+ <div role="group">
+ <div role="treeitem" id="tree3_treeitem1.1">1.1</div>
+ <div role="treeitem" id="tree3_treeitem1.2">1.2</div>
+ </div>
+ </div>
+ </div>
+
+ <div role="treegrid" id="treegrid">
+ <div role="row" id="treegridrow1">
+ <span role="gridcell">cell1</span><span role="gridcell">cell2</span>
+ </div>
+ <div role="row" id="treegridrow2" aria-level="1">
+ <span role="gridcell">cell3</span><span role="gridcell">cell4</span>
+ </div>
+ <div role="row" id="treegridrow3" aria-level="2">
+ <span role="gridcell">cell5</span><span role="gridcell">cell6</span>
+ </div>
+ </div>
+
+ <div role="list" id="list">
+ <div role="listitem" id="listitem1">Item 1
+ <div role="group">
+ <div role="listitem" id="listitem1.1">Item 1A</div>
+ <div role="listitem" id="listitem1.2">Item 1B</div>
+ </div>
+ </div>
+ </div>
+
+ <div role="tree" id="tree4">
+ <div role="treeitem" id="tree4_treeitem1">1
+ <div tabindex="0">
+ <div role="group">
+ <div role="treeitem" id="tree4_treeitem1.1">1.1</div>
+ <div role="treeitem" id="tree4_treeitem1.2">1.2</div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div role="list" id="list2">
+ <div role="listitem" id="list2_listitem1">1
+ <div tabindex="0">
+ <div role="group">
+ <div role="listitem" id="list2_listitem1.1">1.1</div>
+ <div role="listitem" id="list2_listitem1.2">1.2</div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <iframe id="iframe"></iframe>
+
+ <div id="tablist" role="tablist">
+ <div id="tab" role="tab" aria-controls="tabpanel">tab</div>
+ </div>
+ <div id="tabpanel" role="tabpanel">tabpanel</div>
+
+ <div id="lr1" aria-live="assertive">1</div>
+ <div id="lr2" aria-live="assertive">a</div>
+ <input type="button" id="button" aria-controls="lr1 lr2"
+ onclick="getNode('lr1').textContent += '1'; getNode('lr2').textContent += 'a';"/>
+
+ <div id="atomic" aria-atomic="true">live region</div>
+
+ <span id="flowto" aria-flowto="flowfrom">flow to</span>
+ <span id="flowfrom">flow from</span>
+
+ <span id="flowto1" aria-flowto="flowfrom1 flowfrom2">flow to</span>
+ <span id="flowfrom1">flow from</span>
+ <span id="flowfrom2">flow from</span>
+
+ <form id="form">
+ <input id="input" />
+ <input id="input2" />
+ <input type="submit" id="submit" />
+ <output id="output" style="display:block" for="input input2"></output>
+ <output id="output2" for="input input2"></output>
+ </form>
+
+ <table id="table">
+ <caption id="caption">tabple caption</caption>
+ <tr>
+ <td>cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <fieldset id="fieldset">
+ <legend id="legend">legend</legend>
+ <input />
+ </fieldset>
+
+ <input id="has_details" aria-details="details"><section id="details"></section>
+ <input id="has_multiple_details" aria-details="details2 details3"><section id="details2"></section><section id="details3"></section>
+ <input id="has_error" aria-errormessage="error"><section id="error"></section>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/relations/test_general.xhtml b/accessible/tests/mochitest/relations/test_general.xhtml
new file mode 100644
index 0000000000..bc3b328fd9
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_general.xhtml
@@ -0,0 +1,237 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="nsIAccessible::getAccessibleRelated() tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../relations.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ // xul:label@control
+ testRelation("label1", RELATION_LABEL_FOR, "checkbox1");
+ testRelation("checkbox1", RELATION_LABELLED_BY, "label1");
+
+ // xul:label@control, multiple
+ testRelation("label1_1", RELATION_LABEL_FOR, "checkbox1_1");
+ testRelation("label1_2", RELATION_LABEL_FOR, "checkbox1_1");
+ testRelation("checkbox1_1", RELATION_LABELLED_BY,
+ [ "label1_1", "label1_2" ]);
+
+ // aria-labelledby
+ testRelation("label2", RELATION_LABEL_FOR, "checkbox2");
+ testRelation("checkbox2", RELATION_LABELLED_BY, "label2");
+
+ // aria-labelledby, multiple relations
+ testRelation("label3", RELATION_LABEL_FOR, "checkbox3");
+ testRelation("label4", RELATION_LABEL_FOR, "checkbox3");
+ testRelation("checkbox3", RELATION_LABELLED_BY, ["label3", "label4"]);
+
+ // xul:label@control referring to HTML element
+ testRelation("label_input", RELATION_LABEL_FOR, "input");
+ testRelation("input", RELATION_LABELLED_BY, "label_input");
+
+ // aria-describedby
+ testRelation("descr1", RELATION_DESCRIPTION_FOR, "checkbox4");
+ testRelation("checkbox4", RELATION_DESCRIBED_BY, "descr1");
+
+ // aria-describedby, multiple relations
+ testRelation("descr2", RELATION_DESCRIPTION_FOR, "checkbox5");
+ testRelation("descr3", RELATION_DESCRIPTION_FOR, "checkbox5");
+ testRelation("checkbox5", RELATION_DESCRIBED_BY, ["descr2", "descr3"]);
+
+ // xul:description@control
+ testRelation("descr4", RELATION_DESCRIPTION_FOR, "checkbox6");
+ testRelation("checkbox6", RELATION_DESCRIBED_BY, "descr4");
+
+ // xul:description@control, multiple
+ testRelation("descr5", RELATION_DESCRIPTION_FOR, "checkbox7");
+ testRelation("descr6", RELATION_DESCRIPTION_FOR, "checkbox7");
+ testRelation("checkbox7", RELATION_DESCRIBED_BY,
+ [ "descr5", "descr6" ]);
+
+ // aria_owns, multiple relations
+ testRelation("treeitem1", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem2", RELATION_NODE_CHILD_OF, "tree");
+
+ // 'node child of' relation for outlineitem role
+ testRelation("treeitem3", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem4", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem5", RELATION_NODE_CHILD_OF, "treeitem4");
+
+ // no relation node_child_of for accessible contained in an unexpected
+ // parent
+ testRelation("treeitem6", RELATION_NODE_CHILD_OF, null);
+
+ // 'node child of' relation for the document having window, returns
+ // direct accessible parent (fixed in bug 419770).
+ var iframeElmObj = {};
+ var iframeAcc = getAccessible("iframe", null, iframeElmObj);
+ var iframeDoc = iframeElmObj.value.contentDocument;
+ var iframeDocAcc = getAccessible(iframeDoc);
+ testRelation(iframeDocAcc, RELATION_NODE_CHILD_OF, iframeAcc);
+
+ // aria-controls
+ getAccessible("tab");
+ todo(false,
+ "Getting an accessible tab, otherwise relations for tabpanel aren't cached. Bug 606924 will fix that.");
+ testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab");
+ testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel");
+
+ // aria-controls, multiple relations
+ testRelation("lr1", RELATION_CONTROLLED_BY, "button");
+ testRelation("lr2", RELATION_CONTROLLED_BY, "button");
+ testRelation("button", RELATION_CONTROLLER_FOR, ["lr1", "lr2"]);
+
+ // aria-flowto
+ testRelation("flowto", RELATION_FLOWS_TO, "flowfrom");
+ testRelation("flowfrom", RELATION_FLOWS_FROM, "flowto");
+
+ // aria-flowto, multiple relations
+ testRelation("flowto1", RELATION_FLOWS_TO, ["flowfrom1", "flowfrom2"]);
+ testRelation("flowfrom1", RELATION_FLOWS_FROM, "flowto1");
+ testRelation("flowfrom2", RELATION_FLOWS_FROM, "flowto1");
+
+ // 'labelled by'/'label for' relation for xul:groupbox and xul:label
+ var groupboxAcc = getAccessible("groupbox");
+ var labelAcc = groupboxAcc.firstChild;
+ testRelation(labelAcc, RELATION_LABEL_FOR, groupboxAcc);
+ testRelation(groupboxAcc, RELATION_LABELLED_BY, labelAcc);
+
+ // 'labelled by'/'label for' relations for xul:tab and xul:tabpanel
+ // (fixed in bug 366527)
+ testRelation("tabpanel1", RELATION_LABELLED_BY, "tab1");
+ testRelation("tab1", RELATION_LABEL_FOR, "tabpanel1");
+ testRelation("tabpanel2", RELATION_LABELLED_BY, "tab2");
+ testRelation("tab2", RELATION_LABEL_FOR, "tabpanel2");
+ testRelation("tabpanel3", RELATION_LABELLED_BY, "tab3");
+ testRelation("tab3", RELATION_LABEL_FOR, "tabpanel3");
+
+ // finish test
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <vbox style="overflow: auto;" flex="1">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=475298"
+ title="mochitests for accessible relations">
+ Mozilla Bug 475298
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673389"
+ title="node_child_of on an item not in a proper container">
+ Mozilla Bug 67389
+ </a><br/>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <label id="label1" control="checkbox1">label</label>
+ <checkbox id="checkbox1"/>
+
+ <label id="label1_1" control="checkbox1_1">label</label>
+ <label id="label1_2" control="checkbox1_1">label</label>
+ <checkbox id="checkbox1_1"/>
+
+ <description id="label2">label</description>
+ <description role="checkbox" id="checkbox2" aria-labelledby="label2"/>
+
+ <description id="label3">label</description>
+ <description id="label4">label</description>
+ <description role="checkbox" id="checkbox3"
+ aria-labelledby="label3 label4"/>
+
+ <label id="label_input" control="input">label</label>
+ <html:input id="input"/>
+
+ <description id="descr1">description</description>
+ <description role="checkbox" id="checkbox4" aria-describedby="descr1"/>
+
+ <description id="descr2">label</description>
+ <description id="descr3">label</description>
+ <description role="checkbox" id="checkbox5"
+ aria-describedby="descr2 descr3"/>
+
+ <description id="descr4" control="checkbox6">description</description>
+ <checkbox id="checkbox6"/>
+
+ <description id="descr5" control="checkbox7">description</description>
+ <description id="descr6" control="checkbox7">description</description>
+ <checkbox id="checkbox7"/>
+
+ <description role="treeitem" id="treeitem1">Yellow</description>
+ <description role="treeitem" id="treeitem2">Orange</description>
+ <vbox id="tree" role="tree" aria-owns="treeitem1 treeitem2">
+ <description role="treeitem" id="treeitem3">Blue</description>
+ <description role="treeitem" id="treeitem4" aria-level="1">Green</description>
+ <description role="treeitem" id="treeitem5" aria-level="2">Light green</description>
+ </vbox>
+
+ <description role="treeitem" id="treeitem6">Dark green</description>
+
+ <iframe id="iframe"/>
+
+ <hbox id="tablist" role="tablist">
+ <description id="tab" role="tab" aria-controls="tabpanel">tab</description>
+ </hbox>
+ <description id="tabpanel" role="tabpanel">tabpanel</description>
+
+ <description id="lr1" aria-live="assertive">1</description>
+ <description id="lr2" aria-live="assertive">a</description>
+ <button id="button" aria-controls="lr1 lr2" label="button"
+ oncommand="getNode('lr1').textContent += '1'; getNode('lr2').textContent += 'a';"/>
+
+ <description id="flowto1" aria-flowto="flowfrom1 flowfrom2">flow to</description>
+ <description id="flowfrom1">flow from</description>
+ <description id="flowfrom2">flow from</description>
+
+ <description id="flowto" aria-flowto="flowfrom">flow to</description>
+ <description id="flowfrom">flow from</description>
+
+ <groupbox id="groupbox">
+ <label value="caption"/>
+ </groupbox>
+
+ <tabbox>
+ <tabs>
+ <tab label="tab1" id="tab1"/>
+ <tab label="tab2" id="tab2" linkedpanel="tabpanel2"/>
+ <tab label="tab3" id="tab3" linkedpanel="tabpanel3"/>
+ </tabs>
+ <tabpanels>
+ <tabpanel id="tabpanel1">
+ <description>tabpanel1</description>
+ </tabpanel>
+ <tabpanel id="tabpanel3">
+ <description>tabpanel3</description>
+ </tabpanel>
+ <tabpanel id="tabpanel2">
+ <description>tabpanel2</description>
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/relations/test_groupInfoUpdate.html b/accessible/tests/mochitest/relations/test_groupInfoUpdate.html
new file mode 100644
index 0000000000..efca27617c
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_groupInfoUpdate.html
@@ -0,0 +1,57 @@
+<html>
+<head>
+ <title>Test accessible relations when AccGroupInfo updated</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+ async function doTests() {
+ info("Testing NODE_CHILD_OF update after DOM removal");
+ testRelation("l1i2", RELATION_NODE_CHILD_OF, "l1i1");
+ let reorder = waitForEvent(EVENT_REORDER, "l1");
+ getNode("l1i1").remove();
+ await reorder;
+ testRelation("l1i2", RELATION_NODE_CHILD_OF, "l1");
+
+ info("Testing NODE_CHILD_OF update after aria-owns removal");
+ testRelation("l2i2", RELATION_NODE_CHILD_OF, "l2i1");
+ reorder = waitForEvent(EVENT_REORDER, "l2");
+ // Move l2i1 out of l2 using aria-owns.
+ getNode("l2trash").setAttribute("aria-owns", "l2i1");
+ await reorder;
+ testRelation("l2i2", RELATION_NODE_CHILD_OF, "l2");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body id="body">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="l1" role="list">
+ <div id="l1i1" role="listitem" aria-level="1">a</div>
+ <div id="l1i2" role="listitem" aria-level="2">b</div>
+ </div>
+
+ <div id="l2" role="list">
+ <div id="l2i1" role="listitem" aria-level="1">a</div>
+ <div id="l2i2" role="listitem" aria-level="2">b</div>
+ </div>
+ <div id="l2trash"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/relations/test_shadowdom.html b/accessible/tests/mochitest/relations/test_shadowdom.html
new file mode 100644
index 0000000000..adb9490d99
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_shadowdom.html
@@ -0,0 +1,58 @@
+<html>
+
+<head>
+ <title>Explicit content and shadow DOM content relations tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // explicit content
+ let label = document.getElementById("label");
+ let element = document.getElementById("element");
+ testRelation(label, RELATION_LABEL_FOR, element);
+ testRelation(element, RELATION_LABELLED_BY, label);
+
+ // shadow DOM content
+ let shadowRoot = document.getElementById("shadowcontainer").shadowRoot;
+ let shadowLabel = shadowRoot.getElementById("label");
+ let shadowElement = shadowRoot.getElementById("element");
+
+ testRelation(shadowLabel, RELATION_LABEL_FOR, shadowElement);
+ testRelation(shadowElement, RELATION_LABELLED_BY, shadowLabel);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ addA11yLoadEvent(doTest, window);
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content">
+ <div id="label"></div>
+ <div id="element" aria-labelledby="label"></div>
+ <div id="shadowcontainer"></div>
+ <script>
+ let shadowRoot = document.getElementById("shadowcontainer").
+ attachShadow({mode: "open"});
+ shadowRoot.innerHTML =
+ `<div id="label"></div><div id="element" aria-labelledby="label"></div>`;
+ </script>
+ </div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/relations/test_tabbrowser.xhtml b/accessible/tests/mochitest/relations/test_tabbrowser.xhtml
new file mode 100644
index 0000000000..3356bc6140
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_tabbrowser.xhtml
@@ -0,0 +1,109 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tabbrowser relation tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../relations.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Invoker
+ function testTabRelations()
+ {
+ this.eventSeq = [
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 0),
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
+ ];
+
+ this.invoke = function testTabRelations_invoke()
+ {
+ var docURIs = ["about:license", "about:mozilla"];
+ tabBrowser().loadTabs(docURIs, {
+ inBackground: false,
+ replace: true,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ // Flush layout, so as to guarantee that the a11y tree is constructed.
+ browserDocument().documentElement.getBoundingClientRect();
+ }
+
+ this.finalCheck = function testTabRelations_finalCheck(aEvent)
+ {
+ ////////////////////////////////////////////////////////////////////////
+ // 'labelled by'/'label for' relations for xul:tab and xul:tabpanel
+
+ var tabs = Array.from(tabBrowser().tabContainer.allTabs);
+ // For preloaded tabs, there might be items in this array where this relation
+ // doesn't hold, so just deal with that:
+ var panels = tabs.map(t => t.linkedBrowser.closest("tabpanels > *"));
+
+ testRelation(panels[0], RELATION_LABELLED_BY, tabs[0]);
+ testRelation(tabs[0], RELATION_LABEL_FOR, panels[0]);
+ testRelation(panels[1], RELATION_LABELLED_BY, tabs[1]);
+ testRelation(tabs[1], RELATION_LABEL_FOR, panels[1]);
+ }
+
+ this.getID = function testTabRelations_getID()
+ {
+ return "relations of tabs";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest()
+ {
+ // Load documents into tabs and wait for DocLoadComplete events caused by
+ // these documents load before we start the test.
+
+ gQueue = new eventQueue();
+
+ gQueue.push(new testTabRelations());
+ gQueue.onFinish = function() { closeBrowserWindow(); }
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTest);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=552944"
+ title="No relationship between tabs and associated property page in new tabbrowser construct">
+ Mozilla Bug 552944
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="eventdump"></vbox>
+ </vbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/relations/test_tree.xhtml b/accessible/tests/mochitest/relations/test_tree.xhtml
new file mode 100644
index 0000000000..7c309f0956
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_tree.xhtml
@@ -0,0 +1,105 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree relations tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../relations.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ var treeNode = getNode("tree");
+
+ var tree = getAccessible(treeNode);
+ var treeitem1 = tree.firstChild.nextSibling;
+ testRelation(treeitem1, RELATION_NODE_CHILD_OF, [tree]);
+
+ var treeitem2 = treeitem1.nextSibling;
+ testRelation(treeitem2, RELATION_NODE_CHILD_OF, [tree]);
+
+ var treeitem3 = treeitem2.nextSibling;
+ testRelation(treeitem3, RELATION_NODE_CHILD_OF, [treeitem2]);
+
+ var treeitem4 = treeitem3.nextSibling;
+ testRelation(treeitem4, RELATION_NODE_CHILD_OF, [treeitem2]);
+
+ var treeitem5 = treeitem4.nextSibling;
+ testRelation(treeitem5, RELATION_NODE_CHILD_OF, [tree]);
+
+ var treeitem6 = treeitem5.nextSibling;
+ testRelation(treeitem6, RELATION_NODE_CHILD_OF, [tree]);
+
+ testRelation(tree, RELATION_NODE_PARENT_OF,
+ [treeitem1, treeitem2, treeitem5, treeitem6]);
+ testRelation(treeitem2, RELATION_NODE_PARENT_OF,
+ [treeitem3, treeitem4]);
+
+ // treeitems and treecells shouldn't pick up relations from tree
+ testRelation(treeitem1, RELATION_LABELLED_BY, null);
+ testRelation(treeitem1.firstChild, RELATION_LABELLED_BY, null);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTreeTreeView());
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Bug 503727
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=527461"
+ title="Implement RELATION_NODE_PARENT_OF">
+ Bug 527461
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=691248"
+ title="XUL tree items shouldn't pick up relations from XUL tree">
+ Bug 691248
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <label control="tree" value="It's a tree"/>
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="column2"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/relations/test_ui_modalprompt.html b/accessible/tests/mochitest/relations/test_ui_modalprompt.html
new file mode 100644
index 0000000000..a05b273d86
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_ui_modalprompt.html
@@ -0,0 +1,111 @@
+<html>
+
+<head>
+ <title>Modal prompts</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ SpecialPowers.pushPrefEnv({
+ set: [["prompts.contentPromptSubDialog", false]],
+ });
+ function showAlert() {
+ this.eventSeq = [
+ {
+ type: EVENT_SHOW,
+ match(aEvent) {
+ return aEvent.accessible.role == ROLE_DIALOG;
+ },
+ },
+ ];
+
+ this.invoke = function showAlert_invoke() {
+ window.setTimeout(
+ function() {
+ currentTabDocument().defaultView.alert("hello");
+ }, 0);
+ };
+
+ this.check = function showAlert_finalCheck(aEvent) {
+ if(aEvent.type === EVENT_HIDE) {
+ return;
+ }
+ var dialog = aEvent.accessible.DOMNode;
+ var info = dialog.querySelector(".tabmodalprompt-infoBody");
+ testRelation(info, RELATION_DESCRIPTION_FOR, dialog);
+ testRelation(dialog, RELATION_DESCRIBED_BY, info);
+ };
+
+ this.getID = function showAlert_getID() {
+ return "show alert";
+ };
+ }
+
+ function closeAlert() {
+ this.eventSeq = [
+ {
+ type: EVENT_HIDE,
+ match(aEvent) {
+ return aEvent.accessible.role == ROLE_DIALOG;
+ },
+ },
+ ];
+
+ this.invoke = function showAlert_invoke() {
+ synthesizeKey("VK_RETURN", {}, browserWindow());
+ };
+
+ this.getID = function showAlert_getID() {
+ return "cleanup alert";
+ };
+ }
+
+
+ // gA11yEventDumpToConsole = true; // debug
+
+ var gQueue = null;
+ function doTests() {
+ gQueue = new eventQueue();
+ gQueue.push(new showAlert());
+ gQueue.push(new closeAlert());
+ gQueue.onFinish = function() {
+ closeBrowserWindow();
+ };
+ gQueue.invoke(); // will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTests);
+ </script>
+
+</head>
+
+<body id="body">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=661293"
+ title="The tabmodalprompt dialog's prompt label doesn't get the text properly associated for accessibility">
+ Mozilla Bug 661293
+ </a>
+ <br>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/relations/test_update.html b/accessible/tests/mochitest/relations/test_update.html
new file mode 100644
index 0000000000..581d592bec
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_update.html
@@ -0,0 +1,213 @@
+<html>
+
+<head>
+ <title>Test updating of accessible relations</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function testRelated(aRelAttr, aHostRelation, aDependentRelation,
+ aHostID, aHostNodeID, aDependent1ID, aDependent2ID) {
+ // no attribute
+ testRelation(aDependent1ID, aDependentRelation, null);
+ testRelation(aDependent2ID, aDependentRelation, null);
+ if (aHostRelation)
+ testRelation(aHostID, aHostRelation, null);
+
+ // set attribute
+ getNode(aHostNodeID).setAttribute(aRelAttr, aDependent1ID);
+ testRelation(aDependent1ID, aDependentRelation, aHostID);
+ testRelation(aDependent2ID, aDependentRelation, null);
+ if (aHostRelation)
+ testRelation(aHostID, aHostRelation, aDependent1ID);
+
+ // change attribute
+ getNode(aHostNodeID).setAttribute(aRelAttr, aDependent2ID);
+ testRelation(aDependent1ID, aDependentRelation, null);
+ testRelation(aDependent2ID, aDependentRelation, aHostID);
+ if (aHostRelation)
+ testRelation(aHostID, aHostRelation, aDependent2ID);
+
+ // remove attribute
+ getNode(aHostNodeID).removeAttribute(aRelAttr);
+ testRelation(aDependent1ID, aDependentRelation, null);
+ testRelation(aDependent2ID, aDependentRelation, null);
+ if (aHostRelation)
+ testRelation(aHostID, aHostRelation, null);
+ }
+
+ function insertRelated(aHostRelAttr, aDependentID, aInsertHostFirst,
+ aHostRelation, aDependentRelation) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, document),
+ ];
+
+ this.invoke = function insertRelated_invoke() {
+ this.hostNode = document.createElement("div");
+ this.hostNode.setAttribute(aHostRelAttr, aDependentID);
+
+ this.dependentNode = document.createElement("div");
+ this.dependentNode.setAttribute("id", aDependentID);
+
+ if (aInsertHostFirst) {
+ document.body.appendChild(this.hostNode);
+ document.body.appendChild(this.dependentNode);
+ } else {
+ document.body.appendChild(this.dependentNode);
+ document.body.appendChild(this.hostNode);
+ }
+ };
+
+ this.finalCheck = function insertRelated_finalCheck() {
+ testRelation(this.dependentNode, aDependentRelation, this.hostNode);
+ if (aHostRelation)
+ testRelation(this.hostNode, aHostRelation, this.dependentNode);
+ };
+
+ this.getID = function insertRelated_getID() {
+ return "Insert " + aHostRelAttr + "='" + aDependentID + "' node" +
+ (aInsertHostFirst ? " before" : "after") + " dependent node";
+ };
+ }
+
+ /**
+ * Relative accessible recreation shouldn't break accessible relations.
+ * Note: modify this case if the invoke function doesn't change accessible
+ * tree due to changes in layout module. It can be changed on any case
+ * when accessibles are recreated.
+ */
+ function recreateRelatives(aContainerID, aLabelID, aElmID) {
+ this.containerNode = getNode(aContainerID);
+ this.container = getNode(this.containerNode);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.container),
+ new invokerChecker(EVENT_SHOW, this.containerNode),
+ ];
+
+ this.invoke = function recreateRelatives_invoke() {
+ testRelation(aLabelID, RELATION_LABEL_FOR, aElmID);
+ testRelation(aElmID, RELATION_LABELLED_BY, aLabelID);
+
+ this.containerNode.setAttribute('role', 'group');
+ };
+
+ this.finalCheck = function recreateRelatives_finalCheck() {
+ testRelation(aLabelID, RELATION_LABEL_FOR, aElmID);
+ testRelation(aElmID, RELATION_LABELLED_BY, aLabelID);
+ };
+
+ this.getID = function recreateRelatives_getID() {
+ return "recreate relatives ";
+ };
+ }
+
+ // gA11yEventDumpToConsole = true; // debug
+
+ var gQueue = null;
+
+ function doTest() {
+ // Relation updates on ARIA attribute changes.
+ testRelated("aria-labelledby",
+ RELATION_LABELLED_BY, RELATION_LABEL_FOR,
+ "host", "host", "dependent1", "dependent2");
+
+ testRelated("aria-describedby",
+ RELATION_DESCRIBED_BY, RELATION_DESCRIPTION_FOR,
+ "host", "host", "dependent1", "dependent2");
+
+ testRelated("aria-controls",
+ RELATION_CONTROLLER_FOR, RELATION_CONTROLLED_BY,
+ "host", "host", "dependent1", "dependent2");
+
+ testRelated("aria-flowto",
+ RELATION_FLOWS_TO, RELATION_FLOWS_FROM,
+ "host", "host", "dependent1", "dependent2");
+
+ // Document relation updates on ARIA attribute change.
+ testRelated("aria-labelledby",
+ RELATION_LABELLED_BY, RELATION_LABEL_FOR,
+ document, "body", "dependent1", "dependent2");
+
+ // Insert related accessibles into tree.
+ gQueue = new eventQueue();
+ gQueue.push(new insertRelated("aria-labelledby", "dependent3", true,
+ RELATION_LABELLED_BY, RELATION_LABEL_FOR));
+ gQueue.push(new insertRelated("aria-labelledby", "dependent4", false,
+ RELATION_LABELLED_BY, RELATION_LABEL_FOR));
+
+ gQueue.push(new insertRelated("aria-describedby", "dependent5", true,
+ RELATION_DESCRIBED_BY,
+ RELATION_DESCRIPTION_FOR));
+ gQueue.push(new insertRelated("aria-describedby", "dependent6", false,
+ RELATION_DESCRIBED_BY,
+ RELATION_DESCRIPTION_FOR));
+
+ gQueue.push(new insertRelated("aria-controls", "dependent9", true,
+ RELATION_CONTROLLER_FOR,
+ RELATION_CONTROLLED_BY));
+ gQueue.push(new insertRelated("aria-controls", "dependent10", false,
+ RELATION_CONTROLLER_FOR,
+ RELATION_CONTROLLED_BY));
+
+ gQueue.push(new insertRelated("aria-flowto", "dependent11", true,
+ RELATION_FLOWS_TO, RELATION_FLOWS_FROM));
+ gQueue.push(new insertRelated("aria-flowto", "dependent12", false,
+ RELATION_FLOWS_TO, RELATION_FLOWS_FROM));
+
+ // Update relations when accessibles are recreated
+ gQueue.push(new recreateRelatives("container", "label", "input"));
+
+ gQueue.invoke(); // will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body id="body">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=573469"
+ title="Cache relations defined by ARIA attributes">
+ Mozilla Bug 573469
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=631068"
+ title="Accessible recreation breaks relations">
+ Mozilla Bug 631068
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=635346"
+ title="Allow relations for document defined on document content">
+ Mozilla Bug 635346
+ </a>
+ <br>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="dependent1">label</div>
+ <div id="dependent2">label2</div>
+ <div role="checkbox" id="host"></div>
+
+ <form id="container" style="overflow: hidden;">
+ <label for="input" id="label">label</label>
+ <input id="input">
+ </form>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/role.js b/accessible/tests/mochitest/role.js
new file mode 100644
index 0000000000..36fa7af84f
--- /dev/null
+++ b/accessible/tests/mochitest/role.js
@@ -0,0 +1,200 @@
+/* import-globals-from common.js */
+
+// //////////////////////////////////////////////////////////////////////////////
+// Role constants
+
+const ROLE_ALERT = nsIAccessibleRole.ROLE_ALERT;
+const ROLE_ARTICLE = nsIAccessibleRole.ROLE_ARTICLE;
+const ROLE_ANIMATION = nsIAccessibleRole.ROLE_ANIMATION;
+const ROLE_APPLICATION = nsIAccessibleRole.ROLE_APPLICATION;
+const ROLE_APP_ROOT = nsIAccessibleRole.ROLE_APP_ROOT;
+const ROLE_AUTOCOMPLETE = nsIAccessibleRole.ROLE_AUTOCOMPLETE;
+const ROLE_BLOCKQUOTE = nsIAccessibleRole.ROLE_BLOCKQUOTE;
+const ROLE_BUTTONDROPDOWNGRID = nsIAccessibleRole.ROLE_BUTTONDROPDOWNGRID;
+const ROLE_CANVAS = nsIAccessibleRole.ROLE_CANVAS;
+const ROLE_CAPTION = nsIAccessibleRole.ROLE_CAPTION;
+const ROLE_CELL = nsIAccessibleRole.ROLE_CELL;
+const ROLE_CHECKBUTTON = nsIAccessibleRole.ROLE_CHECKBUTTON;
+const ROLE_CHECK_MENU_ITEM = nsIAccessibleRole.ROLE_CHECK_MENU_ITEM;
+const ROLE_CHROME_WINDOW = nsIAccessibleRole.ROLE_CHROME_WINDOW;
+const ROLE_CODE = nsIAccessibleRole.ROLE_CODE;
+const ROLE_COLUMNHEADER = nsIAccessibleRole.ROLE_COLUMNHEADER;
+const ROLE_COMBOBOX = nsIAccessibleRole.ROLE_COMBOBOX;
+const ROLE_COMBOBOX_LIST = nsIAccessibleRole.ROLE_COMBOBOX_LIST;
+const ROLE_COMBOBOX_OPTION = nsIAccessibleRole.ROLE_COMBOBOX_OPTION;
+const ROLE_COMMENT = nsIAccessibleRole.ROLE_COMMENT;
+const ROLE_CONTENT_DELETION = nsIAccessibleRole.ROLE_CONTENT_DELETION;
+const ROLE_CONTENT_INSERTION = nsIAccessibleRole.ROLE_CONTENT_INSERTION;
+const ROLE_DATE_EDITOR = nsIAccessibleRole.ROLE_DATE_EDITOR;
+const ROLE_DEFINITION = nsIAccessibleRole.ROLE_DEFINITION;
+const ROLE_DEFINITION_LIST = nsIAccessibleRole.ROLE_DEFINITION_LIST;
+const ROLE_DETAILS = nsIAccessibleRole.ROLE_DETAILS;
+const ROLE_DIAGRAM = nsIAccessibleRole.ROLE_DIAGRAM;
+const ROLE_DIALOG = nsIAccessibleRole.ROLE_DIALOG;
+const ROLE_DOCUMENT = nsIAccessibleRole.ROLE_DOCUMENT;
+const ROLE_EDITCOMBOBOX = nsIAccessibleRole.ROLE_EDITCOMBOBOX;
+const ROLE_EMBEDDED_OBJECT = nsIAccessibleRole.ROLE_EMBEDDED_OBJECT;
+const ROLE_ENTRY = nsIAccessibleRole.ROLE_ENTRY;
+const ROLE_EQUATION = nsIAccessibleRole.ROLE_EQUATION;
+const ROLE_FIGURE = nsIAccessibleRole.ROLE_FIGURE;
+const ROLE_FOOTER = nsIAccessibleRole.ROLE_FOOTER;
+const ROLE_FOOTNOTE = nsIAccessibleRole.ROLE_FOOTNOTE;
+const ROLE_FLAT_EQUATION = nsIAccessibleRole.ROLE_FLAT_EQUATION;
+const ROLE_FORM = nsIAccessibleRole.ROLE_FORM;
+const ROLE_FORM_LANDMARK = nsIAccessibleRole.ROLE_FORM_LANDMARK;
+const ROLE_GRAPHIC = nsIAccessibleRole.ROLE_GRAPHIC;
+const ROLE_GRID_CELL = nsIAccessibleRole.ROLE_GRID_CELL;
+const ROLE_GROUPING = nsIAccessibleRole.ROLE_GROUPING;
+const ROLE_HEADER = nsIAccessibleRole.ROLE_HEADER;
+const ROLE_HEADING = nsIAccessibleRole.ROLE_HEADING;
+const ROLE_IMAGE_MAP = nsIAccessibleRole.ROLE_IMAGE_MAP;
+const ROLE_INTERNAL_FRAME = nsIAccessibleRole.ROLE_INTERNAL_FRAME;
+const ROLE_LABEL = nsIAccessibleRole.ROLE_LABEL;
+const ROLE_LANDMARK = nsIAccessibleRole.ROLE_LANDMARK;
+const ROLE_LINK = nsIAccessibleRole.ROLE_LINK;
+const ROLE_LIST = nsIAccessibleRole.ROLE_LIST;
+const ROLE_LISTBOX = nsIAccessibleRole.ROLE_LISTBOX;
+const ROLE_LISTITEM = nsIAccessibleRole.ROLE_LISTITEM;
+const ROLE_LISTITEM_MARKER = nsIAccessibleRole.ROLE_LISTITEM_MARKER;
+const ROLE_MARK = nsIAccessibleRole.ROLE_MARK;
+const ROLE_MATHML_MATH = nsIAccessibleRole.ROLE_MATHML_MATH;
+const ROLE_MATHML_IDENTIFIER = nsIAccessibleRole.ROLE_MATHML_IDENTIFIER;
+const ROLE_MATHML_NUMBER = nsIAccessibleRole.ROLE_MATHML_NUMBER;
+const ROLE_MATHML_OPERATOR = nsIAccessibleRole.ROLE_MATHML_OPERATOR;
+const ROLE_MATHML_TEXT = nsIAccessibleRole.ROLE_MATHML_TEXT;
+const ROLE_MATHML_STRING_LITERAL = nsIAccessibleRole.ROLE_MATHML_STRING_LITERAL;
+const ROLE_MATHML_GLYPH = nsIAccessibleRole.ROLE_MATHML_GLYPH;
+const ROLE_MATHML_ROW = nsIAccessibleRole.ROLE_MATHML_ROW;
+const ROLE_MATHML_FRACTION = nsIAccessibleRole.ROLE_MATHML_FRACTION;
+const ROLE_MATHML_SQUARE_ROOT = nsIAccessibleRole.ROLE_MATHML_SQUARE_ROOT;
+const ROLE_MATHML_ROOT = nsIAccessibleRole.ROLE_MATHML_ROOT;
+const ROLE_MATHML_FENCED = nsIAccessibleRole.ROLE_MATHML_FENCED;
+const ROLE_MATHML_ENCLOSED = nsIAccessibleRole.ROLE_MATHML_ENCLOSED;
+const ROLE_MATHML_STYLE = nsIAccessibleRole.ROLE_MATHML_STYLE;
+const ROLE_MATHML_SUB = nsIAccessibleRole.ROLE_MATHML_SUB;
+const ROLE_MATHML_SUP = nsIAccessibleRole.ROLE_MATHML_SUP;
+const ROLE_MATHML_SUB_SUP = nsIAccessibleRole.ROLE_MATHML_SUB_SUP;
+const ROLE_MATHML_UNDER = nsIAccessibleRole.ROLE_MATHML_UNDER;
+const ROLE_MATHML_OVER = nsIAccessibleRole.ROLE_MATHML_OVER;
+const ROLE_MATHML_UNDER_OVER = nsIAccessibleRole.ROLE_MATHML_UNDER_OVER;
+const ROLE_MATHML_MULTISCRIPTS = nsIAccessibleRole.ROLE_MATHML_MULTISCRIPTS;
+const ROLE_MATHML_TABLE = nsIAccessibleRole.ROLE_MATHML_TABLE;
+const ROLE_MATHML_LABELED_ROW = nsIAccessibleRole.ROLE_MATHML_LABELED_ROW;
+const ROLE_MATHML_TABLE_ROW = nsIAccessibleRole.ROLE_MATHML_TABLE_ROW;
+const ROLE_MATHML_CELL = nsIAccessibleRole.ROLE_MATHML_CELL;
+const ROLE_MATHML_ACTION = nsIAccessibleRole.ROLE_MATHML_ACTION;
+const ROLE_MATHML_ERROR = nsIAccessibleRole.ROLE_MATHML_ERROR;
+const ROLE_MATHML_STACK = nsIAccessibleRole.ROLE_MATHML_STACK;
+const ROLE_MATHML_LONG_DIVISION = nsIAccessibleRole.ROLE_MATHML_LONG_DIVISION;
+const ROLE_MATHML_STACK_GROUP = nsIAccessibleRole.ROLE_MATHML_STACK_GROUP;
+const ROLE_MATHML_STACK_ROW = nsIAccessibleRole.ROLE_MATHML_STACK_ROW;
+const ROLE_MATHML_STACK_CARRIES = nsIAccessibleRole.ROLE_MATHML_STACK_CARRIES;
+const ROLE_MATHML_STACK_CARRY = nsIAccessibleRole.ROLE_MATHML_STACK_CARRY;
+const ROLE_MATHML_STACK_LINE = nsIAccessibleRole.ROLE_MATHML_STACK_LINE;
+const ROLE_MENUBAR = nsIAccessibleRole.ROLE_MENUBAR;
+const ROLE_MENUITEM = nsIAccessibleRole.ROLE_MENUITEM;
+const ROLE_MENUPOPUP = nsIAccessibleRole.ROLE_MENUPOPUP;
+const ROLE_METER = nsIAccessibleRole.ROLE_METER;
+const ROLE_NAVIGATION = nsIAccessibleRole.ROLE_NAVIGATION;
+const ROLE_NON_NATIVE_DOCUMENT = nsIAccessibleRole.ROLE_NON_NATIVE_DOCUMENT;
+const ROLE_NOTHING = nsIAccessibleRole.ROLE_NOTHING;
+const ROLE_NOTE = nsIAccessibleRole.ROLE_NOTE;
+const ROLE_OPTION = nsIAccessibleRole.ROLE_OPTION;
+const ROLE_OUTLINE = nsIAccessibleRole.ROLE_OUTLINE;
+const ROLE_OUTLINEITEM = nsIAccessibleRole.ROLE_OUTLINEITEM;
+const ROLE_PAGETAB = nsIAccessibleRole.ROLE_PAGETAB;
+const ROLE_PAGETABLIST = nsIAccessibleRole.ROLE_PAGETABLIST;
+const ROLE_PANE = nsIAccessibleRole.ROLE_PANE;
+const ROLE_PARAGRAPH = nsIAccessibleRole.ROLE_PARAGRAPH;
+const ROLE_PARENT_MENUITEM = nsIAccessibleRole.ROLE_PARENT_MENUITEM;
+const ROLE_PASSWORD_TEXT = nsIAccessibleRole.ROLE_PASSWORD_TEXT;
+const ROLE_PROGRESSBAR = nsIAccessibleRole.ROLE_PROGRESSBAR;
+const ROLE_PROPERTYPAGE = nsIAccessibleRole.ROLE_PROPERTYPAGE;
+const ROLE_PUSHBUTTON = nsIAccessibleRole.ROLE_PUSHBUTTON;
+const ROLE_RADIOBUTTON = nsIAccessibleRole.ROLE_RADIOBUTTON;
+const ROLE_RADIO_GROUP = nsIAccessibleRole.ROLE_RADIO_GROUP;
+const ROLE_RADIO_MENU_ITEM = nsIAccessibleRole.ROLE_RADIO_MENU_ITEM;
+const ROLE_REGION = nsIAccessibleRole.ROLE_REGION;
+const ROLE_RICH_OPTION = nsIAccessibleRole.ROLE_RICH_OPTION;
+const ROLE_ROW = nsIAccessibleRole.ROLE_ROW;
+const ROLE_ROWHEADER = nsIAccessibleRole.ROLE_ROWHEADER;
+const ROLE_SCROLLBAR = nsIAccessibleRole.ROLE_SCROLLBAR;
+const ROLE_SECTION = nsIAccessibleRole.ROLE_SECTION;
+const ROLE_SEPARATOR = nsIAccessibleRole.ROLE_SEPARATOR;
+const ROLE_SLIDER = nsIAccessibleRole.ROLE_SLIDER;
+const ROLE_SPINBUTTON = nsIAccessibleRole.ROLE_SPINBUTTON;
+const ROLE_STATICTEXT = nsIAccessibleRole.ROLE_STATICTEXT;
+const ROLE_STATUSBAR = nsIAccessibleRole.ROLE_STATUSBAR;
+const ROLE_SUBSCRIPT = nsIAccessibleRole.ROLE_SUBSCRIPT;
+const ROLE_SUGGESTION = nsIAccessibleRole.ROLE_SUGGESTION;
+const ROLE_SUPERSCRIPT = nsIAccessibleRole.ROLE_SUPERSCRIPT;
+const ROLE_SUMMARY = nsIAccessibleRole.ROLE_SUMMARY;
+const ROLE_SWITCH = nsIAccessibleRole.ROLE_SWITCH;
+const ROLE_TABLE = nsIAccessibleRole.ROLE_TABLE;
+const ROLE_TERM = nsIAccessibleRole.ROLE_TERM;
+const ROLE_TEXT = nsIAccessibleRole.ROLE_TEXT;
+const ROLE_TEXT_CONTAINER = nsIAccessibleRole.ROLE_TEXT_CONTAINER;
+const ROLE_TEXT_LEAF = nsIAccessibleRole.ROLE_TEXT_LEAF;
+const ROLE_TIME_EDITOR = nsIAccessibleRole.ROLE_TIME_EDITOR;
+const ROLE_TOGGLE_BUTTON = nsIAccessibleRole.ROLE_TOGGLE_BUTTON;
+const ROLE_TOOLBAR = nsIAccessibleRole.ROLE_TOOLBAR;
+const ROLE_TOOLTIP = nsIAccessibleRole.ROLE_TOOLTIP;
+const ROLE_TREE_TABLE = nsIAccessibleRole.ROLE_TREE_TABLE;
+const ROLE_WHITESPACE = nsIAccessibleRole.ROLE_WHITESPACE;
+
+// //////////////////////////////////////////////////////////////////////////////
+// Public methods
+
+/**
+ * Test that the role of the given accessible is the role passed in.
+ *
+ * @param aAccOrElmOrID the accessible, DOM element or ID to be tested.
+ * @param aRole The role that is to be expected.
+ */
+function testRole(aAccOrElmOrID, aRole) {
+ var role = getRole(aAccOrElmOrID);
+ is(role, aRole, "Wrong role for " + prettyName(aAccOrElmOrID) + "!");
+}
+
+/**
+ * Return the role of the given accessible. Return -1 if accessible could not
+ * be retrieved.
+ *
+ * @param aAccOrElmOrID [in] The accessible, DOM element or element ID the
+ * accessible role is being requested for.
+ */
+function getRole(aAccOrElmOrID) {
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc) {
+ return -1;
+ }
+
+ var role = -1;
+ try {
+ role = acc.role;
+ } catch (e) {
+ ok(false, "Role for " + aAccOrElmOrID + " could not be retrieved!");
+ }
+
+ return role;
+}
+
+/**
+ * Analogy of SimpleTest.is function used to check the role.
+ */
+function isRole(aIdentifier, aRole, aMsg) {
+ var role = getRole(aIdentifier);
+ if (role == -1) {
+ return;
+ }
+
+ if (role == aRole) {
+ ok(true, aMsg);
+ return;
+ }
+
+ var got = roleToString(role);
+ var expected = roleToString(aRole);
+
+ ok(false, aMsg + "got '" + got + "', expected '" + expected + "'");
+}
diff --git a/accessible/tests/mochitest/role/a11y.ini b/accessible/tests/mochitest/role/a11y.ini
new file mode 100644
index 0000000000..02a2b61cb1
--- /dev/null
+++ b/accessible/tests/mochitest/role/a11y.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+support-files =
+ chrome_body_role_alert.xhtml
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/moz.png
+
+[test_aria.html]
+[test_aria.xhtml]
+[test_dpub_aria.html]
+[test_general.html]
+[test_general.xhtml]
+[test_graphics_aria.html]
+[test_svg.html]
diff --git a/accessible/tests/mochitest/role/chrome_body_role_alert.xhtml b/accessible/tests/mochitest/role/chrome_body_role_alert.xhtml
new file mode 100644
index 0000000000..29ff10fb01
--- /dev/null
+++ b/accessible/tests/mochitest/role/chrome_body_role_alert.xhtml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body role="alert">
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/role/test_aria.html b/accessible/tests/mochitest/role/test_aria.html
new file mode 100644
index 0000000000..bd7ffd27fb
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_aria.html
@@ -0,0 +1,729 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test weak ARIA roles</title>
+
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+
+ // To test initial roles on body elements, we need to use an iframe.
+ function testBodyRole(iframeId, role) {
+ let iframe = getNode(iframeId);
+ let doc = iframe.contentDocument;
+ let docAcc = getAccessible(doc);
+ testRole(docAcc, role);
+ }
+
+ async function doTest() {
+ // ARIA role map.
+ testRole("aria_alert", ROLE_ALERT);
+ testRole("aria_alert_mixed", ROLE_ALERT);
+ testRole("aria_alertdialog", ROLE_DIALOG);
+ testRole("aria_alertdialog_mixed", ROLE_DIALOG);
+ testRole("aria_application", ROLE_APPLICATION);
+ testRole("aria_application_mixed", ROLE_APPLICATION);
+ testRole("aria_article", ROLE_ARTICLE);
+ testRole("aria_article_mixed", ROLE_ARTICLE);
+ testRole("aria_blockquote", ROLE_BLOCKQUOTE);
+ testRole("aria_blockquote_mixed", ROLE_BLOCKQUOTE);
+ testRole("aria_button", ROLE_PUSHBUTTON);
+ testRole("aria_button_mixed", ROLE_PUSHBUTTON);
+ testRole("aria_caption", ROLE_CAPTION);
+ testRole("aria_caption_mixed", ROLE_CAPTION);
+ testRole("aria_checkbox", ROLE_CHECKBUTTON);
+ testRole("aria_checkbox_mixed", ROLE_CHECKBUTTON);
+ testRole("aria_code", ROLE_CODE);
+ testRole("aria_code_mixed", ROLE_CODE);
+ testRole("aria_columnheader", ROLE_COLUMNHEADER);
+ testRole("aria_columnheader_mixed", ROLE_COLUMNHEADER);
+ testRole("aria_combobox", ROLE_EDITCOMBOBOX);
+ testRole("aria_combobox_mixed", ROLE_EDITCOMBOBOX);
+ testRole("aria_comment", ROLE_COMMENT);
+ testRole("aria_comment_mixed", ROLE_COMMENT);
+ testRole("aria_deletion", ROLE_CONTENT_DELETION);
+ testRole("aria_deletion_mixed", ROLE_CONTENT_DELETION);
+ testRole("aria_dialog", ROLE_DIALOG);
+ testRole("aria_dialog_mixed", ROLE_DIALOG);
+ testRole("aria_directory", ROLE_LIST);
+ testRole("aria_directory_mixed", ROLE_LIST);
+ testRole("aria_document", ROLE_NON_NATIVE_DOCUMENT);
+ testRole("aria_document_mixed", ROLE_NON_NATIVE_DOCUMENT);
+ testRole("aria_form", ROLE_FORM);
+ testRole("aria_form_mixed", ROLE_FORM);
+ testRole("aria_form_with_label", ROLE_FORM);
+ testRole("aria_form_with_label_mixed", ROLE_FORM);
+ testRole("aria_feed", ROLE_GROUPING);
+ testRole("aria_feed_mixed", ROLE_GROUPING);
+ testRole("aria_figure", ROLE_FIGURE);
+ testRole("aria_figure_mixed", ROLE_FIGURE);
+ testRole("aria_grid", ROLE_TABLE);
+ testRole("aria_grid_mixed", ROLE_TABLE);
+ testRole("aria_gridcell", ROLE_GRID_CELL);
+ testRole("aria_gridcell_mixed", ROLE_GRID_CELL);
+ testRole("aria_group", ROLE_GROUPING);
+ testRole("aria_group_mixed", ROLE_GROUPING);
+ testRole("aria_heading", ROLE_HEADING);
+ testRole("aria_heading_mixed", ROLE_HEADING);
+ testRole("aria_img", ROLE_GRAPHIC);
+ testRole("aria_img_mixed", ROLE_GRAPHIC);
+ testRole("aria_insertion", ROLE_CONTENT_INSERTION);
+ testRole("aria_insertion_mixed", ROLE_CONTENT_INSERTION);
+ testRole("aria_link", ROLE_LINK);
+ testRole("aria_link_mixed", ROLE_LINK);
+ testRole("aria_list", ROLE_LIST);
+ testRole("aria_list_mixed", ROLE_LIST);
+ testRole("aria_listbox", ROLE_LISTBOX);
+ testRole("aria_listbox_mixed", ROLE_LISTBOX);
+ testRole("aria_listitem", ROLE_LISTITEM);
+ testRole("aria_listitem_mixed", ROLE_LISTITEM);
+ testRole("aria_log", ROLE_TEXT); // weak role
+ testRole("aria_log_mixed", ROLE_TEXT); // weak role
+ testRole("aria_mark", ROLE_MARK);
+ testRole("aria_mark_mixed", ROLE_MARK);
+ testRole("aria_marquee", ROLE_ANIMATION);
+ testRole("aria_marquee_mixed", ROLE_ANIMATION);
+ testRole("aria_math", ROLE_FLAT_EQUATION);
+ testRole("aria_math_mixed", ROLE_FLAT_EQUATION);
+ testRole("aria_menu", ROLE_MENUPOPUP);
+ testRole("aria_menu_mixed", ROLE_MENUPOPUP);
+ testRole("aria_menubar", ROLE_MENUBAR);
+ testRole("aria_menubar_mixed", ROLE_MENUBAR);
+ testRole("aria_menuitem", ROLE_MENUITEM);
+ testRole("aria_menuitem_mixed", ROLE_MENUITEM);
+ testRole("aria_menuitemcheckbox", ROLE_CHECK_MENU_ITEM);
+ testRole("aria_menuitemcheckbox_mixed", ROLE_CHECK_MENU_ITEM);
+ testRole("aria_menuitemradio", ROLE_RADIO_MENU_ITEM);
+ testRole("aria_menuitemradio_mixed", ROLE_RADIO_MENU_ITEM);
+ testRole("aria_meter", ROLE_METER);
+ testRole("aria_meter_mixed", ROLE_METER);
+ testRole("aria_note", ROLE_NOTE);
+ testRole("aria_note_mixed", ROLE_NOTE);
+ testRole("aria_paragraph", ROLE_PARAGRAPH);
+ testRole("aria_paragraph_mixed", ROLE_PARAGRAPH);
+ testRole("aria_presentation", ROLE_TEXT); // weak role
+ testRole("aria_presentation_mixed", ROLE_TEXT); // weak role
+ testRole("aria_progressbar", ROLE_PROGRESSBAR);
+ testRole("aria_progressbar_mixed", ROLE_PROGRESSBAR);
+ testRole("aria_radio", ROLE_RADIOBUTTON);
+ testRole("aria_radio_mixed", ROLE_RADIOBUTTON);
+ testRole("aria_radiogroup", ROLE_RADIO_GROUP);
+ testRole("aria_radiogroup_mixed", ROLE_RADIO_GROUP);
+ testRole("aria_region_no_name", ROLE_TEXT);
+ testRole("aria_region_no_name_mixed", ROLE_TEXT);
+ testRole("aria_region_has_label", ROLE_REGION);
+ testRole("aria_region_has_label_mixed", ROLE_REGION);
+ testRole("aria_region_has_labelledby", ROLE_REGION);
+ testRole("aria_region_has_labelledby_mixed", ROLE_REGION);
+ testRole("aria_region_has_title", ROLE_REGION);
+ testRole("aria_region_has_title_mixed", ROLE_REGION);
+ testRole("aria_region_empty_name", ROLE_TEXT);
+ testRole("aria_region_empty_name_mixed", ROLE_TEXT);
+ testRole("aria_region_as_table_with_caption", ROLE_REGION);
+ testRole("aria_region_as_table_with_caption_mixed", ROLE_REGION);
+ testRole("aria_region_as_table_with_miscaption", ROLE_TABLE);
+ testRole("aria_region_as_table_with_miscaption_mixed", ROLE_TABLE);
+ testRole("aria_row", ROLE_ROW);
+ testRole("aria_row_mixed", ROLE_ROW);
+ testRole("aria_rowheader", ROLE_ROWHEADER);
+ testRole("aria_rowheader_mixed", ROLE_ROWHEADER);
+ testRole("aria_scrollbar", ROLE_SCROLLBAR);
+ testRole("aria_scrollbar_mixed", ROLE_SCROLLBAR);
+ testRole("aria_searchbox", ROLE_ENTRY);
+ testRole("aria_searchbox_mixed", ROLE_ENTRY);
+ testRole("aria_separator", ROLE_SEPARATOR);
+ testRole("aria_separator_mixed", ROLE_SEPARATOR);
+ testRole("aria_slider", ROLE_SLIDER);
+ testRole("aria_slider_mixed", ROLE_SLIDER);
+ testRole("aria_spinbutton", ROLE_SPINBUTTON);
+ testRole("aria_spinbutton_mixed", ROLE_SPINBUTTON);
+ testRole("aria_status", ROLE_STATUSBAR);
+ testRole("aria_status_mixed", ROLE_STATUSBAR);
+ testRole("aria_subscript", ROLE_SUBSCRIPT);
+ testRole("aria_subscript_mixed", ROLE_SUBSCRIPT);
+ testRole("aria_suggestion", ROLE_SUGGESTION);
+ testRole("aria_suggestion_mixed", ROLE_SUGGESTION);
+ testRole("aria_superscript", ROLE_SUPERSCRIPT);
+ testRole("aria_superscript_mixed", ROLE_SUPERSCRIPT);
+ testRole("aria_switch", ROLE_SWITCH);
+ testRole("aria_switch_mixed", ROLE_SWITCH);
+ testRole("aria_tab", ROLE_PAGETAB);
+ testRole("aria_tab_mixed", ROLE_PAGETAB);
+ testRole("aria_tablist", ROLE_PAGETABLIST);
+ testRole("aria_tablist_mixed", ROLE_PAGETABLIST);
+ testRole("aria_tabpanel", ROLE_PROPERTYPAGE);
+ testRole("aria_tabpanel_mixed", ROLE_PROPERTYPAGE);
+ testRole("aria_term", ROLE_TERM);
+ testRole("aria_term_mixed", ROLE_TERM);
+ testRole("aria_textbox", ROLE_ENTRY);
+ testRole("aria_textbox_mixed", ROLE_ENTRY);
+ testRole("aria_timer", ROLE_TEXT); // weak role
+ testRole("aria_timer_mixed", ROLE_TEXT); // weak role
+ testRole("aria_toolbar", ROLE_TOOLBAR);
+ testRole("aria_toolbar_mixed", ROLE_TOOLBAR);
+ testRole("aria_tooltip", ROLE_TOOLTIP);
+ testRole("aria_tooltip_mixed", ROLE_TOOLTIP);
+ testRole("aria_tree", ROLE_OUTLINE);
+ testRole("aria_tree_mixed", ROLE_OUTLINE);
+ testRole("aria_treegrid", ROLE_TREE_TABLE);
+ testRole("aria_treegrid_mixed", ROLE_TREE_TABLE);
+ testRole("aria_treeitem", ROLE_OUTLINEITEM);
+ testRole("aria_treeitem_mixed", ROLE_OUTLINEITEM);
+
+ // Note:
+ // The phrase "weak foo" here means that there is no good foo-to-platform
+ // role mapping. Similarly "strong foo" means there is a good foo-to-
+ // platform role mapping.
+
+ testRole("articlemain", ROLE_LANDMARK);
+ testRole("articlemain_mixed", ROLE_LANDMARK);
+ testRole("articleform", ROLE_FORM);
+ testRole("articleform_mixed", ROLE_FORM);
+
+ // Test article exposed as article
+ testRole("testArticle", ROLE_ARTICLE);
+ testRole("testArticle_mixed", ROLE_ARTICLE);
+
+ // weak roles that are forms of "live regions"
+ testRole("log_table", ROLE_TABLE);
+ testRole("log_table_mixed", ROLE_TABLE);
+ testRole("timer_div", ROLE_SECTION);
+ testRole("timer_div_mixed", ROLE_SECTION);
+
+ // other roles that are forms of "live regions"
+ testRole("marquee_h1", ROLE_ANIMATION);
+ testRole("marquee_h1_mixed", ROLE_ANIMATION);
+
+ // strong landmark
+ testRole("application", ROLE_APPLICATION);
+ testRole("application_mixed", ROLE_APPLICATION);
+ testRole("form", ROLE_FORM);
+ testRole("form_mixed", ROLE_FORM);
+ testRole("application_table", ROLE_APPLICATION);
+ testRole("application_table_mixed", ROLE_APPLICATION);
+
+ // landmarks
+ let landmarks = ["banner", "complementary", "contentinfo",
+ "main", "navigation", "search"];
+ for (const l in landmarks) {
+ testRole(landmarks[l], ROLE_LANDMARK);
+ testRole(landmarks[l] + "_mixed", ROLE_LANDMARK);
+ }
+
+ for (const l in landmarks) {
+ let id = landmarks[l] + "_table";
+ testRole(id, ROLE_LANDMARK);
+ testRole(id + "_mixed", ROLE_LANDMARK);
+
+ let accessibleTable = getAccessible(id, [nsIAccessibleTable], null,
+ DONOTFAIL_IF_NO_INTERFACE);
+ ok(!!accessibleTable, "landmarked table should have nsIAccessibleTable");
+
+ accessibleTable = getAccessible(id+"_mixed", [nsIAccessibleTable], null,
+ DONOTFAIL_IF_NO_INTERFACE);
+ ok(!!accessibleTable, "Uppercase landmarked table should have nsIAccessibleTable");
+
+ if (accessibleTable)
+ is(accessibleTable.getCellAt(0, 0).firstChild.name, "hi", "no cell");
+ }
+
+ // ////////////////////////////////////////////////////////////////////////
+ // test gEmptyRoleMap
+ testRole("buttontable_row", ROLE_TEXT_CONTAINER);
+ testRole("buttontable_row_mixed", ROLE_TEXT_CONTAINER);
+ testRole("buttontable_cell", ROLE_TEXT_CONTAINER);
+ testRole("buttontable_cell_mixed", ROLE_TEXT_CONTAINER);
+
+ // abstract roles
+ var abstract_roles = ["composite", "landmark", "structure", "widget",
+ "window", "input", "range", "select", "section",
+ "sectionhead"];
+ for (const a in abstract_roles) {
+ testRole(abstract_roles[a], ROLE_SECTION);
+ testRole(abstract_roles[a]+ "_mixed", ROLE_SECTION);
+ }
+
+ // ////////////////////////////////////////////////////////////////////////
+ // roles transformed by ARIA state attributes
+ testRole("togglebutton", ROLE_TOGGLE_BUTTON);
+ testRole("togglebutton_mixed", ROLE_TOGGLE_BUTTON);
+ testRole("implicit_gridcell", ROLE_GRID_CELL);
+ testRole("implicit_gridcell_mixed", ROLE_GRID_CELL);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ignore unknown roles, take first known
+ testRole("unknown_roles", ROLE_PUSHBUTTON);
+ testRole("unknown_roles_mixed", ROLE_PUSHBUTTON);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // misc roles
+ testRole("note", ROLE_NOTE);
+ testRole("note_mixed", ROLE_NOTE);
+ testRole("scrollbar", ROLE_SCROLLBAR);
+ testRole("scrollbar_mixed", ROLE_SCROLLBAR);
+ testRole("dir", ROLE_LIST);
+ testRole("dir_mixed", ROLE_LIST);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // test document role map update
+ var testDoc = getAccessible(document, [nsIAccessibleDocument]);
+ testRole(testDoc, ROLE_DOCUMENT);
+ document.body.setAttribute("role", "application");
+ testRole(testDoc, ROLE_APPLICATION);
+ document.body.setAttribute("role", "APPLICATION");
+ testRole(testDoc, ROLE_APPLICATION);
+ document.body.setAttribute("role", "dialog");
+ testRole(testDoc, ROLE_DIALOG);
+ document.body.setAttribute("role", "DIALOG");
+ testRole(testDoc, ROLE_DIALOG);
+ // Other roles aren't valid on body elements.
+ document.body.setAttribute("role", "document");
+ testRole(testDoc, ROLE_DOCUMENT);
+ document.body.setAttribute("role", "DOCUMENT");
+ testRole(testDoc, ROLE_DOCUMENT);
+ document.body.setAttribute("role", "button");
+ testRole(testDoc, ROLE_DOCUMENT);
+ document.body.setAttribute("role", "BUTTON");
+ testRole(testDoc, ROLE_DOCUMENT);
+ document.body.setAttribute("role", "main");
+ testRole(testDoc, ROLE_DOCUMENT);
+ document.body.setAttribute("role", "MAIN");
+ testRole(testDoc, ROLE_DOCUMENT);
+
+ // Test equation image
+ testRole("img_eq", ROLE_FLAT_EQUATION);
+ testRole("img_eq_mixed", ROLE_FLAT_EQUATION);
+
+ // Test textual equation
+ testRole("txt_eq", ROLE_FLAT_EQUATION);
+ testRole("txt_eq_mixed", ROLE_FLAT_EQUATION);
+
+ // Test initial ARIA roles on the body element.
+ testBodyRole("iframe_aria_application", ROLE_APPLICATION);
+ testBodyRole("iframe_aria_application_mixed", ROLE_APPLICATION);
+ testBodyRole("iframe_aria_dialog", ROLE_DIALOG);
+ testBodyRole("iframe_aria_dialog_mixed", ROLE_DIALOG);
+ // role="alert" is valid on the body of chrome documents.
+ let win = Services.ww.openWindow(
+ null,
+ "chrome://mochitests/content/a11y/accessible/tests/mochitest/role/chrome_body_role_alert.xhtml",
+ "_blank",
+ "chrome",
+ []
+ );
+ await new Promise(resolve => addA11yLoadEvent(resolve, win));
+ testRole(win.document, ROLE_ALERT);
+ win.close();
+ // Other roles aren't valid on body elements.
+ testBodyRole("iframe_aria_document", ROLE_DOCUMENT);
+ testBodyRole("iframe_aria_document_mixed", ROLE_DOCUMENT);
+ testBodyRole("iframe_aria_button", ROLE_DOCUMENT);
+ testBodyRole("iframe_aria_button_mixed", ROLE_DOCUMENT);
+ testBodyRole("iframe_aria_main", ROLE_DOCUMENT);
+ testBodyRole("iframe_aria_main_mixed", ROLE_DOCUMENT);
+ // role="alert" is not valid on the body of content documents.
+ testBodyRole("iframe_aria_alert", ROLE_DOCUMENT);
+ testBodyRole("iframe_aria_alert_mixed", ROLE_DOCUMENT);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=428479">Mozilla Bug 428479</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=429666">Mozilla Bug 429666</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=481114">Mozilla Bug 481114</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469688">Mozilla Bug 469688</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469688">Mozilla Bug 520188</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=529289">Mozilla Bug 529289</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=529289">Mozilla Bug 607219</a>
+ <a target="_blank"
+ title="HTML buttons with aria-pressed not exposing IA2 TOGGLE_BUTTON role"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=725432">
+ Bug 725432
+ </a>
+ <a target="_blank"
+ title="Map ARIA role FORM"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=735645">
+ Bug 735645
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563"
+ title="Support ARIA 1.1 switch role">
+ Bug 1136563
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518"
+ title="Support ARIA 1.1 searchbox role">
+ Bug 1121518
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1356049"
+ title="Map ARIA figure role">
+ Bug 1356049
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <span id="aria_alert" role="alert"></span>
+ <span id="aria_alert_mixed" role="aLERt"></span>
+ <span id="aria_alertdialog" role="alertdialog"></span>
+ <span id="aria_alertdialog_mixed" role="aLERTDIALOg"></span>
+ <span id="aria_application" role="application"></span>
+ <span id="aria_application_mixed" role="aPPLICATIOn"></span>
+ <span id="aria_article" role="article"></span>
+ <span id="aria_article_mixed" role="aRTICLe"></span>
+ <span id="aria_blockquote" role="blockquote"></span>
+ <span id="aria_blockquote_mixed" role="bLOCKQUOTe"></span>
+ <span id="aria_button" role="button"></span>
+ <span id="aria_button_mixed" role="bUTTOn"></span>
+ <span id="aria_caption" role="caption"></span>
+ <span id="aria_caption_mixed" role="cAPTIOn"></span>
+ <span id="aria_checkbox" role="checkbox"></span>
+ <span id="aria_checkbox_mixed" role="cHECKBOx"></span>
+ <span id="aria_code" role="code"></span>
+ <span id="aria_code_mixed" role="cODe"></span>
+ <span id="aria_columnheader" role="columnheader"></span>
+ <span id="aria_columnheader_mixed" role="cOLUMNHEADEr"></span>
+ <span id="aria_combobox" role="combobox"></span>
+ <span id="aria_combobox_mixed" role="cOMBOBOx"></span>
+ <span id="aria_comment" role="comment"></span>
+ <span id="aria_comment_mixed" role="cOMMENt"></span>
+ <span id="aria_deletion" role="deletion"></span>
+ <span id="aria_deletion_mixed" role="dELETIOn"></span>
+ <span id="aria_dialog" role="dialog"></span>
+ <span id="aria_dialog_mixed" role="dIALOg"></span>
+ <span id="aria_directory" role="directory"></span>
+ <span id="aria_directory_mixed" role="dIRECTORy"></span>
+ <span id="aria_document" role="document"></span>
+ <span id="aria_document_mixed" role="dOCUMENt"></span>
+ <span id="aria_form" role="form"></span>
+ <span id="aria_form_mixed" role="fORm"></span>
+ <span id="aria_form_with_label" role="form" aria-label="Label"></span>
+ <span id="aria_form_with_label_mixed" role="fORm" aria-label="Label"></span>
+ <span id="aria_feed" role="feed"></span>
+ <span id="aria_feed_mixed" role="fEEd"></span>
+ <span id="aria_figure" role="figure"></span>
+ <span id="aria_figure_mixed" role="fIGURe"></span>
+ <span id="aria_grid" role="grid"></span>
+ <span id="aria_grid_mixed" role="gRId"></span>
+ <span id="aria_gridcell" role="gridcell"></span>
+ <span id="aria_gridcell_mixed" role="gRIDCELl"></span>
+ <span id="aria_group" role="group"></span>
+ <span id="aria_group_mixed" role="gROUp"></span>
+ <span id="aria_heading" role="heading"></span>
+ <span id="aria_heading_mixed" role="hEADINg"></span>
+ <span id="aria_img" role="img"></span>
+ <span id="aria_img_mixed" role="iMg"></span>
+ <span id="aria_insertion" role="insertion"></span>
+ <span id="aria_insertion_mixed" role="iNSERTIOn"></span>
+ <span id="aria_link" role="link"></span>
+ <span id="aria_link_mixed" role="lINk"></span>
+ <span id="aria_list" role="list"></span>
+ <span id="aria_list_mixed" role="lISt"></span>
+ <span id="aria_listbox" role="listbox"></span>
+ <span id="aria_listbox_mixed" role="lISTBOx"></span>
+ <span id="aria_listitem" role="listitem"></span>
+ <span id="aria_listitem_mixed" role="lISTITEm"></span>
+ <span id="aria_log" role="log"></span>
+ <span id="aria_log_mixed" role="lOg"></span>
+ <span id="aria_mark" role="mark"></span>
+ <span id="aria_mark_mixed" role="mARk"></span>
+ <span id="aria_marquee" role="marquee"></span>
+ <span id="aria_marquee_mixed" role="mARQUEe"></span>
+ <span id="aria_math" role="math"></span>
+ <span id="aria_math_mixed" role="mATh"></span>
+ <span id="aria_menu" role="menu"></span>
+ <span id="aria_menu_mixed" role="mENu"></span>
+ <span id="aria_menubar" role="menubar"></span>
+ <span id="aria_menubar_mixed" role="mENUBAr"></span>
+ <span id="aria_menuitem" role="menuitem"></span>
+ <span id="aria_menuitem_mixed" role="mENUITEm"></span>
+ <span id="aria_menuitemcheckbox" role="menuitemcheckbox"></span>
+ <span id="aria_menuitemcheckbox_mixed" role="mENUITEMCHECKBOx"></span>
+ <span id="aria_menuitemradio" role="menuitemradio"></span>
+ <span id="aria_menuitemradio_mixed" role="mENUITEMRADIo"></span>
+ <span id="aria_meter" role="meter"></span>
+ <span id="aria_meter_mixed" role="meTer"></span>
+ <span id="aria_note" role="note"></span>
+ <span id="aria_note_mixed" role="nOTe"></span>
+ <span id="aria_paragraph" role="paragraph"></span>
+ <span id="aria_paragraph_mixed" role="pARAGRAPh"></span>
+ <span id="aria_presentation" role="presentation" tabindex="0"></span>
+ <span id="aria_presentation_mixed" role="pRESENTATIOn" tabindex="0"></span>
+ <span id="aria_progressbar" role="progressbar"></span>
+ <span id="aria_progressbar_mixed" role="pROGRESSBAr"></span>
+ <span id="aria_radio" role="radio"></span>
+ <span id="aria_radio_mixed" role="rADIo"></span>
+ <span id="aria_radiogroup" role="radiogroup"></span>
+ <span id="aria_radiogroup_mixed" role="rADIOGROUp"></span>
+ <span id="aria_region_no_name" role="region"></span>
+ <span id="aria_region_no_name_mixed" role="rEGIOn"></span>
+ <span id="aria_region_has_label" role="region" aria-label="label"></span>
+ <span id="aria_region_has_label_mixed" role="rEGIOn" aria-label="label"></span>
+ <span id="aria_region_has_labelledby" role="region" aria-labelledby="label"><span id="label" aria-label="label"></span>
+ <span id="aria_region_has_labelledby_mixed" role="rEGIOn" aria-labelledby="label"><span id="label" aria-label="label"></span>
+ <span id="aria_region_has_title" role="region" title="title"></span>
+ <span id="aria_region_has_title_mixed" role="rEGIOn" title="title"></span>
+ <span id="aria_region_empty_name" role="region" aria-label="" title="" aria-labelledby="empty"></span><span id="empty"></span>
+ <span id="aria_region_empty_name_mixed" role="rEGIOn" aria-label="" title="" aria-labelledby="empty"></span><span id="empty"></span>
+ <table id="aria_region_as_table_with_caption" role="region"><caption>hello</caption></table>
+ <table id="aria_region_as_table_with_caption_mixed" role="rEGIOn"><caption>hello</caption></table>
+ <table id="aria_region_as_table_with_miscaption" role="region"><caption role="option">hello</caption></table>
+ <table id="aria_region_as_table_with_miscaption_mixed" role="rEGIOn"><caption role="option">hello</caption></table>
+ <span id="aria_row" role="row"></span>
+ <span id="aria_row_mixed" role="rOw"></span>
+ <span id="aria_rowheader" role="rowheader"></span>
+ <span id="aria_rowheader_mixed" role="rOWHEADEr"></span>
+ <span id="aria_scrollbar" role="scrollbar"></span>
+ <span id="aria_scrollbar_mixed" role="sCROLLBAr"></span>
+ <span id="aria_searchbox" role="textbox"></span>
+ <span id="aria_searchbox_mixed" role="tEXTBOx"></span>
+ <span id="aria_separator" role="separator"></span>
+ <span id="aria_separator_mixed" role="sEPARATOr"></span>
+ <span id="aria_slider" role="slider"></span>
+ <span id="aria_slider_mixed" role="sLIDEr"></span>
+ <span id="aria_spinbutton" role="spinbutton"></span>
+ <span id="aria_spinbutton_mixed" role="sPINBUTTOn"></span>
+ <span id="aria_status" role="status"></span>
+ <span id="aria_status_mixed" role="sTATUs"></span>
+ <span id="aria_subscript" role="subscript"></span>
+ <span id="aria_subscript_mixed" role="sUBSCRIPt"></span>
+ <span id="aria_suggestion" role="suggestion"></span>
+ <span id="aria_suggestion_mixed" role="sUGGESTIOn"></span>
+ <span id="aria_superscript" role="superscript"></span>
+ <span id="aria_superscript_mixed" role="sUPERSCRIPt"></span>
+ <span id="aria_switch" role="switch"></span>
+ <span id="aria_switch_mixed" role="sWITCh"></span>
+ <span id="aria_tab" role="tab"></span>
+ <span id="aria_tab_mixed" role="tAb"></span>
+ <span id="aria_tablist" role="tablist"></span>
+ <span id="aria_tablist_mixed" role="tABLISt"></span>
+ <span id="aria_tabpanel" role="tabpanel"></span>
+ <span id="aria_tabpanel_mixed" role="tABPANEl"></span>
+ <span id="aria_term" role="term"></span>
+ <span id="aria_term_mixed" role="tERm"></span>
+ <span id="aria_textbox" role="textbox"></span>
+ <span id="aria_textbox_mixed" role="tEXTBOx"></span>
+ <span id="aria_timer" role="timer"></span>
+ <span id="aria_timer_mixed" role="tIMEr"></span>
+ <span id="aria_toolbar" role="toolbar"></span>
+ <span id="aria_toolbar_mixed" role="tOOLBAr"></span>
+ <span id="aria_tooltip" role="tooltip"></span>
+ <span id="aria_tooltip_mixed" role="tOOLTIp"></span>
+ <span id="aria_tree" role="tree"></span>
+ <span id="aria_tree_mixed" role="tREe"></span>
+ <span id="aria_treegrid" role="treegrid"></span>
+ <span id="aria_treegrid_mixed" role="tREEGRId"></span>
+ <span id="aria_treeitem" role="treeitem"></span>
+ <span id="aria_treeitem_mixed" role="tREEITEm"></span>
+
+ <article id="articlemain" role="main">a main area</article>
+ <article id="articlemain_mixed" role="mAIn">a main area</article>
+ <article id="articleform" role="form">a form area</article>
+ <article id="articleform_mixed" role="fORm">a form area</article>
+
+ <div id="testArticle" role="article" title="Test article">
+ <p>This is a paragraph inside the article.</p>
+ </div>
+
+ <div id="testArticle_mixed" role="aRTICLe" title="Test article">
+ <p>This is a paragraph inside the article.</p>
+ </div>
+
+ <!-- "live" roles -->
+ <table role="log" id="log_table">
+ <tr><td>Table based log</td></tr>
+ </table>
+ <table role="LOG" id="log_table_mixed">
+ <tr><td>Table based log</td></tr>
+ </table>
+ <h1 role="marquee" id="marquee_h1">marquee</h1>
+ <h1 role="MARQUEE" id="marquee_h1_mixed">marquee</h1>
+ <div role="timer" id="timer_div">timer</div>
+ <div role="TIMER" id="timer_div_mixed">timer</div>
+
+ <!-- landmarks -->
+ <div role="application" id="application">application</div>
+ <div role="aPPLICATIOn" id="application_mixed">application</div>
+ <div role="form" id="form">form</div>
+ <div role="fORm" id="form_mixed">form</div>
+
+ <!-- weak landmarks -->
+ <div role="banner" id="banner">banner</div>
+ <div role="bANNEr" id="banner_mixed">banner</div>
+ <div role="complementary" id="complementary">complementary</div>
+ <div role="cOMPLEMENTARy" id="complementary_mixed">complementary</div>
+ <div role="contentinfo" id="contentinfo">contentinfo</div>
+ <div role="cONTENTINFo" id="contentinfo_mixed">contentinfo</div>
+ <div role="main" id="main">main</div>
+ <div role="mAIN" id="main_mixed">main</div>
+ <div role="navigation" id="navigation">navigation</div>
+ <div role="nAVIGATIOn" id="navigation_mixed">navigation</div>
+ <div role="search" id="search">search</div>
+ <div role="sEARCh" id="search_mixed">search</div>
+
+ <!-- landmarks are tables -->
+ <table role="application" id="application_table">application table
+ <tr><td>hi<td></tr></table>
+ <table role="aPPLICATIOn" id="application_table_mixed">application table
+ <tr><td>hi<td></tr></table>
+ <table role="banner" id="banner_table">banner table
+ <tr><td>hi<td></tr></table>
+ <table role="bANNEr" id="banner_table_mixed">banner table
+ <tr><td>hi<td></tr></table>
+ <table role="complementary" id="complementary_table">complementary table
+ <tr><td>hi<td></tr></table>
+ <table role="cOMPLEMENTARy" id="complementary_table_mixed">complementary table
+ <tr><td>hi<td></tr></table>
+ <table role="contentinfo" id="contentinfo_table">contentinfo table
+ <tr><td>hi<td></tr></table>
+ <table role="cONTENTINFo" id="contentinfo_table_mixed">contentinfo table
+ <tr><td>hi<td></tr></table>
+ <table role="main" id="main_table">main table
+ <tr><td>hi<td></tr></table>
+ <table role="mAIn" id="main_table_mixed">main table
+ <tr><td>hi<td></tr></table>
+ <table role="navigation" id="navigation_table">navigation table
+ <tr><td>hi<td></tr></table>
+ <table role="nAVIGATIOn" id="navigation_table_mixed">navigation table
+ <tr><td>hi<td></tr></table>
+ <table role="search" id="search_table">search table
+ <tr><td>hi<td></tr></table>
+ <table role="sEARCh" id="search_table_mixed">search table
+ <tr><td>hi<td></tr></table>
+
+ <!-- test gEmptyRoleMap -->
+ <table role="button">
+ <tr id="buttontable_row">
+ <td id="buttontable_cell">cell</td>
+ </tr>
+ </table>
+ <table role="bUTTOn">
+ <tr id="buttontable_row_mixed">
+ <td id="buttontable_cell_mixed">cell</td>
+ </tr>
+ </table>
+
+ <!-- user agents must not map abstract roles to platform API -->
+ <!-- test abstract base type roles -->
+ <div role="composite" id="composite">composite</div>
+ <div role="cOMPOSITe" id="composite_mixed">composite</div>
+ <div role="landmark" id="landmark">landmark</div>
+ <div role="lANDMARk" id="landmark_mixed">landmark</div>
+ <div role="roletype" id="roletype">roletype</div>
+ <div role="rOLETYPe" id="roletype_mixed">roletype</div>
+ <div role="structure" id="structure">structure</div>
+ <div role="sTRUCTURe" id="structure_mixed">structure</div>
+ <div role="widget" id="widget">widget</div>
+ <div role="wIDGEt" id="widget_mixed">widget</div>
+ <div role="window" id="window">window</div>
+ <div role="wINDOw" id="window_mixed">window</div>
+ <!-- test abstract input roles -->
+ <div role="input" id="input">input</div>
+ <div role="iNPUt" id="input_mixed">input</div>
+ <div role="range" id="range">range</div>
+ <div role="rANGe" id="range_mixed">range</div>
+ <div role="select" id="select">select</div>
+ <div role="sELECt" id="select_mixed">select</div>
+ <!-- test abstract structure roles -->
+ <div role="section" id="section">section</div>
+ <div role="sECTIOn" id="section_mixed">section</div>
+ <div role="sectionhead" id="sectionhead">sectionhead</div>
+ <div role="sECTIONHEAd" id="sectionhead_mixed">sectionhead</div>
+
+ <!-- roles transformed by ARIA roles of ancestors -->
+ <table role="grid">
+ <tr>
+ <td id="implicit_gridcell">foo</td>
+ </tr>
+ </table>
+ <table role="gRId">
+ <tr>
+ <td id="implicit_gridcell_mixed">foo</td>
+ </tr>
+ </table>
+
+ <!-- roles transformed by ARIA state attributes -->
+ <button aria-pressed="true" id="togglebutton"></button>
+ <button aria-pressed="tRUe" id="togglebutton_mixed"></button>
+
+ <!-- take the first known mappable role -->
+ <div role="wiggly:worm abc123 button" id="unknown_roles">worm button</div>
+ <div role="wiggly:worm abc123 bUTTOn" id="unknown_roles_mixed">worm button</div>
+
+ <!-- misc roles -->
+ <div role="note" id="note">note</div>
+ <div role="nOTe" id="note_mixed">note</div>
+ <div role="scrollbar" id="scrollbar">scrollbar</div>
+ <div role="sCROLLBAr" id="scrollbar_mixed">scrollbar</div>
+
+ <div id="dir" role="directory">
+ <div role="listitem">A</div>
+ <div role="listitem">B</div>
+ <div role="listitem">C</div>
+ </div>
+
+ <div id="dir_mixed" role="dIRECTORy">
+ <div role="listitem">A</div>
+ <div role="listitem">B</div>
+ <div role="listitem">C</div>
+ </div>
+
+ <p>Image:
+ <img id="img_eq" role="math" src="foo" alt="x^2 + y^2 + z^2">
+ </p>
+
+ <p>Image:
+ <img id="img_eq_mixed" role="mATh" src="foo" alt="x^2 + y^2 + z^2">
+ </p>
+
+ <p>Text:
+ <span id="txt_eq" role="math" title="x^2 + y^2 + z^2">x<sup>2</sup> +
+ y<sup>2</sup> + z<sup>2</sup></span>
+ </p>
+ <p>Text:
+ <span id="txt_eq_mixed" role="mATh" title="x^2 + y^2 + z^2">x<sup>2</sup> +
+ y<sup>2</sup> + z<sup>2</sup></span>
+ </p>
+
+ <iframe id="iframe_aria_application"
+ src="data:text/html,<body role='application'>"></iframe>
+ <iframe id="iframe_aria_application_mixed"
+ src="data:text/html,<body role='aPPLICATIOn'>"></iframe>
+ <iframe id="iframe_aria_dialog"
+ src="data:text/html,<body role='dialog'>"></iframe>
+ <iframe id="iframe_aria_dialog_mixed"
+ src="data:text/html,<body role='dIALOg'>"></iframe>
+ <iframe id="iframe_aria_document"
+ src="data:text/html,<body role='document'>"></iframe>
+ <iframe id="iframe_aria_document_mixed"
+ src="data:text/html,<body role='dOCUMENt'>"></iframe>
+ <iframe id="iframe_aria_button"
+ src="data:text/html,<body role='button'>"></iframe>
+ <iframe id="iframe_aria_button_mixed"
+ src="data:text/html,<body role='bUTTOn'>"></iframe>
+ <iframe id="iframe_aria_main"
+ src="data:text/html,<body role='main'>"></iframe>
+ <iframe id="iframe_aria_main_mixed"
+ src="data:text/html,<body role='mAIn'>"></iframe>
+ <iframe id="iframe_aria_alert"
+ src="data:text/html,<body role='alert'>"></iframe>
+ <iframe id="iframe_aria_alert_mixed"
+ src="data:text/html,<body role='aLERt'>"></iframe>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/role/test_aria.xhtml b/accessible/tests/mochitest/role/test_aria.xhtml
new file mode 100644
index 0000000000..9aea4ec222
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_aria.xhtml
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Name Calculating Test.">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ ok(!isAccessible("presentation_label"),
+ "Presentation label shouldn't be accessible.");
+ ok(!isAccessible("presentation_descr"),
+ "Presentation description shouldn't be accessible.");
+
+ // aria-pressed
+ testRole("pressed_button", ROLE_TOGGLE_BUTTON);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=494345"
+ title="Do not create accessibles for XUL label or description having a role of 'presentation'">
+ Mozilla Bug 494345
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1033283"
+ title="Expose pressed state on XUL menu toggle buttons">
+ Mozilla Bug 1033283
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <label id="presentation_label" role="presentation" value="label"/>
+ <description id="presentation_descr" role="presentation" value="description"/>
+ <button id="pressed_button" aria-pressed="true" label="I am pressed" />
+ </vbox>
+
+
+ </hbox>
+</window>
+
diff --git a/accessible/tests/mochitest/role/test_dpub_aria.html b/accessible/tests/mochitest/role/test_dpub_aria.html
new file mode 100644
index 0000000000..621c86a59b
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_dpub_aria.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test DPub ARIA roles</title>
+
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // DPub ARIA role map.
+ testRole("doc-abstract", ROLE_SECTION);
+ testRole("doc-acknowledgments", ROLE_LANDMARK);
+ testRole("doc-afterword", ROLE_LANDMARK);
+ testRole("doc-appendix", ROLE_LANDMARK);
+ testRole("doc-backlink", ROLE_LINK);
+ testRole("doc-biblioentry", ROLE_LISTITEM);
+ testRole("doc-bibliography", ROLE_LANDMARK);
+ testRole("doc-biblioref", ROLE_LINK);
+ testRole("doc-chapter", ROLE_LANDMARK);
+ testRole("doc-colophon", ROLE_SECTION);
+ testRole("doc-conclusion", ROLE_LANDMARK);
+ testRole("doc-cover", ROLE_GRAPHIC);
+ testRole("doc-credit", ROLE_SECTION);
+ testRole("doc-credits", ROLE_LANDMARK);
+ testRole("doc-dedication", ROLE_SECTION);
+ testRole("doc-endnote", ROLE_LISTITEM);
+ testRole("doc-endnotes", ROLE_LANDMARK);
+ testRole("doc-epigraph", ROLE_SECTION);
+ testRole("doc-epilogue", ROLE_LANDMARK);
+ testRole("doc-errata", ROLE_LANDMARK);
+ testRole("doc-example", ROLE_SECTION);
+ testRole("doc-footnote", ROLE_FOOTNOTE);
+ testRole("doc-foreword", ROLE_LANDMARK);
+ testRole("doc-glossary", ROLE_LANDMARK);
+ testRole("doc-glossref", ROLE_LINK);
+ testRole("doc-index", ROLE_NAVIGATION);
+ testRole("doc-introduction", ROLE_LANDMARK);
+ testRole("doc-noteref", ROLE_LINK);
+ testRole("doc-notice", ROLE_NOTE);
+ testRole("doc-pagebreak", ROLE_SEPARATOR);
+ testRole("doc-pagelist", ROLE_NAVIGATION);
+ testRole("doc-part", ROLE_LANDMARK);
+ testRole("doc-preface", ROLE_LANDMARK);
+ testRole("doc-prologue", ROLE_LANDMARK);
+ testRole("doc-pullquote", ROLE_SECTION);
+ testRole("doc-qna", ROLE_SECTION);
+ testRole("doc-subtitle", ROLE_HEADING);
+ testRole("doc-tip", ROLE_NOTE);
+ testRole("doc-toc", ROLE_NAVIGATION);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1343537"
+ title="implement ARIA DPUB extension">
+ Bug 1343537
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <div id="doc-abstract" role="doc-abstract">abstract</div>
+ <div id="doc-acknowledgments" role="doc-acknowledgments">acknowledgments</div>
+ <div id="doc-afterword" role="doc-afterword">afterword</div>
+ <div id="doc-appendix" role="doc-appendix">appendix</div>
+ <div id="doc-backlink" role="doc-backlink">backlink</div>
+ <div id="doc-biblioentry" role="doc-biblioentry">biblioentry</div>
+ <div id="doc-bibliography" role="doc-bibliography">bibliography</div>
+ <div id="doc-biblioref" role="doc-biblioref">biblioref</div>
+ <div id="doc-chapter" role="doc-chapter">chapter</div>
+ <div id="doc-colophon" role="doc-colophon">colophon</div>
+ <div id="doc-conclusion" role="doc-conclusion">conclusion</div>
+ <div id="doc-cover" role="doc-cover">cover</div>
+ <div id="doc-credit" role="doc-credit">credit</div>
+ <div id="doc-credits" role="doc-credits">credits</div>
+ <div id="doc-dedication" role="doc-dedication">dedication</div>
+ <div id="doc-endnote" role="doc-endnote">endnote</div>
+ <div id="doc-endnotes" role="doc-endnotes">endnotes</div>
+ <div id="doc-epigraph" role="doc-epigraph">epigraph</div>
+ <div id="doc-epilogue" role="doc-epilogue">epilogue</div>
+ <div id="doc-errata" role="doc-errata">errata</div>
+ <div id="doc-example" role="doc-example">example</div>
+ <div id="doc-footnote" role="doc-footnote">footnote</div>
+ <div id="doc-foreword" role="doc-foreword">foreword</div>
+ <div id="doc-glossary" role="doc-glossary">glossary</div>
+ <div id="doc-glossref" role="doc-glossref">glossref</div>
+ <div id="doc-index" role="doc-index">index</div>
+ <div id="doc-introduction" role="doc-introduction">introduction</div>
+ <div id="doc-noteref" role="doc-noteref">noteref</div>
+ <div id="doc-notice" role="doc-notice">notice</div>
+ <div id="doc-pagebreak" role="doc-pagebreak">pagebreak</div>
+ <div id="doc-pagelist" role="doc-pagelist">pagelist</div>
+ <div id="doc-part" role="doc-part">part</div>
+ <div id="doc-preface" role="doc-preface">preface</div>
+ <div id="doc-prologue" role="doc-prologue">prologue</div>
+ <div id="doc-pullquote" role="doc-pullquote">pullquote</div>
+ <div id="doc-qna" role="doc-qna">qna</div>
+ <div id="doc-subtitle" role="doc-subtitle">subtitle</div>
+ <div id="doc-tip" role="doc-tip">tip</div>
+ <div id="doc-toc" role="doc-toc">toc</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/role/test_general.html b/accessible/tests/mochitest/role/test_general.html
new file mode 100644
index 0000000000..282690cf98
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_general.html
@@ -0,0 +1,201 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>test nsHyperTextAccessible accesible objects creation and their roles</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+ function doTests() {
+ // landmark tests section
+ testRole("frm", ROLE_FORM);
+
+ // nsHyperTextAcc tests section
+ // Test html:form.
+ testRole("nav", ROLE_LANDMARK);
+ testRole("header", ROLE_LANDMARK);
+ testRole("footer", ROLE_LANDMARK);
+ testRole("article", ROLE_ARTICLE);
+ testRole("aside", ROLE_LANDMARK);
+ testRole("section", ROLE_SECTION);
+
+ // Bug 996821
+ // Check that landmark elements get accessibles with styled overflow.
+ testRole("section_overflow", ROLE_SECTION);
+ testRole("nav_overflow", ROLE_LANDMARK);
+ testRole("header_overflow", ROLE_SECTION);
+ testRole("aside_overflow", ROLE_LANDMARK);
+ testRole("footer_overflow", ROLE_SECTION);
+ testRole("article_overflow", ROLE_ARTICLE);
+
+ // test html:div
+ testRole("sec", ROLE_SECTION);
+
+ // Test html:blockquote
+ testRole("quote", ROLE_BLOCKQUOTE);
+
+ // Test html:h, all levels
+ testRole("head1", ROLE_HEADING);
+ testRole("head2", ROLE_HEADING);
+ testRole("head3", ROLE_HEADING);
+ testRole("head4", ROLE_HEADING);
+ testRole("head5", ROLE_HEADING);
+ testRole("head6", ROLE_HEADING);
+
+ // Test that an html:input @type="file" is exposed as ROLE_GROUPING.
+ testRole("data", ROLE_GROUPING);
+
+ // Test that input type="checkbox" and type="radio" are
+ // exposed as such regardless of appearance style.
+ testRole("checkbox_regular", ROLE_CHECKBUTTON);
+ testRole("checkbox_appearance_none", ROLE_CHECKBUTTON);
+ testRole("radio_regular", ROLE_RADIOBUTTON);
+ testRole("radio_appearance_none", ROLE_RADIOBUTTON);
+
+ // Test regular paragraph by comparison to make sure exposure does not
+ // get broken.
+ testRole("p", ROLE_PARAGRAPH);
+
+ // Test dl, dt, dd
+ testRole("definitionlist", ROLE_DEFINITION_LIST);
+ testRole("definitionterm", ROLE_TERM);
+ testRole("definitiondescription", ROLE_DEFINITION);
+
+ // Has click, mousedown or mouseup listeners.
+ testRole("span1", ROLE_TEXT);
+ testRole("span2", ROLE_TEXT);
+ testRole("span3", ROLE_TEXT);
+
+ // Test role of listbox inside combobox
+ testRole("listbox1", ROLE_COMBOBOX_LIST);
+ testRole("listbox2", ROLE_COMBOBOX_LIST);
+
+ // Test role of menu and li items in menu
+ testRole("menu", ROLE_LIST);
+ testRole("menuItem", ROLE_LISTITEM);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+<body>
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=472326"
+ title="html:input of type "file" no longer rendered to screen readers">
+ Mozilla Bug 472326
+ </a><br>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=474261"
+ title="Test remaining implementations in nsHyperTextAccessible::GetRole">
+ bug 474261
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409"
+ title="Expose click action if mouseup and mousedown are registered">
+ Mozilla Bug 423409
+ </a>
+ <a target="_blank"
+ title="Provide mappings for html5 <nav> <header> <footer> <article>"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=593368">
+ Bug 593368
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=613502"
+ title="Map <article> like we do aria role article">
+ Bug 613502
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=610650"
+ title="Change implementation of HTML5 landmark elements to conform">
+ Bug 610650
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=614310"
+ title="Map section to pane (like role=region)">
+ Mozilla Bug 614310
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=734982"
+ title="Map ARIA role FORM">
+ Bug 734982
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1044431"
+ title="Listbox owned by combobox has the wrong role">
+ Mozilla Bug 1044431
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <form id="frm" action="submit.php" method="post">
+ <label for="data">File</label>:
+ <input type="file" id="data" name="data" size="50"/>
+ <input type="checkbox" id="checkbox_regular" value="Check me"/>
+ <input type="checkbox" style="-moz-appearance: none;" id="checkbox_appearance_none" value="Check me"/>
+ <input type="radio" id="radio_regular" value="Check me"/>
+ <input type="radio" style="-moz-appearance: none;" id="radio_appearance_none" value="Check me"/>
+ </form>
+
+ <nav id="nav">a nav</nav>
+ <header id="header">a header</header>
+ <footer id="footer">a footer</footer>
+ <article id="article">an article</article>
+ <aside id="aside">by the way I am an aside</aside>
+ <section id="section">a section</section>
+
+ <section style="overflow: hidden;" id="section_overflow">
+ <nav style="overflow: hidden;"
+ id="nav_overflow">overflow nav</nav>
+ <header style="overflow: hidden;"
+ id="header_overflow">overflow header</header>
+ <aside style="overflow: hidden;"
+ id="aside_overflow">overflow aside</aside>
+ <footer style="overflow: hidden;"
+ id="footer_overflow">overflow footer</footer>
+ </section>
+ <article style="overflow: hidden;"
+ id="article_overflow">overflow article</article>
+
+ <p id="p">A paragraph for comparison.</p>
+ <div id="sec">A normal div</div>
+ <blockquote id="quote">A citation</blockquote>
+ <h1 id="head1">A heading level 1</h1>
+ <h2 id="head2">A heading level 2</h2>
+ <h3 id="head3">A heading level 3</h3>
+ <h4 id="head4">A heading level 4</h4>
+ <h5 id="head5">A heading level 5</h5>
+ <h6 id="head6">A heading level 6</h6>
+
+ <dl id="definitionlist">
+ <dt id="definitionterm">gecko</dt>
+ <dd id="definitiondescription">geckos have sticky toes</dd>
+ </dl>
+
+ <span id="span1" onclick="">clickable span</span>
+ <span id="span2" onmousedown="">clickable span</span>
+ <span id="span3" onmouseup="">clickable span</span>
+
+ <div id="combobox1" role="combobox">
+ <div id="listbox1" role="listbox"></div>
+ </div>
+ <div id="combobox2" role="combobox" aria-owns="listbox2"></div>
+ <div id="listbox2" role="listbox"></div>
+
+ <menu id="menu">
+ <li id="menuItem">menu item!</li>
+ </menu>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/role/test_general.xhtml b/accessible/tests/mochitest/role/test_general.xhtml
new file mode 100644
index 0000000000..95de7775ea
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_general.xhtml
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Role XUL Test.">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ ok(!isAccessible("image"),
+ "image without tooltiptext shouldn't be accessible.");
+ testRole("image-tooltiptext", ROLE_GRAPHIC);
+
+ ok(!isAccessible("statusbarpanel"),
+ "statusbarpanel shouldn't be accessible.");
+ testRole("statusbar", ROLE_STATUSBAR);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=900097"
+ title="statusbarpanel shouldn't be a button accessible">
+ Mozilla Bug 900097
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <image id="image" src="../moz.png"/>
+ <image id="image-tooltiptext" src="../moz.png" tooltiptext="hello"/>
+
+ <statusbarpanel id="statusbarpanel"></statusbarpanel>
+ <statusbar id="statusbar"></statusbar>
+
+ </hbox>
+</window>
+
diff --git a/accessible/tests/mochitest/role/test_graphics_aria.html b/accessible/tests/mochitest/role/test_graphics_aria.html
new file mode 100644
index 0000000000..8ddfe9224d
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_graphics_aria.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test Graphics ARIA roles</title>
+
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // Graphics ARIA role map.
+ testRole("graphics-document", ROLE_NON_NATIVE_DOCUMENT);
+ testRole("graphics-object", ROLE_GROUPING);
+ testRole("graphics-symbol", ROLE_GRAPHIC);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432513"
+ title="implement ARIA Graphics roles">
+ Bug 1432513
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <div id="graphics-document" role="graphics-document">document</div>
+ <div id="graphics-object" role="graphics-object">object</div>
+ <div id="graphics-symbol" role="graphics-symbol">symbol</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/role/test_svg.html b/accessible/tests/mochitest/role/test_svg.html
new file mode 100644
index 0000000000..716dae7ee2
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_svg.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>SVG elements accessible roles</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+ function doTests() {
+ testRole("svg", ROLE_DIAGRAM);
+ testRole("g", ROLE_GROUPING);
+ testRole("rect", ROLE_GRAPHIC);
+ testRole("circle", ROLE_GRAPHIC);
+ testRole("ellipse", ROLE_GRAPHIC);
+ testRole("line", ROLE_GRAPHIC);
+ testRole("polygon", ROLE_GRAPHIC);
+ testRole("polyline", ROLE_GRAPHIC);
+ testRole("path", ROLE_GRAPHIC);
+ testRole("image", ROLE_GRAPHIC);
+ testRole("image2", ROLE_GRAPHIC);
+ testRole("a", ROLE_LINK);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=822983"
+ title="Map SVG graphic elements to accessibility API">
+ Bug 822983
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g id="g">
+ <title>g</title>
+ </g>
+ <rect width="300" height="100" id="rect"
+ style="fill:rgb(0,0,255);stroke-width:1;stroke:rgb(0,0,0)">
+ <title>rect</title>
+ </rect>
+ <circle cx="100" cy="50" r="40" stroke="black" id="circle"
+ stroke-width="2" fill="red">
+ <title>circle</title>
+ </circle>
+ <ellipse cx="300" cy="80" rx="100" ry="50" id="ellipse"
+ style="fill:yellow;stroke:purple;stroke-width:2">
+ <title>ellipse</title>
+ </ellipse>
+ <line x1="0" y1="0" x2="200" y2="200" id="line"
+ style="stroke:rgb(255,0,0);stroke-width:2">
+ <title>line</title>
+ </line>
+ <polygon points="200,10 250,190 160,210" id="polygon"
+ style="fill:lime;stroke:purple;stroke-width:1">
+ <title>polygon</title>
+ </polygon>
+ <polyline points="20,20 40,25 60,40 80,120 120,140 200,180" id="polyline"
+ style="fill:none;stroke:black;stroke-width:3" >
+ <title>polyline</title>
+ </polyline>
+ <path d="M150 0 L75 200 L225 200 Z" id="path">
+ <title>path</title>
+ </path>
+ <image x1="25" y1="80" width="50" height="20" id="image"
+ xlink:href="../moz.png">
+ <title>image</title>
+ </image>
+ <image x1="25" y1="110" width="50" height="20" id="image2"
+ xlink:href="../moz.png" aria-label="hello"/>
+ <a href="#" id="a"><text>a</text></a>
+ </svg>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/scroll/a11y.ini b/accessible/tests/mochitest/scroll/a11y.ini
new file mode 100644
index 0000000000..3f58374c93
--- /dev/null
+++ b/accessible/tests/mochitest/scroll/a11y.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_zoom.html]
diff --git a/accessible/tests/mochitest/scroll/test_zoom.html b/accessible/tests/mochitest/scroll/test_zoom.html
new file mode 100644
index 0000000000..b0ece28a91
--- /dev/null
+++ b/accessible/tests/mochitest/scroll/test_zoom.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test scrollToPoint when page is zoomed</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+
+ <script type="application/javascript">
+ function testScrollToPoint() {
+ // scrollToPoint relative screen
+ var anchor = getAccessible("bottom1");
+ let [x /* y */] = getPos(anchor);
+ var [docX, docY] = getPos(document);
+
+ anchor.scrollToPoint(COORDTYPE_SCREEN_RELATIVE, docX, docY);
+ testPos(anchor, [x, docY]);
+
+ // scrollToPoint relative window
+ anchor = getAccessible("bottom2");
+ [x /* y */] = getPos(anchor);
+ var wnd = getRootAccessible().DOMDocument.defaultView;
+ var [screenX, screenY] = CSSToDevicePixels(wnd, wnd.screenX, wnd.screenY);
+ let scrollToX = docX - screenX, scrollToY = docY - screenY;
+
+ anchor.scrollToPoint(COORDTYPE_WINDOW_RELATIVE, scrollToX, scrollToY);
+ testPos(anchor, [x, docY]);
+
+ // scrollToPoint relative parent
+ anchor = getAccessible("bottom3");
+ [x /* y */] = getPos(anchor);
+ var [parentX, parentY] = getPos(anchor.parent);
+ scrollToX = parentX - docX;
+ scrollToY = parentY - docY;
+
+ anchor.scrollToPoint(COORDTYPE_PARENT_RELATIVE, scrollToX, scrollToY);
+ testPos(anchor, [x, docY]);
+ }
+
+ function doTest() {
+ testScrollToPoint();
+ zoomDocument(document, 2.0);
+ testScrollToPoint(); // zoom and test again
+
+ zoomDocument(document, 1.0);
+ SimpleTest.finish();
+ }
+
+ addA11yLoadEvent(doTest);
+ SimpleTest.waitForExplicitFinish();
+ </script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942"
+ title="scrollToPoint is broken when page is zoomed">
+ Mozilla Bug 727942
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <h1>Below there is a bunch of named anchors</h1>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ This is in the middle anchor #1<a id="bottom1"></a>
+ <br><br><br><br><br><br><br><br><br><br>
+ This is in the middle anchor #2<a id="bottom2"></a>
+ <br><br><br><br><br><br><br><br><br><br>
+ This is in the middle anchor #3<a id="bottom3"></a>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/selectable.js b/accessible/tests/mochitest/selectable.js
new file mode 100644
index 0000000000..43c7260b0a
--- /dev/null
+++ b/accessible/tests/mochitest/selectable.js
@@ -0,0 +1,138 @@
+/* import-globals-from common.js */
+/* import-globals-from states.js */
+
+/**
+ * Test selection getter methods of nsIAccessibleSelectable.
+ *
+ * @param aIdentifier [in] selectable container accessible
+ * @param aSelectedChildren [in] array of selected children
+ */
+function testSelectableSelection(aIdentifier, aSelectedChildren, aMsg) {
+ var acc = getAccessible(aIdentifier, [nsIAccessibleSelectable]);
+ if (!acc) {
+ return;
+ }
+
+ var msg = aMsg ? aMsg : "";
+ var len = aSelectedChildren.length;
+
+ // getSelectedChildren
+ var selectedChildren = acc.selectedItems;
+ is(
+ selectedChildren ? selectedChildren.length : 0,
+ len,
+ msg +
+ "getSelectedChildren: wrong selected children count for " +
+ prettyName(aIdentifier)
+ );
+
+ for (let idx = 0; idx < len; idx++) {
+ let expectedAcc = aSelectedChildren[idx];
+ let expectedAccId =
+ expectedAcc instanceof nsIAccessible
+ ? getAccessibleDOMNodeID(expectedAcc)
+ : expectedAcc;
+ let actualAcc = selectedChildren.queryElementAt(idx, nsIAccessible);
+ let actualAccId = getAccessibleDOMNodeID(actualAcc);
+ is(
+ actualAccId,
+ expectedAccId,
+ msg +
+ "getSelectedChildren: wrong selected child at index " +
+ idx +
+ " for " +
+ prettyName(aIdentifier) +
+ " { actual : " +
+ prettyName(actualAcc) +
+ ", expected: " +
+ prettyName(expectedAcc) +
+ "}"
+ );
+ }
+
+ // selectedItemCount
+ is(
+ acc.selectedItemCount,
+ aSelectedChildren.length,
+ "selectedItemCount: wrong selected children count for " +
+ prettyName(aIdentifier)
+ );
+
+ // getSelectedItemAt
+ for (let idx = 0; idx < len; idx++) {
+ let expectedAcc = aSelectedChildren[idx];
+ let expectedAccId =
+ expectedAcc instanceof nsIAccessible
+ ? getAccessibleDOMNodeID(expectedAcc)
+ : expectedAcc;
+ is(
+ getAccessibleDOMNodeID(acc.getSelectedItemAt(idx)),
+ expectedAccId,
+ msg +
+ "getSelectedItemAt: wrong selected child at index " +
+ idx +
+ " for " +
+ prettyName(aIdentifier)
+ );
+ }
+
+ // isItemSelected
+ testIsItemSelected(acc, acc, { value: 0 }, aSelectedChildren, msg);
+}
+
+/**
+ * Test isItemSelected method, helper for testSelectableSelection
+ */
+function testIsItemSelected(
+ aSelectAcc,
+ aTraversedAcc,
+ aIndexObj,
+ aSelectedChildren,
+ aMsg
+) {
+ var childCount = aTraversedAcc.childCount;
+ for (var idx = 0; idx < childCount; idx++) {
+ var child = aTraversedAcc.getChildAt(idx);
+ var [state /* extraState */] = getStates(child);
+ if (state & STATE_SELECTABLE) {
+ var isSelected = false;
+ var len = aSelectedChildren.length;
+ for (var jdx = 0; jdx < len; jdx++) {
+ let expectedAcc = aSelectedChildren[jdx];
+ let matches =
+ expectedAcc instanceof nsIAccessible
+ ? child == expectedAcc
+ : getAccessibleDOMNodeID(child) == expectedAcc;
+
+ if (matches) {
+ isSelected = true;
+ break;
+ }
+ }
+
+ // isItemSelected
+ is(
+ aSelectAcc.isItemSelected(aIndexObj.value++),
+ isSelected,
+ aMsg +
+ "isItemSelected: wrong selected child " +
+ prettyName(child) +
+ " for " +
+ prettyName(aSelectAcc)
+ );
+
+ // selected state
+ testStates(
+ child,
+ isSelected ? STATE_SELECTED : 0,
+ 0,
+ !isSelected ? STATE_SELECTED : 0,
+ 0
+ );
+
+ continue;
+ }
+
+ testIsItemSelected(aSelectAcc, child, aIndexObj, aSelectedChildren);
+ }
+}
diff --git a/accessible/tests/mochitest/selectable/a11y.ini b/accessible/tests/mochitest/selectable/a11y.ini
new file mode 100644
index 0000000000..4f980a18f4
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/a11y.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/treeview.css
+
+[test_listbox.xhtml]
+[test_menu.xhtml]
+[test_menulist.xhtml]
+[test_tabs.xhtml]
+[test_tree.xhtml]
diff --git a/accessible/tests/mochitest/selectable/test_listbox.xhtml b/accessible/tests/mochitest/selectable/test_listbox.xhtml
new file mode 100644
index 0000000000..b915b2790f
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_listbox.xhtml
@@ -0,0 +1,144 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../treeview.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL tree selectable tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../selectable.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "debug";
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // single selectable listbox, the first item is selected by default
+
+ var id = "listbox";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for list of " + id);
+
+ var select = getAccessible(id, [nsIAccessibleSelectable]);
+ select.removeItemFromSelection(0);
+ testSelectableSelection(select, [ ]);
+
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "lb1_item2" ], "addItemToSelect(1): ");
+
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ ],
+ "removeItemFromSelection(1): ");
+
+ todo(!select.selectAll(),
+ "No way to select all items in listbox '" + id + "'");
+ testSelectableSelection(select, [ "lb1_item1" ], "selectAll: ");
+
+ select.addItemToSelection(1);
+ select.unselectAll();
+ testSelectableSelection(select, [ ], "unselectAll: ");
+
+ //////////////////////////////////////////////////////////////////////////
+ // multiple selectable listbox
+
+ id = "listbox2";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for list of " + id);
+
+ select = getAccessible(id, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ ]);
+
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "lb2_item2" ], "addItemToSelect(1): ");
+
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ ],
+ "removeItemFromSelection(1): ");
+
+ is(select.selectAll(), true,
+ "All items should be selected in listbox '" + id + "'");
+ testSelectableSelection(select, [ "lb2_item1", "lb2_item2" ],
+ "selectAll: ");
+
+ select.unselectAll();
+ testSelectableSelection(select, [ ], "unselectAll: ");
+
+ //////////////////////////////////////////////////////////////////////////
+ // listbox with headers
+
+ // XXX: addItemToSelection/removeItemFromSelection don't work correctly
+ // on listboxes with headers because header is inserted into hierarchy
+ // and child indexes that are used in these methods are shifted (see bug
+ // 591939).
+ todo(false,
+ "Fix addItemToSelection/removeItemFromSelection on listboxes with headers.");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176"
+ title="add pseudo SelectAccessible interface">
+ Mozilla Bug 590176
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <richlistbox id="listbox">
+ <richlistitem id="lb1_item1">
+ <label value="cell0"/>
+ <label value="cell1"/>
+ </richlistitem>
+ <richlistitem id="lb1_item2">
+ <label value="cell3"/>
+ <label value="cell4"/>
+ </richlistitem>
+ </richlistbox>
+
+ <richlistbox id="listbox2" seltype="multiple">
+ <richlistitem id="lb2_item1">
+ <label value="cell0"/>
+ <label value="cell1"/>
+ </richlistitem>
+ <richlistitem id="lb2_item2">
+ <label value="cell3"/>
+ <label value="cell4"/>
+ </richlistitem>
+ </richlistbox>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/selectable/test_menu.xhtml b/accessible/tests/mochitest/selectable/test_menu.xhtml
new file mode 100644
index 0000000000..bfef622348
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_menu.xhtml
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../treeview.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL tree selectable tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../selectable.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "debug";
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // menu
+
+ var id = "menu";
+ var menu = getAccessible("menu");
+ var menuList = menu.firstChild;
+ todo(isAccessible(menuList, [nsIAccessibleSelectable]),
+ "No selectable accessible for list of menu '" + id + "'");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176"
+ title="add pseudo SelectAccessible interface">
+ Mozilla Bug 590176
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menu label="menu" id="menu">
+ <menupopup>
+ <menuitem label="item1" id="m_item1"/>
+ <menuitem label="item2" id="m_item2"/>
+ </menupopup>
+ </menu>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/selectable/test_menulist.xhtml b/accessible/tests/mochitest/selectable/test_menulist.xhtml
new file mode 100644
index 0000000000..2e4b5ac08f
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_menulist.xhtml
@@ -0,0 +1,95 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../treeview.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL tree selectable tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../selectable.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "debug";
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // menulist aka combobox
+
+ var id = "combobox";
+ var combobox = getAccessible(id);
+ var comboboxList = combobox.firstChild;
+ ok(isAccessible(comboboxList, [nsIAccessibleSelectable]),
+ "No selectable accessible for list of " + id);
+
+ var select = getAccessible(comboboxList, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ "cb1_item1" ]);
+
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "cb1_item2" ], "addItemToSelection(1): ");
+
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ ],
+ "removeItemFromSelection(1): ");
+
+ is(select.selectAll(), false,
+ "No way to select all items in combobox '" + id + "'");
+ testSelectableSelection(select, [ ], "selectAll: ");
+
+ select.addItemToSelection(1);
+ select.unselectAll();
+ testSelectableSelection(select, [ ], "unselectAll: ");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176"
+ title="add pseudo SelectAccessible interface">
+ Mozilla Bug 590176
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menulist id="combobox">
+ <menupopup>
+ <menuitem id="cb1_item1" label="item1"/>
+ <menuitem id="cb1_item2" label="item2"/>
+ </menupopup>
+ </menulist>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/selectable/test_tabs.xhtml b/accessible/tests/mochitest/selectable/test_tabs.xhtml
new file mode 100644
index 0000000000..bc16ab0fe7
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_tabs.xhtml
@@ -0,0 +1,93 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL tabs selectable tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../selectable.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ var id = "tabs_single";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for tabs_single");
+ var select = getAccessible(id, [nsIAccessibleSelectable]);
+
+ testSelectableSelection(select, ["tab_single1"]);
+
+ select.unselectAll();
+ select.addItemToSelection(1); // tab_single2
+ testSelectableSelection(select, ["tab_single2"], "select tab_single2: ");
+
+ id = "tabs_multi";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for tabs_multi");
+ select = getAccessible(id, [nsIAccessibleSelectable]);
+
+ // Make sure both XUL selection and ARIA selection are included.
+ testSelectableSelection(select, ["tab_multi_xul1", "tab_multi_aria"]);
+
+ select.unselectAll();
+ select.addItemToSelection(2); // tab_multi_xul2
+ // We can only affect XUL selection, so ARIA selection won't change.
+ testSelectableSelection(select, ["tab_multi_aria", "tab_multi_xul2"],
+ "select tab_multi_xul2: ");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1480058"
+ title="XUL tabs don't support ARIA selection">
+ Mozilla Bug 1480058
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tabbox>
+ <tabs id="tabs_single">
+ <tab id="tab_single1" label="tab1" selected="true"/>
+ <tab id="tab_single2" label="tab2"/>
+ </tabs>
+ </tabbox>
+
+ <tabbox>
+ <tabs id="tabs_multi" aria-multiselectable="true">
+ <tab id="tab_multi_xul1" label="tab1" selected="true"/>
+ <tab id="tab_multi_aria" label="tab2" aria-selected="true"/>
+ <tab id="tab_multi_xul2" label="tab3"/>
+ </tabs>
+ </tabbox>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/selectable/test_tree.xhtml b/accessible/tests/mochitest/selectable/test_tree.xhtml
new file mode 100644
index 0000000000..5527625b24
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_tree.xhtml
@@ -0,0 +1,171 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../treeview.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL tree selectable tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../selectable.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+
+ /**
+ * Event queue invoker object to test accessible states for XUL tree
+ * accessible.
+ */
+ function statesChecker(aTreeID, aView)
+ {
+ this.DOMNode = getNode(aTreeID);
+
+ this.invoke = function invoke()
+ {
+ this.DOMNode.view = aView;
+ }
+ this.check = function check()
+ {
+ var tree = getAccessible(this.DOMNode);
+
+ var isTreeMultiSelectable = false;
+ var seltype = this.DOMNode.getAttribute("seltype");
+ if (seltype != "single")
+ isTreeMultiSelectable = true;
+
+ // selectAll
+ var accSelectable = getAccessible(this.DOMNode,
+ [nsIAccessibleSelectable]);
+ ok(accSelectable, "tree is not selectable!");
+ if (accSelectable) {
+ is(accSelectable.selectAll(), isTreeMultiSelectable,
+ "SelectAll is not correct for seltype: " + seltype);
+ }
+
+ var selectedChildren = [];
+ if (isTreeMultiSelectable) {
+ var rows = tree.children;
+ for (var i = 0; i < rows.length; i++) {
+ var row = rows.queryElementAt(i, nsIAccessible);
+ if (getRole(row) == ROLE_OUTLINEITEM || getRole(row) == ROLE_ROW)
+ selectedChildren.push(row);
+ }
+ }
+ testSelectableSelection(accSelectable, selectedChildren,
+ "selectAll test. ");
+
+ // unselectAll
+ accSelectable.unselectAll();
+ testSelectableSelection(accSelectable, [], "unselectAll test. ");
+
+ // addItemToSelection
+ accSelectable.addItemToSelection(1);
+ accSelectable.addItemToSelection(3);
+
+ selectedChildren = isTreeMultiSelectable ?
+ [ accSelectable.getChildAt(2), accSelectable.getChildAt(4) ] :
+ [ accSelectable.getChildAt(2) ];
+ testSelectableSelection(accSelectable, selectedChildren,
+ "addItemToSelection test. ");
+
+ // removeItemFromSelection
+ accSelectable.removeItemFromSelection(1);
+
+ selectedChildren = isTreeMultiSelectable ?
+ [ accSelectable.getChildAt(4) ] : [ ];
+ testSelectableSelection(accSelectable, selectedChildren,
+ "removeItemFromSelection test. ");
+ }
+
+ this.getID = function getID()
+ {
+ "tree processor for " + prettyName(aTreeID);
+ }
+ }
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue(EVENT_REORDER);
+ gQueue.push(new statesChecker("tree", new nsTreeTreeView()));
+ gQueue.push(new statesChecker("treesingle", new nsTreeTreeView()));
+ gQueue.push(new statesChecker("tabletree", new nsTreeTreeView()));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=523118"
+ title="we mistake 'cell' and text' xul tree seltypes for multiselects">
+ Mozilla Bug 523118
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=624977"
+ title="Optimize nsXulTreeAccessible selectedItems()">
+ Mozilla Bug 624977
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="treesingle" flex="1" seltype="single">
+ <treecols>
+ <treecol id="col_single" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="tabletree" flex="1" editable="true">
+ <treecols>
+ <treecol id="tabletree_col1" cycler="true" label="cycler"/>
+ <treecol id="tabletree_col2" flex="1" primary="true" label="column1"/>
+ <treecol id="tabletree_col3" flex="1" label="column2"/>
+ <treecol id="tabletree_col4" flex="1" label="checker"
+ type="checkbox" editable="true"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/states.js b/accessible/tests/mochitest/states.js
new file mode 100644
index 0000000000..c8ce9dd79d
--- /dev/null
+++ b/accessible/tests/mochitest/states.js
@@ -0,0 +1,365 @@
+// //////////////////////////////////////////////////////////////////////////////
+// Helper functions for accessible states testing.
+//
+// requires:
+// common.js
+// role.js
+//
+// //////////////////////////////////////////////////////////////////////////////
+/* import-globals-from common.js */
+/* import-globals-from role.js */
+
+// //////////////////////////////////////////////////////////////////////////////
+// State constants
+
+// const STATE_BUSY is defined in common.js
+const STATE_ANIMATED = nsIAccessibleStates.STATE_ANIMATED;
+const STATE_CHECKED = nsIAccessibleStates.STATE_CHECKED;
+const STATE_CHECKABLE = nsIAccessibleStates.STATE_CHECKABLE;
+const STATE_COLLAPSED = nsIAccessibleStates.STATE_COLLAPSED;
+const STATE_DEFAULT = nsIAccessibleStates.STATE_DEFAULT;
+const STATE_EXPANDED = nsIAccessibleStates.STATE_EXPANDED;
+const STATE_EXTSELECTABLE = nsIAccessibleStates.STATE_EXTSELECTABLE;
+const STATE_FLOATING = nsIAccessibleStates.STATE_FLOATING;
+const STATE_FOCUSABLE = nsIAccessibleStates.STATE_FOCUSABLE;
+const STATE_FOCUSED = nsIAccessibleStates.STATE_FOCUSED;
+const STATE_HASPOPUP = nsIAccessibleStates.STATE_HASPOPUP;
+const STATE_INVALID = nsIAccessibleStates.STATE_INVALID;
+const STATE_INVISIBLE = nsIAccessibleStates.STATE_INVISIBLE;
+const STATE_LINKED = nsIAccessibleStates.STATE_LINKED;
+const STATE_MIXED = nsIAccessibleStates.STATE_MIXED;
+const STATE_MULTISELECTABLE = nsIAccessibleStates.STATE_MULTISELECTABLE;
+const STATE_OFFSCREEN = nsIAccessibleStates.STATE_OFFSCREEN;
+const STATE_PRESSED = nsIAccessibleStates.STATE_PRESSED;
+const STATE_PROTECTED = nsIAccessibleStates.STATE_PROTECTED;
+const STATE_READONLY = nsIAccessibleStates.STATE_READONLY;
+const STATE_REQUIRED = nsIAccessibleStates.STATE_REQUIRED;
+const STATE_SELECTABLE = nsIAccessibleStates.STATE_SELECTABLE;
+const STATE_SELECTED = nsIAccessibleStates.STATE_SELECTED;
+const STATE_TRAVERSED = nsIAccessibleStates.STATE_TRAVERSED;
+const STATE_UNAVAILABLE = nsIAccessibleStates.STATE_UNAVAILABLE;
+
+const EXT_STATE_ACTIVE = nsIAccessibleStates.EXT_STATE_ACTIVE;
+const EXT_STATE_CURRENT = nsIAccessibleStates.EXT_STATE_CURRENT;
+const EXT_STATE_DEFUNCT = nsIAccessibleStates.EXT_STATE_DEFUNCT;
+const EXT_STATE_EDITABLE = nsIAccessibleStates.EXT_STATE_EDITABLE;
+const EXT_STATE_ENABLED = nsIAccessibleStates.EXT_STATE_ENABLED;
+const EXT_STATE_EXPANDABLE = nsIAccessibleStates.EXT_STATE_EXPANDABLE;
+const EXT_STATE_HORIZONTAL = nsIAccessibleStates.EXT_STATE_HORIZONTAL;
+const EXT_STATE_MODAL = nsIAccessibleStates.EXT_STATE_MODAL;
+const EXT_STATE_MULTI_LINE = nsIAccessibleStates.EXT_STATE_MULTI_LINE;
+const EXT_STATE_PINNED = nsIAccessibleStates.EXT_STATE_PINNED;
+const EXT_STATE_SENSITIVE = nsIAccessibleStates.EXT_STATE_SENSITIVE;
+const EXT_STATE_SINGLE_LINE = nsIAccessibleStates.EXT_STATE_SINGLE_LINE;
+const EXT_STATE_STALE = nsIAccessibleStates.EXT_STATE_STALE;
+const EXT_STATE_SUPPORTS_AUTOCOMPLETION =
+ nsIAccessibleStates.EXT_STATE_SUPPORTS_AUTOCOMPLETION;
+const EXT_STATE_VERTICAL = nsIAccessibleStates.EXT_STATE_VERTICAL;
+const EXT_STATE_SELECTABLE_TEXT = nsIAccessibleStates.EXT_STATE_SELECTABLE_TEXT;
+const EXT_STATE_OPAQUE = nsIAccessibleStates.EXT_STATE_OPAQUE;
+
+const kOrdinalState = false;
+const kExtraState = 1;
+
+// //////////////////////////////////////////////////////////////////////////////
+// Test functions
+
+/**
+ * Tests the states and extra states of the given accessible.
+ * Also tests for unwanted states and extra states.
+ * In addition, the function performs a few plausibility checks derived from the
+ * sstates and extra states passed in.
+ *
+ * @param aAccOrElmOrID The accessible, DOM element or ID to be tested.
+ * @param aState The state bits that are wanted.
+ * @param aExtraState The extra state bits that are wanted.
+ * @param aAbsentState State bits that are not wanted.
+ * @param aAbsentExtraState Extra state bits that are not wanted.
+ * @param aTestName The test name.
+ */
+function testStates(
+ aAccOrElmOrID,
+ aState,
+ aExtraState,
+ aAbsentState,
+ aAbsentExtraState,
+ aTestName
+) {
+ var [state, extraState] = getStates(aAccOrElmOrID);
+ var role = getRole(aAccOrElmOrID);
+ var id =
+ prettyName(aAccOrElmOrID) + (aTestName ? " [" + aTestName + "]" : "");
+
+ // Primary test.
+ if (aState) {
+ isState(state & aState, aState, false, "wrong state bits for " + id + "!");
+ }
+
+ if (aExtraState) {
+ isState(
+ extraState & aExtraState,
+ aExtraState,
+ true,
+ "wrong extra state bits for " + id + "!"
+ );
+ }
+
+ if (aAbsentState) {
+ isState(
+ state & aAbsentState,
+ 0,
+ false,
+ "state bits should not be present in ID " + id + "!"
+ );
+ }
+
+ if (aAbsentExtraState) {
+ isState(
+ extraState & aAbsentExtraState,
+ 0,
+ true,
+ "extraState bits should not be present in ID " + id + "!"
+ );
+ }
+
+ // Additional test.
+
+ // focused/focusable
+ if (state & STATE_FOCUSED) {
+ isState(
+ state & STATE_FOCUSABLE,
+ STATE_FOCUSABLE,
+ false,
+ "Focused " + id + " must be focusable!"
+ );
+ }
+
+ if (aAbsentState && aAbsentState & STATE_FOCUSABLE) {
+ isState(
+ state & STATE_FOCUSED,
+ 0,
+ false,
+ "Not focusable " + id + " must be not focused!"
+ );
+ }
+
+ // multiline/singleline
+ if (extraState & EXT_STATE_MULTI_LINE) {
+ isState(
+ extraState & EXT_STATE_SINGLE_LINE,
+ 0,
+ true,
+ "Multiline " + id + " cannot be singleline!"
+ );
+ }
+
+ if (extraState & EXT_STATE_SINGLE_LINE) {
+ isState(
+ extraState & EXT_STATE_MULTI_LINE,
+ 0,
+ true,
+ "Singleline " + id + " cannot be multiline!"
+ );
+ }
+
+ // expanded/collapsed/expandable
+ if (state & STATE_COLLAPSED || state & STATE_EXPANDED) {
+ isState(
+ extraState & EXT_STATE_EXPANDABLE,
+ EXT_STATE_EXPANDABLE,
+ true,
+ "Collapsed or expanded " + id + " must be expandable!"
+ );
+ }
+
+ if (state & STATE_COLLAPSED) {
+ isState(
+ state & STATE_EXPANDED,
+ 0,
+ false,
+ "Collapsed " + id + " cannot be expanded!"
+ );
+ }
+
+ if (state & STATE_EXPANDED) {
+ isState(
+ state & STATE_COLLAPSED,
+ 0,
+ false,
+ "Expanded " + id + " cannot be collapsed!"
+ );
+ }
+
+ if (aAbsentState && extraState & EXT_STATE_EXPANDABLE) {
+ if (aAbsentState & STATE_EXPANDED) {
+ isState(
+ state & STATE_COLLAPSED,
+ STATE_COLLAPSED,
+ false,
+ "Not expanded " + id + " must be collapsed!"
+ );
+ } else if (aAbsentState & STATE_COLLAPSED) {
+ isState(
+ state & STATE_EXPANDED,
+ STATE_EXPANDED,
+ false,
+ "Not collapsed " + id + " must be expanded!"
+ );
+ }
+ }
+
+ // checked/mixed/checkable
+ if (
+ state & STATE_CHECKED ||
+ (state & STATE_MIXED &&
+ role != ROLE_TOGGLE_BUTTON &&
+ role != ROLE_PROGRESSBAR)
+ ) {
+ isState(
+ state & STATE_CHECKABLE,
+ STATE_CHECKABLE,
+ false,
+ "Checked or mixed element must be checkable!"
+ );
+ }
+
+ if (state & STATE_CHECKED) {
+ isState(
+ state & STATE_MIXED,
+ 0,
+ false,
+ "Checked element cannot be state mixed!"
+ );
+ }
+
+ if (state & STATE_MIXED) {
+ isState(
+ state & STATE_CHECKED,
+ 0,
+ false,
+ "Mixed element cannot be state checked!"
+ );
+ }
+
+ // selected/selectable
+ if (state & STATE_SELECTED && !(aAbsentState & STATE_SELECTABLE)) {
+ isState(
+ state & STATE_SELECTABLE,
+ STATE_SELECTABLE,
+ false,
+ "Selected element must be selectable!"
+ );
+ }
+}
+
+/**
+ * Tests an accessible and its sub tree for the passed in state bits.
+ * Used to make sure that states are propagated to descendants, for example the
+ * STATE_UNAVAILABLE from a container to its children.
+ *
+ * @param aAccOrElmOrID The accessible, DOM element or ID to be tested.
+ * @param aState The state bits that are wanted.
+ * @param aExtraState The extra state bits that are wanted.
+ * @param aAbsentState State bits that are not wanted.
+ */
+function testStatesInSubtree(aAccOrElmOrID, aState, aExtraState, aAbsentState) {
+ // test accessible and its subtree for propagated states.
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc) {
+ return;
+ }
+
+ if (getRole(acc) != ROLE_TEXT_LEAF) {
+ // Right now, text leafs don't get tested because the states are not being
+ // propagated.
+ testStates(acc, aState, aExtraState, aAbsentState);
+ }
+
+ // Iterate over its children to see if the state got propagated.
+ var children = null;
+ try {
+ children = acc.children;
+ } catch (e) {}
+ ok(children, "Could not get children for " + aAccOrElmOrID + "!");
+
+ if (children) {
+ for (var i = 0; i < children.length; i++) {
+ var childAcc = children.queryElementAt(i, nsIAccessible);
+ testStatesInSubtree(childAcc, aState, aExtraState, aAbsentState);
+ }
+ }
+}
+
+/**
+ * Fails if no defunct state on the accessible.
+ */
+function testIsDefunct(aAccessible, aTestName) {
+ var id = prettyName(aAccessible) + (aTestName ? " [" + aTestName + "]" : "");
+ var [, /* state*/ extraState] = getStates(aAccessible);
+ isState(
+ extraState & EXT_STATE_DEFUNCT,
+ EXT_STATE_DEFUNCT,
+ true,
+ "no defuct state for " + id + "!"
+ );
+}
+
+function getStringStates(aAccOrElmOrID) {
+ var [state, extraState] = getStates(aAccOrElmOrID);
+ return statesToString(state, extraState);
+}
+
+function getStates(aAccOrElmOrID) {
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc) {
+ return [0, 0];
+ }
+
+ var state = {},
+ extraState = {};
+ acc.getState(state, extraState);
+
+ return [state.value, extraState.value];
+}
+
+/**
+ * Return true if the accessible has given states.
+ */
+function hasState(aAccOrElmOrID, aState, aExtraState) {
+ var [state, exstate] = getStates(aAccOrElmOrID);
+ return (
+ (aState ? state & aState : true) &&
+ (aExtraState ? exstate & aExtraState : true)
+ );
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Private implementation details
+
+/**
+ * Analogy of SimpleTest.is function used to compare states.
+ */
+function isState(aState1, aState2, aIsExtraStates, aMsg) {
+ if (aState1 == aState2) {
+ ok(true, aMsg);
+ return;
+ }
+
+ var got = "0";
+ if (aState1) {
+ got = statesToString(
+ aIsExtraStates ? 0 : aState1,
+ aIsExtraStates ? aState1 : 0
+ );
+ }
+
+ var expected = "0";
+ if (aState2) {
+ expected = statesToString(
+ aIsExtraStates ? 0 : aState2,
+ aIsExtraStates ? aState2 : 0
+ );
+ }
+
+ ok(false, aMsg + "got '" + got + "', expected '" + expected + "'");
+}
diff --git a/accessible/tests/mochitest/states/a11y.ini b/accessible/tests/mochitest/states/a11y.ini
new file mode 100644
index 0000000000..06e7a5a061
--- /dev/null
+++ b/accessible/tests/mochitest/states/a11y.ini
@@ -0,0 +1,36 @@
+[DEFAULT]
+support-files =
+ z_frames.html
+ z_frames_article.html
+ z_frames_checkbox.html
+ z_frames_textbox.html
+ z_frames_update.html
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/dumbfile.zip
+ !/accessible/tests/mochitest/formimage.png
+ !/accessible/tests/mochitest/treeview.css
+
+[test_aria.html]
+[test_aria.xhtml]
+[test_aria_imgmap.html]
+[test_aria_widgetitems.html]
+[test_buttons.html]
+[test_controls.html]
+[test_controls.xhtml]
+[test_doc.html]
+[test_doc_busy.html]
+[test_docarticle.html]
+[test_editablebody.html]
+[test_expandable.xhtml]
+[test_frames.html]
+[test_inputs.html]
+[test_link.html]
+[test_popup.xhtml]
+[test_selects.html]
+[test_stale.html]
+[test_tabs.xhtml]
+[test_textbox.xhtml]
+[test_tree.xhtml]
+[test_visibility.html]
+[test_visibility.xhtml]
+skip-if = asan # Bug 1199631
diff --git a/accessible/tests/mochitest/states/test_aria.html b/accessible/tests/mochitest/states/test_aria.html
new file mode 100644
index 0000000000..da5df6fa09
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_aria.html
@@ -0,0 +1,655 @@
+<html>
+
+<head>
+ <title>ARIA based nsIAccessible states testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style type="text/css">
+ .offscreen {
+ position: absolute;
+ left: -5000px;
+ top: -5000px;
+ height: 100px;
+ width: 100px;
+ }
+ </style>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function testAriaDisabledTree(aAccOrElmOrID) {
+ // test accessible and its subtree for propagated state.
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc)
+ return;
+
+ var [state /* extraState */] = getStates(aAccOrElmOrID);
+ if (state & STATE_UNAVAILABLE) {
+ var role = getRole(acc);
+ if (role != ROLE_GROUPING) {
+ testStates(acc, STATE_FOCUSABLE);
+ }
+ }
+
+ // Iterate over its children to see if the state got propagated.
+ var children = null;
+ try {
+ children = acc.children;
+ } catch (e) {}
+ ok(children, "Could not get children for " + aAccOrElmOrID + "!");
+
+ if (children) {
+ for (var i = 0; i < children.length; i++) {
+ var childAcc = children.queryElementAt(i, nsIAccessible);
+ testAriaDisabledTree(childAcc);
+ }
+ }
+ }
+
+ function doTest() {
+ // aria_autocomplete
+ testStates("textbox_autocomplete_inline", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("textbox_autocomplete_list", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("textbox_autocomplete_both", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("combobox_autocomplete_inline", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("combobox_autocomplete_list", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("combobox_autocomplete_both", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+
+ testStates("htmltext_autocomplete_list", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("htmltextarea_autocomplete_list", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+
+ // aria-busy
+ testStates("textbox_busy_false", 0, 0, STATE_BUSY);
+ testStates("textbox_busy_true", STATE_BUSY);
+ testStates("textbox_busy_error", STATE_INVALID);
+
+ // aria-expanded
+ testStates("combobox", STATE_COLLAPSED);
+ testStates("combobox_expanded", STATE_EXPANDED);
+
+ // tri-state checkbox
+ var checkboxElem = getNode("check1");
+ if (checkboxElem) {
+ testStates(checkboxElem, STATE_CHECKED);
+ checkboxElem.checked = false;
+ testStates(checkboxElem, 0, 0, STATE_CHECKED);
+ checkboxElem.indeterminate = true;
+ testStates(checkboxElem, STATE_MIXED, 0);
+ }
+
+ // aria-checked
+ testStates("aria_checked_checkbox", STATE_CHECKED);
+ testStates("aria_mixed_checkbox", STATE_MIXED);
+ testStates("aria_checked_switch", STATE_CHECKED);
+ testStates("aria_mixed_switch", 0, 0, STATE_MIXED); // unsupported
+ testStates("aria_mixed_switch", 0, 0, STATE_CHECKED); // not checked due to being unsupported
+
+ // test disabled group and all its descendants to see if they are
+ // disabled, too. See bug 429285.
+ testAriaDisabledTree("group");
+
+ // aria-modal
+ testStates("aria_modal", 0, EXT_STATE_MODAL);
+ testStates("aria_modal_false", 0, 0, 0, EXT_STATE_MODAL);
+
+ // aria-multiline
+ testStates("aria_multiline_textbox", 0, EXT_STATE_MULTI_LINE);
+
+ // aria-multiselectable
+ testStates("aria_multiselectable_listbox",
+ STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+ testStates("aria_multiselectable_tablist",
+ STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+
+ // aria-pressed
+ testStates("aria_pressed_button", STATE_PRESSED, 0, STATE_CHECKABLE);
+ testStates("aria_pressed_native_button", STATE_PRESSED, 0, STATE_CHECKABLE);
+
+ // aria-readonly
+ testStates("aria_readonly_textbox", STATE_READONLY);
+
+ // readonly on grid and gridcell
+ testStates("aria_grid_default", 0, 0,
+ STATE_READONLY, 0);
+ testStates("aria_grid_default_colheader_readonly", STATE_READONLY, 0,
+ 0, 0);
+ testStates("aria_grid_default_colheader_inherited", 0, 0,
+ STATE_READONLY, 0);
+ testStates("aria_grid_default_rowheader_readonly", STATE_READONLY, 0,
+ 0, 0);
+ testStates("aria_grid_default_rowheader_inherited", 0, 0,
+ STATE_READONLY, 0);
+ testStates("aria_grid_default_cell_readonly", STATE_READONLY, 0,
+ 0, 0);
+ testStates("aria_grid_default_cell_inherited", 0, 0,
+ STATE_READONLY, 0);
+
+ testStates("aria_grid_readonly", STATE_READONLY, 0,
+ 0, 0);
+ testStates("aria_grid_readonly_colheader_editable", 0, 0,
+ STATE_READONLY, 0);
+ testStates("aria_grid_readonly_colheader_inherited", STATE_READONLY, 0,
+ 0, 0);
+ testStates("aria_grid_readonly_rowheader_editable", 0, 0,
+ STATE_READONLY, 0);
+ testStates("aria_grid_readonly_rowheader_inherited", STATE_READONLY, 0,
+ 0, 0);
+ testStates("aria_grid_readonly_cell_editable", 0, 0,
+ STATE_READONLY, 0);
+ testStates("aria_grid_readonly_cell_inherited", STATE_READONLY, 0,
+ 0, 0);
+
+ // readonly on treegrid and gridcell
+ testStates("aria_treegrid_default", 0, 0,
+ STATE_READONLY, 0);
+ testStates("aria_treegrid_default_colheader_readonly", STATE_READONLY, 0,
+ 0, 0);
+ testStates("aria_treegrid_default_colheader_inherited", 0, 0,
+ STATE_READONLY, 0);
+ testStates("aria_treegrid_default_rowheader_readonly", STATE_READONLY, 0,
+ 0, 0);
+ testStates("aria_treegrid_default_rowheader_inherited", 0, 0,
+ STATE_READONLY, 0);
+ testStates("aria_treegrid_default_cell_readonly", STATE_READONLY, 0,
+ 0, 0);
+ testStates("aria_treegrid_default_cell_inherited", 0, 0,
+ STATE_READONLY, 0);
+
+ testStates("aria_treegrid_readonly", STATE_READONLY, 0,
+ 0, 0);
+ testStates("aria_treegrid_readonly_colheader_editable", 0, 0,
+ STATE_READONLY, 0);
+ testStates("aria_treegrid_readonly_colheader_inherited", STATE_READONLY, 0,
+ 0, 0);
+ testStates("aria_treegrid_readonly_rowheader_editable", 0, 0,
+ STATE_READONLY, 0);
+ testStates("aria_treegrid_readonly_rowheader_inherited", STATE_READONLY, 0,
+ 0, 0);
+ testStates("aria_treegrid_readonly_cell_editable", 0, 0,
+ STATE_READONLY, 0);
+ testStates("aria_treegrid_readonly_cell_inherited", STATE_READONLY, 0,
+ 0, 0);
+
+ // aria-readonly on directory
+ testStates("aria_directory", STATE_READONLY);
+
+ // aria-selectable
+ testStates("aria_selectable_listitem", STATE_SELECTABLE | STATE_SELECTED);
+
+ // active state caused by aria-activedescendant
+ testStates("as_item1", 0, EXT_STATE_ACTIVE);
+ testStates("as_item2", 0, 0, 0, EXT_STATE_ACTIVE);
+
+ // universal ARIA properties inherited from file input control
+ var fileBrowseButton = getAccessible("fileinput").firstChild;
+ testStates(fileBrowseButton,
+ STATE_BUSY | STATE_UNAVAILABLE | STATE_REQUIRED | STATE_HASPOPUP | STATE_INVALID);
+ // No states on the label.
+
+ // offscreen test
+ testStates("aria_offscreen_textbox", STATE_OFFSCREEN);
+
+ //
+ // This section tests aria roles on links/anchors for underlying
+ // HTMLLinkAccessible creation. (see closed bug 494807)
+ //
+
+ // strong roles
+ testStates("aria_menuitem_link", 0, 0, STATE_LINKED);
+ testStates("aria_button_link", 0, 0, STATE_LINKED);
+ testStates("aria_checkbox_link", 0, 0, STATE_LINKED);
+
+ // strong landmark
+ testStates("aria_application_link", 0, 0, STATE_LINKED);
+ testStates("aria_application_anchor", 0, 0, STATE_SELECTABLE);
+
+ // strange cases
+ testStates("aria_link_link", STATE_LINKED);
+ testStates("aria_link_anchor", STATE_SELECTABLE);
+
+ // some landmarks that break accessibility for these native elements
+ // Note that these are illegal uses by web authors as per WAI-ARIA in HTML
+ testStates("aria_main_link", 0, 0, STATE_LINKED);
+ testStates("aria_navigation_link", 0, 0, STATE_LINKED);
+ testStates("aria_main_anchor", 0, 0, STATE_SELECTABLE);
+ testStates("aria_navigation_anchor", 0, 0, STATE_SELECTABLE);
+
+ // aria-orientation
+ testStates("aria_combobox", 0, 0, 0, EXT_STATE_HORIZONTAL | EXT_STATE_VERTICAL);
+ testStates("aria_hcombobox", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vcombobox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_listbox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_hlistbox", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vlistbox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_menu", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_hmenu", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vmenu", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_menubar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_hmenubar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vmenubar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_radiogroup", 0, 0, 0, EXT_STATE_HORIZONTAL | EXT_STATE_VERTICAL);
+ testStates("aria_hradiogroup", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vradiogroup", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_scrollbar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_hscrollbar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vscrollbar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_separator", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_hseparator", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vseparator", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_slider", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_hslider", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vslider", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_tablist", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_htablist", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vtablist", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_toolbar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_htoolbar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vtoolbar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_tree", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_htree", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vtree", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_treegrid", 0, 0, 0, EXT_STATE_HORIZONTAL | EXT_STATE_VERTICAL);
+ testStates("aria_htreegrid", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vtreegrid", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+
+ // indeterminate ARIA progressbars (no aria-valuenow or aria-valuetext attribute)
+ // should expose mixed state
+ testStates("aria_progressbar", STATE_MIXED);
+ testStates("aria_progressbar_valuenow", 0, 0, STATE_MIXED);
+ testStates("aria_progressbar_valuetext", 0, 0, STATE_MIXED);
+
+ // aria-current
+ testStates("current_page_1", 0, EXT_STATE_CURRENT);
+ testStates("page_2", 0, 0, EXT_STATE_CURRENT);
+ testStates("page_3", 0, 0, EXT_STATE_CURRENT);
+ testStates("page_4", 0, 0, EXT_STATE_CURRENT);
+ testStates("current_foo", 0, EXT_STATE_CURRENT);
+
+ testStates("aria_listbox", STATE_FOCUSABLE);
+ testStates("aria_grid", STATE_FOCUSABLE);
+ testStates("aria_tree", STATE_FOCUSABLE);
+ testStates("aria_treegrid", STATE_FOCUSABLE);
+ testStates("aria_listbox_disabled", 0, 0, STATE_FOCUSABLE);
+ testStates("aria_grid_disabled", 0, 0, STATE_FOCUSABLE);
+ testStates("aria_tree_disabled", 0, 0, STATE_FOCUSABLE);
+ testStates("aria_treegrid_disabled", 0, 0, STATE_FOCUSABLE);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=457219"
+ title="nsIAccessible states testing">
+ Mozilla Bug 457219
+ </a><br />
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=429285"
+ title="Propagate aria-disabled to descendants">
+ Mozilla Bug 429285
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=457226"
+ title="Mochitests for ARIA states">
+ Mozilla Bug 457226
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=499653"
+ title="Unify ARIA state attributes mapping rules">
+ Mozilla Bug 499653
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=681674"
+ title="aria-autocomplete not supported on standard form text input controls">
+ Mozilla Bug 681674
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=681674"
+ title="aria-orientation should be applied to separator and slider roles">
+ Mozilla Bug 681674
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=689847"
+ title="Expose active state on current item of selectable widgets">
+ Mozilla Bug 689847
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017"
+ title="File input control should be propogate states to descendants">
+ Mozilla Bug 699017
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=690199"
+ title="ARIA select widget should expose focusable state regardless the way they manage its children">
+ Mozilla Bug 690199
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=740851"
+ title="ARIA undetermined progressmeters should expose mixed state">
+ Mozilla Bug 740851
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=762876"
+ title="fix default horizontal / vertical state of role=scrollbar and ensure only one of horizontal / vertical states is exposed">
+ Mozilla Bug 762876
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=892091"
+ title="ARIA treegrid should be editable by default">
+ Bug 892091
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=835121"
+ title="ARIA grid should be editable by default">
+ Mozilla Bug 835121
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=989958"
+ title="Pressed state is not exposed on a button element with aria-pressed attribute">
+ Mozilla Bug 989958
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563"
+ title="Support ARIA 1.1 switch role">
+ Mozilla Bug 1136563
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1355921"
+ title="Elements with a defined, non-false value for aria-current should expose ATK_STATE_ACTIVE">
+ Mozilla Bug 1355921
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="textbox_autocomplete_inline" role="textbox" aria-autocomplete="inline"></div>
+ <div id="textbox_autocomplete_list" role="textbox" aria-autocomplete="list"></div>
+ <div id="textbox_autocomplete_both" role="textbox" aria-autocomplete="both"></div>
+ <div id="combobox_autocomplete_inline" role="combobox" aria-autocomplete="inline"></div>
+ <div id="combobox_autocomplete_list" role="combobox" aria-autocomplete="list"></div>
+ <div id="combobox_autocomplete_both" role="combobox" aria-autocomplete="both"></div>
+
+ <input id="htmltext_autocomplete_list" type="text" aria-autocomplete="list" />
+ <textarea id="htmltextarea_autocomplete_list" aria-autocomplete="list"></textarea>
+
+ <div id="textbox_busy_false" role="textbox" aria-busy="false"></div>
+ <div id="textbox_busy_true" role="textbox" aria-busy="true"></div>
+ <div id="textbox_busy_error" role="textbox" aria-busy="error"></div>
+
+ <div id="combobox" role="combobox">combobox</div>
+ <div id="combobox_expanded" role="combobox"
+ aria-expanded="true">combobox</div>
+
+ <input type="checkbox" id="check1" value="I agree" checked="true"/>
+
+ <div id="aria_checked_checkbox" role="checkbox" aria-checked="true">
+ I agree
+ </div>
+
+ <div id="aria_mixed_checkbox" role="checkbox" aria-checked="mixed">
+ I might agree
+ </div>
+
+ <div id="aria_checked_switch" role="switch" aria-checked="true">
+ I am switched on
+ </div>
+
+ <div id="aria_mixed_switch" role="switch" aria-checked="mixed">
+ I am unsupported
+ </div>
+
+ <div id="aria_modal" aria-modal="true">modal stuff</div>
+ <div id="aria_modal_false" aria-modal="false">non modal stuff</div>div>
+ <div id="aria_multiline_textbox" role="textbox" aria-multiline="true"></div>
+ <div id="aria_multiselectable_listbox" role="listbox" aria-multiselectable="true"></div>
+ <div id="aria_multiselectable_tablist" role="tablist" aria-multiselectable="true"></div>
+ <div id="aria_pressed_button" role="button" aria-pressed="true">Button</div>
+ <button id="aria_pressed_native_button" aria-pressed="true">Button</button>
+
+ <div id="aria_readonly_textbox"
+ role="textbox" aria-readonly="true">This text should be readonly</div>
+
+ <div id="aria_grid_default" role="grid">
+ <div role="row">
+ <div id="aria_grid_default_colheader_readonly"
+ role="columnheader" aria-readonly="true">colheader1</div>
+ <div id="aria_grid_default_colheader_inherited"
+ role="columnheader">colheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_grid_default_rowheader_readonly"
+ role="rowheader" aria-readonly="true">rowheader1</div>
+ <div id="aria_grid_default_rowheader_inherited"
+ role="rowheader">rowheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_grid_default_cell_readonly"
+ role="gridcell" aria-readonly="true">gridcell1</div>
+ <div id="aria_grid_default_cell_inherited"
+ role="gridcell">gridcell2</div>
+ </div>
+ </div>
+
+ <div id="aria_grid_readonly" role="grid" aria-readonly="true">
+ <div role="row">
+ <div id="aria_grid_readonly_colheader_editable"
+ role="columnheader" aria-readonly="false">colheader1</div>
+ <div id="aria_grid_readonly_colheader_inherited"
+ role="columnheader">colheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_grid_readonly_rowheader_editable"
+ role="rowheader" aria-readonly="false">rowheader1</div>
+ <div id="aria_grid_readonly_rowheader_inherited"
+ role="rowheader">rowheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_grid_readonly_cell_editable"
+ role="gridcell" aria-readonly="false">gridcell1</div>
+ <div id="aria_grid_readonly_cell_inherited"
+ role="gridcell">gridcell2</div>
+ </div>
+ </div>
+
+ <div id="aria_treegrid_default" role="grid">
+ <div role="row">
+ <div id="aria_treegrid_default_colheader_readonly"
+ role="columnheader" aria-readonly="true">colheader1</div>
+ <div id="aria_treegrid_default_colheader_inherited"
+ role="columnheader">colheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_treegrid_default_rowheader_readonly"
+ role="rowheader" aria-readonly="true">rowheader1</div>
+ <div id="aria_treegrid_default_rowheader_inherited"
+ role="rowheader">rowheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_treegrid_default_cell_readonly"
+ role="gridcell" aria-readonly="true">gridcell1</div>
+ <div id="aria_treegrid_default_cell_inherited"
+ role="gridcell">gridcell2</div>
+ </div>
+ </div>
+
+ <div id="aria_treegrid_readonly" role="grid" aria-readonly="true">
+ <div role="row">
+ <div id="aria_treegrid_readonly_colheader_editable"
+ role="columnheader" aria-readonly="false">colheader1</div>
+ <div id="aria_treegrid_readonly_colheader_inherited"
+ role="columnheader">colheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_treegrid_readonly_rowheader_editable"
+ role="rowheader" aria-readonly="false">rowheader1</div>
+ <div id="aria_treegrid_readonly_rowheader_inherited"
+ role="rowheader">rowheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_treegrid_readonly_cell_editable"
+ role="gridcell" aria-readonly="false">gridcell1</div>
+ <div id="aria_treegrid_readonly_cell_inherited"
+ role="gridcell">gridcell2</div>
+ </div>
+ </div>
+
+ <div role="listbox">
+ <div id="aria_selectable_listitem" role="option" aria-selected="true">Item1</div>
+ </div>
+
+ <!-- Test that aria-disabled state gets propagated to all descendants -->
+ <div id="group" role="group" aria-disabled="true">
+ <button>hi</button>
+ <div tabindex="0" role="listbox" aria-activedescendant="item1"
+ aria-owns="item5">
+ <div role="option" id="item1">Item 1</div>
+ <div role="option" id="item2">Item 2</div>
+ <div role="option" id="item3">Item 3</div>
+ <div role="option" id="item4">Item 4</div>
+ </div>
+ <div role="slider" tabindex="0">A slider</div>
+ </div>
+ <div role="option" id="item5">Item 5</div>
+
+ <!-- Test active state -->
+ <div id="as_listbox" tabindex="0" role="listbox"
+ aria-activedescendant="as_item1">
+ <div role="option" id="as_item1">Item 1</div>
+ <div role="option" id="as_item2">Item 2</div>
+ </div>
+
+ <!-- universal ARIA properties should be inherited by text field of file input -->
+ <input type="file" id="fileinput"
+ aria-busy="true"
+ aria-disabled="true"
+ aria-required="true"
+ aria-haspopup="true"
+ aria-invalid="true">
+
+ <div id="offscreen_log" role="log" class="offscreen">
+ <div id="aria_offscreen_textbox" role="textbox" aria-readonly="true">This text should be offscreen</div>
+ </div>
+
+ <a id="aria_menuitem_link" role="menuitem" href="foo">menuitem</a>
+ <a id="aria_button_link" role="button" href="foo">button</a>
+ <a id="aria_checkbox_link" role="checkbox" href="foo">checkbox</a>
+
+ <!-- strange edge case: please don't do this in the wild -->
+ <a id="aria_link_link" role="link" href="foo">link</a>
+ <a id="aria_link_anchor" role="link" name="link_anchor">link</a>
+
+ <!-- landmarks: links -->
+ <a id="aria_application_link" role="application" href="foo">app</a>
+ <a id="aria_main_link" role="main" href="foo">main</a>
+ <a id="aria_navigation_link" role="navigation" href="foo">nav</a>
+
+ <!-- landmarks: anchors -->
+ <a id="aria_application_anchor" role="application" name="app_anchor">app</a>
+ <a id="aria_main_anchor" role="main" name="main_anchor">main</a>
+ <a id="aria_navigation_anchor" role="navigation" name="nav_anchor">nav</a>
+
+ <!-- aria-orientation -->
+ <div id="aria_combobox" role="combobox">combobox</div>
+ <div id="aria_hcombobox" role="combobox" aria-orientation="horizontal">horizontal combobox</div>
+ <div id="aria_vcombobox" role="combobox" aria-orientation="vertical">vertical combobox</div>
+ <div id="aria_listbox" role="listbox">listbox</div>
+ <div id="aria_hlistbox" role="listbox" aria-orientation="horizontal">horizontal listbox</div>
+ <div id="aria_vlistbox" role="listbox" aria-orientation="vertical">vertical listbox</div>
+ <div id="aria_menu" role="menu">menu</div>
+ <div id="aria_hmenu" role="menu" aria-orientation="horizontal">horizontal menu</div>
+ <div id="aria_vmenu" role="menu" aria-orientation="vertical">vertical menu</div>
+ <div id="aria_menubar" role="menubar">menubar</div>
+ <div id="aria_hmenubar" role="menubar" aria-orientation="horizontal">horizontal menubar</div>
+ <div id="aria_vmenubar" role="menubar" aria-orientation="vertical">vertical menubar</div>
+ <div id="aria_radiogroup" role="radiogroup">radiogroup</div>
+ <div id="aria_hradiogroup" role="radiogroup" aria-orientation="horizontal">horizontal radiogroup</div>
+ <div id="aria_vradiogroup" role="radiogroup" aria-orientation="vertical">vertical radiogroup</div>
+ <div id="aria_scrollbar" role="scrollbar">scrollbar</div>
+ <div id="aria_hscrollbar" role="scrollbar" aria-orientation="horizontal">horizontal scrollbar</div>
+ <div id="aria_vscrollbar" role="scrollbar" aria-orientation="vertical">vertical scrollbar</div>
+ <div id="aria_separator" role="separator">separator</div>
+ <div id="aria_hseparator" role="separator" aria-orientation="horizontal">horizontal separator</div>
+ <div id="aria_vseparator" role="separator" aria-orientation="vertical">vertical separator</div>
+ <div id="aria_slider" role="slider">slider</div>
+ <div id="aria_hslider" role="slider" aria-orientation="horizontal">horizontal slider</div>
+ <div id="aria_vslider" role="slider" aria-orientation="vertical">vertical slider</div>
+
+ <div id="aria_tablist" role="tablist">tablist</div>
+ <div id="aria_htablist" role="tablist" aria-orientation="horizontal">horizontal tablist</div>
+ <div id="aria_vtablist" role="tablist" aria-orientation="vertical">vertical tablist</div>
+ <div id="aria_toolbar" role="toolbar">toolbar</div>
+ <div id="aria_htoolbar" role="toolbar" aria-orientation="horizontal">horizontal toolbar</div>
+ <div id="aria_vtoolbar" role="toolbar" aria-orientation="vertical">vertical toolbar</div>
+ <div id="aria_tree" role="tree">tree</div>
+ <div id="aria_htree" role="tree" aria-orientation="horizontal">horizontal tree</div>
+ <div id="aria_vtree" role="tree" aria-orientation="vertical">vertical tree</div>
+ <div id="aria_treegrid" role="treegrid">treegrid</div>
+ <div id="aria_htreegrid" role="treegrid" aria-orientation="horizontal">horizontal treegrid</div>
+ <div id="aria_vtreegrid" role="treegrid" aria-orientation="vertical">vertical treegrid</div>
+
+ <!-- indeterminate ARIA progressbars should expose mixed state -->
+ <div id="aria_progressbar" role="progressbar"></div>
+ <div id="aria_progressbar_valuenow" role="progressbar" aria-valuenow="1"></div>
+ <div id="aria_progressbar_valuetext" role="progressbar" aria-valuetext="value"></div>
+
+ <!-- ARIA select widget should expose focusable state regardless the way they manage its children -->
+ <div id="aria_listbox" role="listbox">
+ <div role="option" tabindex="0">A</div>
+ <div role="option" tabindex="0">a</div>
+ </div>
+ <div id="aria_grid" role="grid">
+ <div role="row"><div role="gridcell" tabindex="0">B</div></div></div>
+ <div role="row"><div role="gridcell" tabindex="0">b</div></div></div>
+ <div id="aria_tree" role="tree">
+ <div role="treeitem" tabindex="0">C</div>
+ <div role="treeitem" tabindex="0">c</div>
+ </div>
+ <div id="aria_treegrid" role="treegrid">
+ <div role="row"><div role="gridcell" tabindex="0">D</div></div>
+ <div role="row"><div role="gridcell" tabindex="0">d</div></div>
+ </div>
+ <div id="aria_listbox_disabled" role="listbox" aria-disabled="true">
+ <div role="option">E</div>
+ <div role="option">e</div>
+ </div>
+ <div id="aria_grid_disabled" role="grid" aria-disabled="true">
+ <div role="row"><div role="gridcell">F</div></div>
+ <div role="row"><div role="gridcell">f</div></div>
+ </div>
+ <div id="aria_tree_disabled" role="tree" aria-disabled="true">
+ <div role="treeitem">G</div>
+ <div role="treeitem">g</div>
+ </div>
+ <div id="aria_treegrid_disabled" role="treegrid" aria-disabled="true">
+ <div role="row"><div role="gridcell">H</div></div>
+ <div role="row"><div role="gridcell">h</div></div>
+ </div>
+
+ <!-- Test that directory is readonly -->
+ <div id="aria_directory" role="directory"></div>
+
+ <!-- aria-current -->
+ <div id="current_page_1" role="link" aria-current="page">1</div>
+ <div id="page_2" role="link" aria-current="false">2</div>
+ <div id="page_3" role="link">3</div>
+ <div id="page_4" role="link" aria-current="">4</div>
+ <div id="current_foo" aria-current="foo">foo</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_aria.xhtml b/accessible/tests/mochitest/states/test_aria.xhtml
new file mode 100644
index 0000000000..e42a0ea96d
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_aria.xhtml
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL ARIA state tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ // aria-pressed
+ testStates("pressed_button", STATE_PRESSED, 0, STATE_CHECKABLE);
+
+ testStates("tabs", STATE_MULTISELECTABLE);
+ // Make sure XUL selection works, since aria-selected defaults to false.
+ testStates("tab1", STATE_SELECTED);
+ // aria-selected="true".
+ testStates("tab2", STATE_SELECTED);
+ // Neither.
+ testStates("tab3", 0, 0, STATE_SELECTED);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1033283"
+ title="Expose pressed state on XUL menu toggle buttons">
+ Mozilla Bug 1033283
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <button id="pressed_button" aria-pressed="true" label="I am pressed" />
+
+ <tabbox>
+ <tabs id="tabs" aria-multiselectable="true">
+ <tab id="tab1" label="tab1" selected="true"/>
+ <tab id="tab2" label="tab2" aria-selected="true"/>
+ <tab id="tab3" label="tab3"/>
+ </tabs>
+ </tabbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/states/test_aria_imgmap.html b/accessible/tests/mochitest/states/test_aria_imgmap.html
new file mode 100644
index 0000000000..387e33259e
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_aria_imgmap.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test usemap elements and ARIA</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ // gA11yEventDumpToConsole = true;
+ function doPreTest() {
+ waitForImageMap("imagemap", doTest);
+ }
+
+ function doTest() {
+ var imageMap = getAccessible("imagemap");
+
+ var t1 = imageMap.getChildAt(0);
+ testStates(t1, 0, EXT_STATE_EDITABLE, STATE_LINKED);
+ var t2 = imageMap.getChildAt(1);
+ testStates(t2, 0, EXT_STATE_EDITABLE, STATE_LINKED);
+ var rb1 = imageMap.getChildAt(2);
+ testStates(rb1, (STATE_CHECKABLE | STATE_CHECKED), 0, STATE_LINKED);
+ var rb2 = imageMap.getChildAt(3);
+ testStates(rb2, STATE_CHECKABLE, 0, STATE_CHECKED, STATE_LINKED);
+ var cb1 = imageMap.getChildAt(4);
+ testStates(cb1, (STATE_CHECKABLE | STATE_CHECKED), 0, STATE_LINKED);
+ var cbox = imageMap.getChildAt(5);
+ testStates(cbox, (STATE_HASPOPUP | STATE_COLLAPSED),
+ EXT_STATE_EXPANDABLE, STATE_LINKED);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+
+</head>
+<body>
+
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=548291"
+ title="ARIA states on image maps">
+Mozilla Bug 548291
+</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+
+<img id="imagemap" src="../formimage.png" width="219" height="229" border="0" usemap="#ariaMap">
+<map id="ariaMap" name="ariaMap">
+ <area id="t1" role="textbox" shape="rect" tabindex="0" alt="" title="first name" coords="4,20,108,48" href="#" />
+ <area id="t2" role="textbox" shape="rect" alt="" title="last name" coords="111,21,215,50" href="#" />
+ <area id="rb1" role="radio" aria-checked="true" shape="circle" alt="" title="male" coords="60,75,11" href="#" />
+ <area id="rb2" role="radio" shape="circle" alt="" title="female" coords="73,94,11" href="#" />
+ <area id="cb1" role="checkbox" aria-checked="true" shape="rect" alt="" title="have bike" coords="95,123,118,145" href="#" />
+ <area id="cbox" role="combobox" shape="rect" alt="" title="bike model" coords="120,124,184,146" href="#" />
+ <area id="cb2" role="checkbox" shape="rect" alt="" title="have car" coords="90,145,114,164" href="#" />
+ <area id="cb3" role="checkbox" shape="rect" alt="" title="have airplane" coords="130,163,152,184" href="#" />
+ <area id="b1" role="button" shape="rect" alt="" title="submit" coords="4,198,67,224" href="#" />
+</map>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_aria_widgetitems.html b/accessible/tests/mochitest/states/test_aria_widgetitems.html
new file mode 100644
index 0000000000..c2d546ba01
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_aria_widgetitems.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test ARIA tab accessible selected state</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function focusARIAItem(aID, aIsSelected) {
+ this.DOMNode = getNode(aID);
+
+ this.invoke = function focusARIAItem_invoke() {
+ this.DOMNode.focus();
+ };
+
+ this.check = function focusARIAItem_check(aEvent) {
+ testStates(this.DOMNode, aIsSelected ? STATE_SELECTED : 0, 0,
+ aIsSelected ? 0 : STATE_SELECTED);
+ };
+
+ this.getID = function focusARIAItem_getID() {
+ return "Focused ARIA widget item with aria-selected='" +
+ (aIsSelected ? "true', should" : "false', shouldn't") +
+ " have selected state on " + prettyName(aID);
+ };
+ }
+
+ function focusActiveDescendantItem(aItemID, aWidgetID, aIsSelected) {
+ this.DOMNode = getNode(aItemID);
+ this.widgetDOMNode = getNode(aWidgetID);
+
+ this.invoke = function focusActiveDescendantItem_invoke() {
+ this.widgetDOMNode.setAttribute("aria-activedescendant", aItemID);
+ this.widgetDOMNode.focus();
+ };
+
+ this.check = function focusActiveDescendantItem_check(aEvent) {
+ testStates(this.DOMNode, aIsSelected ? STATE_SELECTED : 0, 0,
+ aIsSelected ? 0 : STATE_SELECTED);
+ };
+
+ this.getID = function tabActiveDescendant_getID() {
+ return "ARIA widget item managed by activedescendant " +
+ (aIsSelected ? "should" : "shouldn't") +
+ " have the selected state on " + prettyName(aItemID);
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest() {
+ // aria-selected
+ testStates("aria_tab1", 0, 0, STATE_SELECTED);
+ testStates("aria_tab2", STATE_SELECTED);
+ testStates("aria_tab3", 0, 0, STATE_SELECTED);
+ testStates("aria_option1", 0, 0, STATE_SELECTED);
+ testStates("aria_option2", STATE_SELECTED);
+ testStates("aria_option3", 0, 0, STATE_SELECTED);
+ testStates("aria_treeitem1", 0, 0, STATE_SELECTED);
+ testStates("aria_treeitem2", STATE_SELECTED);
+ testStates("aria_treeitem3", 0, 0, STATE_SELECTED);
+
+ // selected state when widget item is focused
+ gQueue = new eventQueue(EVENT_FOCUS);
+
+ gQueue.push(new focusARIAItem("aria_tab1", true));
+ gQueue.push(new focusARIAItem("aria_tab2", true));
+ gQueue.push(new focusARIAItem("aria_tab3", false));
+ gQueue.push(new focusARIAItem("aria_option1", true));
+ gQueue.push(new focusARIAItem("aria_option2", true));
+ gQueue.push(new focusARIAItem("aria_option3", false));
+ gQueue.push(new focusARIAItem("aria_treeitem1", true));
+ gQueue.push(new focusARIAItem("aria_treeitem2", true));
+ gQueue.push(new focusARIAItem("aria_treeitem3", false));
+
+ // selected state when widget item is focused (by aria-activedescendant)
+ gQueue.push(new focusActiveDescendantItem("aria_tab5", "aria_tablist2", true));
+ gQueue.push(new focusActiveDescendantItem("aria_tab6", "aria_tablist2", true));
+ gQueue.push(new focusActiveDescendantItem("aria_tab4", "aria_tablist2", false));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=653601"
+ title="aria-selected ignored for ARIA tabs">
+ Mozilla Bug 653601
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=526703"
+ title="Focused widget item should expose selected state by default">
+ Mozilla Bug 526703
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- tab -->
+ <div id="aria_tablist" role="tablist">
+ <div id="aria_tab1" role="tab" tabindex="0">unselected tab</div>
+ <div id="aria_tab2" role="tab" tabindex="0" aria-selected="true">selected tab</div>
+ <div id="aria_tab3" role="tab" tabindex="0" aria-selected="false">focused explicitly unselected tab</div>
+ </div>
+
+ <!-- listbox -->
+ <div id="aria_listbox" role="listbox">
+ <div id="aria_option1" role="option" tabindex="0">unselected option</div>
+ <div id="aria_option2" role="option" tabindex="0" aria-selected="true">selected option</div>
+ <div id="aria_option3" role="option" tabindex="0" aria-selected="false">focused explicitly unselected option</div>
+ </div>
+
+ <!-- tree -->
+ <div id="aria_tree" role="tree">
+ <div id="aria_treeitem1" role="treeitem" tabindex="0">unselected treeitem</div>
+ <div id="aria_treeitem2" role="treeitem" tabindex="0" aria-selected="true">selected treeitem</div>
+ <div id="aria_treeitem3" role="treeitem" tabindex="0" aria-selected="false">focused explicitly unselected treeitem</div>
+ </div>
+
+ <!-- tab managed by active-descendant -->
+ <div id="aria_tablist2" role="tablist" tabindex="0">
+ <div id="aria_tab4" role="tab" aria-selected="false">focused explicitly unselected tab</div>
+ <div id="aria_tab5" role="tab">initially selected tab</div>
+ <div id="aria_tab6" role="tab">later selected tab</div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_buttons.html b/accessible/tests/mochitest/states/test_buttons.html
new file mode 100644
index 0000000000..ec6e65cf32
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_buttons.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML button accessible states</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // Default state.
+ testStates("f1_image", STATE_DEFAULT | STATE_FOCUSABLE);
+ testStates("f2_submit", STATE_DEFAULT | STATE_FOCUSABLE);
+ testStates("f3_submitbutton", STATE_DEFAULT | STATE_FOCUSABLE);
+ testStates("f3_disabled_reset", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE, 0);
+ testStates("f4_button", STATE_FOCUSABLE, 0, STATE_DEFAULT);
+ testStates("f4_disabled_button", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE, 0);
+ testStates("f4_image1", STATE_DEFAULT | STATE_FOCUSABLE);
+ testStates("f4_image2", STATE_FOCUSABLE, 0, STATE_DEFAULT);
+ testStates("f4_submit", STATE_FOCUSABLE, 0, STATE_DEFAULT);
+ testStates("f4_submitbutton", STATE_FOCUSABLE, 0, STATE_DEFAULT);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=664142"
+ title="DEFAULT state exposed incorrectly for HTML">
+ Mozilla Bug 664142
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p>A form with an image button</p>
+ <form name="form1" method="get">
+ <input type="text" name="hi">
+
+ <input id="f1_image" type="image" value="image-button">
+ </form>
+
+ <p>A form with a submit button:</p>
+ <form name="form2" method="get">
+ <input type="text" name="hi">
+ <input id="f2_submit" type="submit">
+ </form>
+
+ <p>A form with a HTML4 submit button:</p>
+ <form name="form3" method="get">
+ <input type="text" name="hi">
+ <button id="f3_submitbutton" type="submit">submit</button>
+ <button id="f3_disabled_reset" type="reset" disabled>reset</button>
+ </form>
+
+ <p>A form with normal button, two image buttons, submit button,
+ HTML4 submit button:</p>
+ <form name="form4" method="get">
+ <input type="text" name="hi">
+ <input id="f4_button" type="button" value="normal" name="normal-button">
+ <input id="f4_disabled_button" type="button" value="disabled" name="disabled-button" disabled>
+ <input id="f4_image1" type="image" value="image-button1" name="image-button1">
+ <input id="f4_image2" type="image" value="image-button2" name="image-button2">
+ <input id="f4_submit" type="submit" value="real-submit" name="real-submit">
+ <button id="f4_submitbutton" type="submit">submit</button>
+ </form>
+
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_controls.html b/accessible/tests/mochitest/states/test_controls.html
new file mode 100644
index 0000000000..27aecf4c52
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_controls.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML control states</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // Undetermined progressbar (no value or aria-value attribute): mixed state
+ testStates("progress", STATE_MIXED);
+ // Determined progressbar (has value): shouldn't have mixed state
+ testStates("progress2", 0, 0, STATE_MIXED);
+ // Determined progressbar (has aria-value): shouldn't have mixed state
+ // testStates("progress3", 0, 0, STATE_MIXED);
+ todo(false, "we should respect ARIA");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=670853"
+ title="Bug 670853 - undetermined progressmeters should expose mixed state">
+ Mozilla Bug 670853
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <progress id="progress"></progress>
+ <progress id="progress2" value="1"></progress>
+ <progress id="progress3" aria-valuenow="1"></progress>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_controls.xhtml b/accessible/tests/mochitest/states/test_controls.xhtml
new file mode 100644
index 0000000000..a1997aef6e
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_controls.xhtml
@@ -0,0 +1,153 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL input control state tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ var gQueue = null;
+ function doTest()
+ {
+ testStates("checkbox", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+ testStates("checkbox2", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+ testStates("radiogroup", 0, 0, STATE_FOCUSABLE | STATE_UNAVAILABLE);
+ testStates("radio", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+ testStates("radio-disabled", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+ testStates("radiogroup-disabled", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+ testStates("radio-disabledradiogroup", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+ testStates("button", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+ testStates("button-disabled", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+ testStates("checkbutton",
+ STATE_FOCUSABLE | STATE_PRESSED, 0, STATE_CHECKABLE);
+ testStates("fakecheckbutton", STATE_FOCUSABLE | STATE_PRESSED, 0,
+ STATE_CHECKABLE);
+ testStates("combobox", STATE_FOCUSABLE | STATE_HASPOPUP, 0, STATE_UNAVAILABLE);
+ testStates("combobox-disabled", STATE_UNAVAILABLE | STATE_HASPOPUP, 0, STATE_FOCUSABLE);
+ testStates("listbox", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+ testStates("listitem", STATE_FOCUSABLE | STATE_SELECTABLE | STATE_SELECTED, 0, STATE_UNAVAILABLE);
+ testStates("listbox-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE);
+ testStates("listitem-disabledlistbox", STATE_UNAVAILABLE | STATE_SELECTED, 0, STATE_FOCUSABLE | STATE_SELECTABLE);
+ testStates("menubar", 0, 0, STATE_FOCUSABLE);
+ testStates("menu", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+ testStates("menu-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE);
+ testStates("tab", STATE_FOCUSABLE | STATE_SELECTABLE | STATE_SELECTED, 0, STATE_UNAVAILABLE);
+ testStates("tab-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE | STATE_SELECTED);
+
+ gQueue = new eventQueue();
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=599163"
+ title="check disabled state instead of attribute">
+ Mozilla Bug 599163
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=756983"
+ title="Isolate focusable and unavailable states from State()">
+ Mozilla Bug 756983
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <checkbox id="checkbox" checked="true" label="Steak"/>
+ <checkbox id="checkbox2" checked="true" label="Salad" disabled="true"/>
+
+ <radiogroup id="radiogroup">
+ <radio id="radio" label="Orange"/>
+ <radio id="radio-disabled" selected="true" label="Violet" disabled="true"/>
+ </radiogroup>
+
+ <radiogroup id="radiogroup-disabled" disabled="true">
+ <radio id="radio-disabledradiogroup" label="Orange"/>
+ <radio id="violet2" selected="true" label="Violet"/>
+ </radiogroup>
+
+ <button id="button" value="button"/>
+ <button id="button-disabled" disabled="true" value="button"/>
+ <button id="checkbutton" type="checkbox" checked="true"
+ value="checkbutton" />
+ <button id="fakecheckbutton" checked="true" value="fakecheckbutton" />
+
+ <menulist id="combobox">
+ <menupopup>
+ <menuitem label="item1"/>
+ </menupopup>
+ </menulist>
+
+ <menulist id="combobox-disabled" disabled="true">
+ <menupopup>
+ <menuitem label="item1"/>
+ </menupopup>
+ </menulist>
+
+ <richlistbox id="listbox">
+ <richlistitem id="listitem">
+ <label value="list item"/>
+ </richlistitem>
+ </richlistbox>
+
+ <richlistbox id="listbox-disabled" disabled="true">
+ <richlistitem id="listitem-disabledlistbox">
+ <label value="list item"/>
+ </richlistitem>
+ </richlistbox>
+
+ <toolbox>
+ <menubar id="menubar">
+ <menu id="menu" label="menu1">
+ <menupopup>
+ <menuitem id="menu1-item1" label="menuitem1.1"/>
+ </menupopup>
+ </menu>
+ <menu id="menu-disabled" label="menu2" disabled="true">
+ <menupopup>
+ <menuitem id="menu-disabled-item1" label="menuitem2.1"/>
+ </menupopup>
+ </menu>
+ </menubar>
+ </toolbox>
+
+ <tabbox>
+ <tabs>
+ <tab id="tab" label="tab1" tooltip="tooltip"/>
+ <tab id="tab-disabled" label="tab1" disabled="true"/>
+ </tabs>
+ <tabpanels>
+ <tabpanel/>
+ <tabpanel/>
+ </tabpanels>
+ </tabbox>
+
+ <tooltip id="tooltip"><description>tooltip</description></tooltip>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/states/test_doc.html b/accessible/tests/mochitest/states/test_doc.html
new file mode 100644
index 0000000000..a00ca957b9
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_doc.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>states of document</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // Bug 566542: root accesible should expose active state when focused.
+ testStates(getRootAccessible(), 0, EXT_STATE_ACTIVE);
+
+ // Bug 509696, 607219.
+ testStates(document, STATE_READONLY, 0); // role=""
+
+ document.body.setAttribute("role", "application");
+ testStates(document, 0, 0, STATE_READONLY);
+ document.body.setAttribute("role", "foo"); // bogus role
+ testStates(document, STATE_READONLY);
+ document.body.removeAttribute("role");
+ testStates(document, STATE_READONLY);
+
+ // Bugs 454997 and 467387
+ testStates(document, STATE_READONLY);
+ testStates("document", STATE_READONLY);
+ testStates("editable_document", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+
+ testStates("p", 0, EXT_STATE_SELECTABLE_TEXT, 0, EXT_STATE_EDITABLE);
+ testStates("unselectable_p", 0, 0, 0, EXT_STATE_SELECTABLE_TEXT | EXT_STATE_EDITABLE);
+ testStates("unselectable_link", 0, 0, 0, EXT_STATE_SELECTABLE_TEXT | EXT_STATE_EDITABLE);
+
+ document.designMode = "on";
+
+ testStates(document, 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("p", 0, EXT_STATE_EDITABLE | EXT_STATE_SELECTABLE_TEXT, STATE_READONLY);
+ testStates("document", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("editable_document", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("unselectable_p", 0, EXT_STATE_SELECTABLE_TEXT | EXT_STATE_EDITABLE);
+ testStates("unselectable_link", 0, EXT_STATE_SELECTABLE_TEXT | EXT_STATE_EDITABLE);
+
+ document.designMode = "off";
+
+ testStates(document, STATE_READONLY);
+ testStates("document", STATE_READONLY);
+ testStates("editable_document", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body role="">
+
+ <a target="_blank"
+ title="<body contenteditable='true'> exposed incorrectly"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=454997">Mozilla Bug 454997</a>
+ <a target="_blank"
+ title="nsIAccessible states tests of editable document"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=467387">Mozilla Bug 467387</a>
+ <a target="_blank"
+ title="Role attribute on body with empty string causes DocAccessible not to have read-only state."
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=509696">Mozilla Bug 509696</a>
+ <a target="_blank"
+ title="Frame for firefox does not implement the state "active" when firefox is the active frame"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=566542">Mozilla Bug 566542</a>
+ <a target="_blank"
+ title="Dynamic role attribute change on body doesn't affect on document role"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=607219">Mozilla Bug 607219</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="p">hello</p>
+
+ <p id="unselectable_p" style="user-select: none;">unselectable <a id="unselectable_link" href="#">link</a></p>
+
+ <div id="document" role="document">document</div>
+ <div id="editable_document" role="document" contentEditable="true">editable document</doc>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_doc_busy.html b/accessible/tests/mochitest/states/test_doc_busy.html
new file mode 100644
index 0000000000..35b6d0542c
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_doc_busy.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>states of document</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+ const { BrowserTestUtils } = ChromeUtils.import(
+ "resource://testing-common/BrowserTestUtils.jsm");
+ const { Downloads } = ChromeUtils.import(
+ "resource://gre/modules/Downloads.jsm");
+
+ function matchDocBusyChange(isBusy) {
+ return function(event) {
+ const scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent);
+ return (
+ event.DOMNode == document &&
+ scEvent.state === STATE_BUSY &&
+ scEvent.isEnabled === isBusy
+ );
+ };
+ }
+
+ function getDownloadStartedPromise() {
+ return Downloads.getList(Downloads.PUBLIC).then(list => {
+ return new Promise(resolve => {
+ list.addView({
+ onDownloadAdded(download) {
+ resolve(download);
+ }
+ })
+ });
+ });
+ }
+
+ async function downloadHandled(downloadResult) {
+ if (Window.isInstance(downloadResult)) {
+ return BrowserTestUtils.closeWindow(downloadResult);
+ }
+ // downloadResult is a download object.
+ await downloadResult.finalize(true);
+ return Downloads.getList(Downloads.PUBLIC).then(list => list.remove(downloadResult));
+ }
+
+ async function doTest() {
+ // Because of variable timing, there are two acceptable possibilities:
+ // 1. We get an event for busy and an event for not busy.
+ // 2. The two events get coalesced, so no events are fired.
+ // However, we fail this test if we get the first event but not the
+ // second.
+ let gotBusy = false;
+ let gotNotBusy = false;
+ const busyEvents = async function() {
+ await waitForEvent(EVENT_STATE_CHANGE, matchDocBusyChange(true));
+ info("Got busy event");
+ gotBusy = true;
+ await waitForEvent(EVENT_STATE_CHANGE, matchDocBusyChange(false));
+ info("Got not-busy event");
+ gotNotBusy = true;
+ }();
+
+ const downloadStarted = getDownloadStartedPromise();
+
+ info("Clicking link to trigger download");
+ synthesizeMouse(getNode("link"), 1, 1, {});
+ info("Waiting for download prompt to open");
+ const downloadResult = await downloadStarted;
+
+ // Once we no longer show a dialog for downloads, the not busy event
+ // might show up a bit later.
+ if (gotBusy && !gotNotBusy) {
+ await busyEvents;
+ }
+
+ // Any busy events should have been fired by the time the download
+ // prompt has opened.
+ if (gotBusy && gotNotBusy) {
+ ok(true, "Got both busy change and not-busy change");
+ } else if (!gotBusy && !gotNotBusy) {
+ ok(true, "No busy events, coalesced");
+ } else {
+ ok(false, "Got busy change but didn't get not-busy change!");
+ }
+ testStates(document, 0, 0, STATE_BUSY, 0, "Document not busy");
+
+ // Clean up.
+ info("Closing download prompt");
+ await downloadHandled(downloadResult);
+ // We might still be waiting on busy events. Remove any pending observers.
+ for (let observer of Services.obs.enumerateObservers(
+ "accessible-event")
+ ) {
+ Services.obs.removeObserver(observer, "accessible-event");
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ title="Missing busy state change event when downloading files"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=446469">Bug 446469</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <a id="link" href="http://example.com/a11y/accessible/tests/mochitest/dumbfile.zip">a file</a>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_docarticle.html b/accessible/tests/mochitest/states/test_docarticle.html
new file mode 100644
index 0000000000..8e45d5ebd6
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_docarticle.html
@@ -0,0 +1,78 @@
+<html>
+<head>
+ <title>states of document article</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var docAcc = getAccessible(document, [nsIAccessibleDocument]);
+ if (docAcc) {
+ testStates(docAcc, STATE_READONLY);
+ testStates("aria_article", STATE_READONLY);
+ testStates("editable_aria_article", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY);
+ testStates("article", STATE_READONLY);
+ testStates("editable_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+
+ document.designMode = "on";
+
+ testStates(docAcc, 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("aria_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("editable_aria_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("editable_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+
+ document.designMode = "off";
+
+ testStates(docAcc, STATE_READONLY);
+ testStates("aria_article", STATE_READONLY);
+ testStates("editable_aria_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("article", STATE_READONLY);
+ testStates("editable_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ }
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body role="article">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=467387"
+ title="Expose non-editable documents as readonly, regardless of role">
+ Mozilla Bug 467387
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=613502"
+ title="Map <article> like we do aria role article">
+ Mozilla Bug 613502
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="aria_article" role="article">aria article</div>
+ <div id="editable_aria_article" role="article" contentEditable="true">
+ editable aria article</div>
+
+ <article id="article">article</article>
+ <article id="editable_article" contentEditable="true">
+ editable article</article>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_editablebody.html b/accessible/tests/mochitest/states/test_editablebody.html
new file mode 100644
index 0000000000..27b7201a2b
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_editablebody.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=454997
+-->
+<head>
+ <title>nsIAccessible states tests of contenteditable body</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ testStates(document, 0, EXT_STATE_EDITABLE);
+ testStates("p", 0, EXT_STATE_EDITABLE);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body contentEditable="true">
+
+ <a target="_blank"
+ title="nsIAccessible states tests of contenteditable body"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=454997">Mozilla Bug 454997</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="p">hello</p>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_expandable.xhtml b/accessible/tests/mochitest/states/test_expandable.xhtml
new file mode 100644
index 0000000000..0d969bef5b
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_expandable.xhtml
@@ -0,0 +1,112 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<!-- Firefox searchbar -->
+<?xml-stylesheet href="chrome://browser/content/browser.css"
+ type="text/css"?>
+<!-- SeaMonkey searchbar -->
+<?xml-stylesheet href="chrome://navigator/content/navigator.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Expanded state change events tests for comboboxes and autocompletes.">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+ <script type="application/javascript"
+ src="../autocomplete.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ //gA11yEventDumpToConsole = true; // debuggin
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new openCombobox("menulist"));
+ gQueue.push(new closeCombobox("menulist"));
+
+ todo(false, "Autocompletes don't fire expanded state change events when popup open. See bug 688480!");
+ //gQueue.push(new openCombobox("autocomplete"));
+ //gQueue.push(new closeCombobox("autocomplete"));
+
+ // XXX: searchbar doesn't fire state change events because accessible
+ // parent of combobox_list accessible is pushbutton accessible.
+ //var searchbar = document.getElementById("searchbar");
+ //gQueue.push(new openHideCombobox(searchbar, true));
+ //gQueue.push(new openHideCombobox(searchbar, false));
+ todo(false, "Enable states test for XUL searchbar widget!");
+
+ gQueue.onFinish = function()
+ {
+ // unregister 'test-a11y-search' autocomplete search
+ shutdownAutoComplete();
+ }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ // This is the hacks needed to use a searchbar without browser.js.
+ var BrowserSearch = {
+ updateOpenSearchBadge() {}
+ };
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Register 'test-a11y-search' autocomplete search.
+ // XPFE AutoComplete needs to register early.
+ initAutoComplete([ "hello", "hi" ],
+ [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]);
+
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox style="overflow: auto;" flex="1">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=467057"
+ title="xul menulist doesn't fire expand/collapse state change events">
+ Mozilla Bug 467057
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menulist id="menulist">
+ <menupopup>
+ <menuitem label="item1"/>
+ <menuitem label="item2"/>
+ <menuitem label="item3"/>
+ </menupopup>
+ </menulist>
+
+ <html:input is="autocomplete-input"
+ id="autocomplete"
+ autocompletesearch="test-a11y-search"/>
+
+ <searchbar id="searchbar"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/states/test_frames.html b/accessible/tests/mochitest/states/test_frames.html
new file mode 100644
index 0000000000..baac222d83
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_frames.html
@@ -0,0 +1,93 @@
+<html>
+
+<head>
+ <title>frame based document testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ if (navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 2);
+ }
+
+ function doTest() {
+ const frameDoc = document.getElementById("frame_doc").contentDocument;
+ const frameDocArticle = document.getElementById("frame_doc_article").contentDocument;
+ const frameDocCheckbox = document.getElementById("frame_doc_checkbox").contentDocument;
+ const frameDocTextbox = document.getElementById("frame_doc_textbox").contentDocument;
+
+ testStates(frameDoc, STATE_READONLY, 0, 0, 0,
+ "test1: frameDoc");
+ testStates(frameDocArticle, STATE_READONLY, 0, 0, 0,
+ "test1: frameDocArticle");
+ testStates(frameDocCheckbox, STATE_READONLY, 0, 0, 0,
+ "test1: frameDocCheckbox");
+ testStates(frameDocTextbox, STATE_READONLY, 0, 0, 0,
+ "test1: frameDocTextbox");
+ frameDoc.designMode = "on";
+ testStates(frameDoc, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0,
+ "test2: frameDoc");
+ testStates(frameDocArticle, STATE_READONLY, 0, 0, 0,
+ "test2: frameDocArticle");
+ testStates(frameDocCheckbox, STATE_READONLY, 0, 0, 0,
+ "test2: frameDocCheckbox");
+ testStates(frameDocTextbox, STATE_READONLY, 0, 0, 0,
+ "test2: frameDocTextbox");
+
+ frameDocArticle.designMode = "on";
+ testStates(frameDocArticle, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0,
+ "test3: frameDocArticle");
+
+ frameDocCheckbox.designMode = "on";
+ testStates(frameDocCheckbox, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0,
+ "test4: frameDocCheckbox");
+
+ // Replace iframe document body before the document accessible tree is
+ // created. Check the states are updated for new body.
+ var frameUpdateDoc =
+ document.getElementById("frame_updatedoc").contentDocument;
+ testStates(frameUpdateDoc, 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, EXT_STATE_STALE, "test5: frameUpdateDoc");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=467387"
+ title="Expose non-editable documents as readonly, regardless of role">
+ Mozilla Bug 467387
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=638106"
+ title="CKEditor document should be editable">
+ Mozilla Bug 638106
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <iframe id="frame_doc" src="z_frames.html"></iframe>
+ <iframe id="frame_doc_article" src="z_frames_article.html"></iframe>
+ <iframe id="frame_doc_checkbox" src="z_frames_checkbox.html"></iframe>
+ <iframe id="frame_doc_textbox" src="z_frames_textbox.html"></iframe>
+ <iframe id="frame_updatedoc" src="z_frames_update.html"></iframe>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_inputs.html b/accessible/tests/mochitest/states/test_inputs.html
new file mode 100644
index 0000000000..d9b9014a34
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_inputs.html
@@ -0,0 +1,268 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML input states</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // //////////////////////////////////////////////////////////////////////////
+ // 'editable' and 'multiline' states.
+ testStates("input", 0, EXT_STATE_EDITABLE, 0, EXT_STATE_MULTI_LINE);
+ testStates("textarea", 0, EXT_STATE_EDITABLE | EXT_STATE_MULTI_LINE);
+
+ testStates("input_readonly", 0, EXT_STATE_EDITABLE);
+ testStates("input_disabled", 0, EXT_STATE_EDITABLE);
+ testStates("textarea_readonly", 0, EXT_STATE_EDITABLE);
+ testStates("textarea_disabled", 0, EXT_STATE_EDITABLE);
+
+ // //////////////////////////////////////////////////////////////////////////
+ // 'required', 'readonly' and 'unavailable' states.
+ var maybe_required = ["input", "search", "radio", "checkbox", "textarea"];
+ var never_required = ["submit", "button", "reset", "image"];
+
+ var i;
+ for (i in maybe_required) {
+ testStates(maybe_required[i],
+ STATE_FOCUSABLE, 0,
+ STATE_REQUIRED | STATE_READONLY | STATE_UNAVAILABLE);
+
+ testStates(maybe_required[i] + "_required",
+ STATE_FOCUSABLE | STATE_REQUIRED, 0,
+ STATE_UNAVAILABLE | STATE_READONLY);
+
+ var readonlyID = maybe_required[i] + "_readonly";
+ if (document.getElementById(readonlyID)) {
+ testStates(readonlyID,
+ STATE_FOCUSABLE | STATE_READONLY, 0,
+ STATE_UNAVAILABLE | STATE_REQUIRED);
+ }
+
+ testStates(maybe_required[i] + "_disabled",
+ STATE_UNAVAILABLE, 0,
+ STATE_FOCUSABLE | STATE_READONLY | STATE_REQUIRED);
+ }
+
+ for (i in never_required) {
+ testStates(never_required[i], 0, 0, STATE_REQUIRED | EXT_STATE_EDITABLE);
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // inherited 'unavailable' state
+ testStates("f", STATE_UNAVAILABLE);
+ testStates("f_input", STATE_UNAVAILABLE);
+ testStates("f_input_disabled", STATE_UNAVAILABLE);
+
+ // //////////////////////////////////////////////////////////////////////////
+ // inherited from file control
+ var fileBrowseButton = getAccessible("file").firstChild;
+ testStates(fileBrowseButton, STATE_UNAVAILABLE | STATE_REQUIRED);
+ // No states on the label.
+
+ // //////////////////////////////////////////////////////////////////////////
+ // 'invalid' state
+ var invalid = ["pattern", "email", "url"];
+ for (i in invalid) {
+ testStates(invalid[i], STATE_INVALID);
+ testStates(invalid[i] + "2", 0, 0, STATE_INVALID);
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // not 'invalid' state
+ // (per spec, min/maxlength are always valid until interactively edited)
+ var validInput = document.createElement("input");
+ validInput.maxLength = "0";
+ validInput.value = "a";
+ ok(validInput.validity.valid,
+ "input should be valid despite maxlength (no interactive edits)");
+
+ var validInput2 = document.createElement("input");
+ validInput2.minLength = "1";
+ validInput2.value = "";
+ ok(validInput2.validity.valid,
+ "input should be valid despite minlength (no interactive edits)");
+
+ var valid = ["minlength", "maxlength"];
+ for (i in valid) {
+ testStates(valid[i], 0, 0, STATE_INVALID);
+ testStates(valid[i] + "2", 0, 0, STATE_INVALID);
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // 'invalid' state
+ // (per spec, min/maxlength validity is affected by interactive edits)
+ var mininp = document.getElementById("minlength");
+ mininp.focus();
+ mininp.setSelectionRange(mininp.value.length, mininp.value.length);
+ synthesizeKey("KEY_Backspace");
+ ok(!mininp.validity.valid,
+ "input should be invalid after interactive edits");
+ testStates(mininp, STATE_INVALID);
+ // inputs currently cannot be made longer than maxlength interactively,
+ // so we're not testing that case.
+
+ // //////////////////////////////////////////////////////////////////////////
+ // autocomplete states
+ testStates("autocomplete-default", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-off", 0, 0, 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-formoff", 0, 0, 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-list", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-list2", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-tel", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-email", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-search", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+
+ // //////////////////////////////////////////////////////////////////////////
+ // haspopup
+ testStates("autocomplete-list", STATE_HASPOPUP);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559275"
+ title="map attribute required to STATE_REQUIRED">
+ Bug 559275
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=389238"
+ title="Support disabled state on fieldset">
+ Bug 389238
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=599163"
+ title="check disabled state instead of attribute">
+ Bug 599163
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=601205"
+ title="Expose intrinsic invalid state to accessibility API">
+ Bug 601205
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=601205"
+ title="Expose intrinsic invalid state to accessibility API">
+ Bug 601205
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559766"
+ title="Add accessibility support for @list on HTML input and for HTML datalist">
+ Bug 559766
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017"
+ title="File input control should be propogate states to descendants">
+ Bug 699017
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=733382"
+ title="Editable state bit should be present on readonly inputs">
+ Bug 733382
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=878590"
+ title="HTML5 datalist is not conveyed by haspopup property">
+ Bug 878590
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+
+ <form>
+ <input id="input" type="input">
+ <input id="input_required" type="input" required>
+ <input id="input_readonly" type="input" readonly>
+ <input id="input_disabled" type="input" disabled>
+ <input id="search" type="search">
+ <input id="search_required" type="search" required>
+ <input id="search_readonly" type="search" readonly>
+ <input id="search_disabled" type="search" disabled>
+ <input id="radio" type="radio">
+ <input id="radio_required" type="radio" required>
+ <input id="radio_disabled" type="radio" disabled>
+ <input id="checkbox" type="checkbox">
+ <input id="checkbox_required" type="checkbox" required>
+ <input id="checkbox_disabled" type="checkbox" disabled>
+ <textarea id="textarea"></textarea>
+ <textarea id="textarea_required" required></textarea>
+ <textarea id="textarea_readonly" readonly></textarea>
+ <textarea id="textarea_disabled" disabled></textarea>
+ </form>
+
+ <!-- bogus required usage -->
+ <input id="submit" type="submit" required>
+ <input id="button" type="button" required>
+ <input id="reset" type="reset" required>
+ <input id="image" type="image" required>
+
+ <!-- inherited disabled -->
+ <fieldset id="f" disabled>
+ <input id="f_input">
+ <input id="f_input_disabled" disabled>
+ </fieldset>
+
+ <!-- inherited from input@type="file" -->
+ <input id="file" type="file" required disabled>
+
+ <!-- invalid/valid -->
+ <input id="maxlength" maxlength="1" value="f">
+ <input id="maxlength2" maxlength="100" value="foo">
+ <input id="minlength" minlength="2" value="fo">
+ <input id="minlength2" minlength="1" value="foo">
+ <input id="pattern" pattern="bar" value="foo">
+ <input id="pattern2" pattern="bar" value="bar">
+ <input id="email" type="email" value="foo">
+ <input id="email2" type="email" value="foo@bar.com">
+ <input id="url" type="url" value="foo">
+ <input id="url2" type="url" value="http://mozilla.org/">
+
+ <!-- autocomplete -->
+ <input id="autocomplete-default">
+ <input id="autocomplete-off" autocomplete="off">
+ <form autocomplete="off">
+ <input id="autocomplete-formoff">
+ </form>
+ <datalist id="cities">
+ <option>Paris</option>
+ <option>San Francisco</option>
+ </datalist>
+ <input id="autocomplete-list" list="cities">
+ <input id="autocomplete-list2" list="cities" autocomplete="off">
+ <input id="autocomplete-tel" type="tel">
+
+ Email Address:
+ <input id="autocomplete-email" type="email" list="contacts" value="xyzzy">
+ <datalist id="contacts">
+ <option>xyzzy@plughs.com</option>
+ <option>nobody@mozilla.org</option>
+ </datalist>
+
+ </br>Search for:
+ <input id="autocomplete-search" type="search" list="searchhisty" value="Gamma">
+ <datalist id="searchhisty">
+ <option>Gamma Rays</option>
+ <option>Gamma Ray Bursts</option>
+ </datalist>
+
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_link.html b/accessible/tests/mochitest/states/test_link.html
new file mode 100644
index 0000000000..65632bd12f
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_link.html
@@ -0,0 +1,85 @@
+<html>
+
+<head>
+ <title>HTML link states testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // a@href and its text node
+ testStates("link_href", STATE_LINKED);
+ testStates(getAccessible("link_href").firstChild, STATE_LINKED);
+
+ // a@onclick
+ testStates("link_click", STATE_LINKED);
+
+ // a@onmousedown
+ testStates("link_mousedown", STATE_LINKED);
+
+ // a@onmouseup
+ testStates("link_mouseup", STATE_LINKED);
+
+ // a@role="link"
+ testStates("link_arialink", STATE_LINKED);
+
+ // a@role="button"
+ testStates("link_ariabutton", 0, 0, STATE_LINKED);
+
+ // a (no @href, no click event listener)
+ testStates("link_notlink", 0, 0, STATE_LINKED);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409"
+ title="Expose click action if mouseup and mousedown are registered">
+ Mozilla Bug 423409
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=754830"
+ title="Calculate link states separately">
+ Mozilla Bug 754830
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=757774"
+ title="Fire state change event when link is traversed">
+ Mozilla Bug 757774
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <a id="link_href" href="http://mozilla.org">link</a>
+ <a id="link_click" onclick="">link</a>
+ <a id="link_mousedown" onmousedown="">link</a>
+ <a id="link_mouseup" onmouseup="">link</a>
+ <a id="link_arialink" role="link">aria link</a>
+ <a id="link_ariabutton" role="button">aria button</a>
+ <a id="link_notlink">not link</a>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_popup.xhtml b/accessible/tests/mochitest/states/test_popup.xhtml
new file mode 100644
index 0000000000..c51e3ac17c
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_popup.xhtml
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL popup attribute test">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ // label with popup
+ testStates("labelWithPopup", STATE_HASPOPUP);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=504252"
+ title="Expose STATE_HASPOPUP on XUL elements that have an @popup attribute">
+ Mozilla Bug 504252
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <!-- label with popup attribute -->
+ <label id="labelWithPopup" value="file name"
+ popup="fileContext"
+ tabindex="0"/>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/states/test_selects.html b/accessible/tests/mochitest/states/test_selects.html
new file mode 100644
index 0000000000..c822fe376f
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_selects.html
@@ -0,0 +1,166 @@
+<html>
+
+<head>
+ <title>HTML selects accessible states tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // gA11yEventDumpToConsole = true;
+
+ function doTest() {
+ // combobox
+ var combobox = getAccessible("combobox");
+ testStates(combobox,
+ STATE_HASPOPUP | STATE_COLLAPSED | STATE_FOCUSABLE, 0,
+ STATE_FOCUSED, 0);
+
+ var comboboxList = combobox.firstChild;
+ testStates(comboboxList, STATE_INVISIBLE, 0, STATE_FOCUSABLE, 0);
+
+ var opt1 = comboboxList.firstChild;
+ testStates(opt1, STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+ 0, STATE_FOCUSED, 0);
+
+ var opt2 = comboboxList.lastChild;
+ testStates(opt2, STATE_SELECTABLE | STATE_FOCUSABLE, 0, STATE_SELECTED, 0,
+ STATE_FOCUSED, 0);
+
+ // collapsed combobox
+ testStates("collapsedcombobox",
+ STATE_COLLAPSED | STATE_FOCUSABLE, 0,
+ STATE_FOCUSED, 0);
+
+ testStates("collapsed-1",
+ STATE_FOCUSABLE | STATE_SELECTABLE, 0,
+ STATE_OFFSCREEN | STATE_INVISIBLE, 0);
+
+ testStates("collapsed-2",
+ STATE_OFFSCREEN, 0,
+ STATE_INVISIBLE, 0);
+
+ // listbox
+ testStates("listbox",
+ STATE_FOCUSABLE, 0,
+ STATE_HASPOPUP | STATE_COLLAPSED | STATE_FOCUSED);
+
+ testStates("listitem-active",
+ STATE_FOCUSABLE | STATE_SELECTABLE, EXT_STATE_ACTIVE,
+ STATE_SELECTED | STATE_FOCUSED);
+
+ testStates("listitem",
+ STATE_FOCUSABLE | STATE_SELECTABLE, 0,
+ STATE_SELECTED | STATE_FOCUSED, EXT_STATE_ACTIVE);
+
+ testStates("listitem-disabled",
+ STATE_UNAVAILABLE, 0,
+ STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+ EXT_STATE_ACTIVE);
+
+ testStates("listgroup",
+ 0, 0,
+ STATE_UNAVAILABLE | STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+ EXT_STATE_ACTIVE);
+
+ testStates("listgroup-disabled",
+ STATE_UNAVAILABLE, 0,
+ STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+ EXT_STATE_ACTIVE);
+
+ todo(false, "no unavailable state on option in disabled group (bug 759666)");
+// testStates("listitem-disabledgroup",
+// STATE_UNAVAILABLE, 0,
+// STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+// EXT_STATE_ACTIVE);
+
+ testStates("listbox-disabled",
+ STATE_UNAVAILABLE, 0,
+ STATE_FOCUSABLE);
+
+ todo(false, "no unavailable state on option in disabled select (bug 759666)");
+// testStates("listitem-disabledlistbox",
+// STATE_UNAVAILABLE, 0,
+// STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+// EXT_STATE_ACTIVE);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=443889"
+ title="mochitest for selects and lists">
+ Mozilla Bug 443889
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=640716"
+ title="mochitest for selects and lists">
+ Mozilla Bug 640716
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=689847"
+ title="Expose active state on current item of selectable widgets">
+ Mozilla Bug 689847
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=756983"
+ title="Isolate focusable and unavailable states from State()">
+ Mozilla Bug 756983
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=907682"
+ title=" HTML:option group position is not correct when select is collapsed">
+ Mozilla Bug 907682
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="combobox">
+ <option>item 1</option>
+ <option>item 2</option>
+ </select>
+
+ <select id="collapsedcombobox">
+ <option id="collapsed-1">item 1</option>
+ <option id="collapsed-2">item 2</option>
+ </select>
+
+ <select id="listbox" name="component" size="3">
+ <option id="listitem-active">Build</option>
+ <option id="listitem">Disability Access APIs</option>
+ <option id="listitem-disabled" disabled>General</option>
+ <optgroup id="listgroup" label="group">
+ <option>option</option>
+ </optgroup>
+ <optgroup id="listgroup-disabled" disabled label="group2">
+ <option id="listitem-disabledgroup">UI</option>
+ </optgroup>
+ </select>
+
+ <select id="listbox-disabled" size="3" disabled>
+ <option id="listitem-disabledlistbox">option</option>
+ </select>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_stale.html b/accessible/tests/mochitest/states/test_stale.html
new file mode 100644
index 0000000000..3218a2125a
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_stale.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Stale state testing</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function addChild(aContainerID) {
+ this.containerNode = getNode(aContainerID);
+ this.childNode = null;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.containerNode),
+ ];
+
+ this.invoke = function addChild_invoke() {
+ this.childNode = document.createElement("div");
+ // Note after bug 646216, a sole div without text won't be accessible
+ // and would not result in an embedded character.
+ // Therefore, add some text.
+ this.childNode.textContent = "hello";
+ this.containerNode.appendChild(this.childNode);
+ };
+
+ this.finalCheck = function addChild_finalCheck() {
+ // no stale state should be set
+ testStates(this.childNode, 0, 0, 0, EXT_STATE_STALE);
+ };
+
+ this.getID = function addChild_getID() {
+ return "add child for " + prettyName(aContainerID);
+ };
+ }
+
+ function removeChildChecker(aInvoker) {
+ this.type = EVENT_HIDE;
+ this.__defineGetter__("target", function() { return aInvoker.child; });
+
+ this.check = function removeChildChecker_check() {
+ // stale state should be set
+ testStates(aInvoker.child, 0, EXT_STATE_STALE);
+ };
+ }
+
+ function removeChild(aContainerID) {
+ this.containerNode = getNode(aContainerID);
+ this.child = null;
+
+ this.eventSeq = [
+ new removeChildChecker(this),
+ ];
+
+ this.invoke = function removeChild_invoke() {
+ var childNode = this.containerNode.firstChild;
+ this.child = getAccessible(childNode);
+
+ this.containerNode.removeChild(childNode);
+ };
+
+ this.getID = function removeChild_getID() {
+ return "remove child from " + prettyName(aContainerID);
+ };
+ }
+
+ // gA11yEventDumpToConsole = true; //debugging
+
+ var gQueue = null;
+ function doTest() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new addChild("container"));
+ gQueue.push(new removeChild("container"));
+
+ gQueue.invoke(); // will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body role="">
+
+ <a target="_blank"
+ title="Expose stale state on accessibles unattached from tree"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=676267">Mozilla Bug 676267</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="container"></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_tabs.xhtml b/accessible/tests/mochitest/states/test_tabs.xhtml
new file mode 100644
index 0000000000..4d8a83c97d
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_tabs.xhtml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tabbox hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ testStates("tab1", 0, EXT_STATE_PINNED);
+ testStates("tab2", 0, 0, 0, EXT_STATE_PINNED);
+ testStates("tab3", 0, 0, 0, EXT_STATE_PINNED);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=577727"
+ title="Make pinned tabs distinguishable from other tabs for accessibility">
+ Mozilla Bug 577727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tabbox>
+ <tabs id="tabs">
+ <tab id="tab1" label="tab1" pinned="true"/>
+ <tab id="tab2" label="tab2" pinned="false"/>
+ <tab id="tab3" label="tab3"/>
+ </tabs>
+ <tabpanels id="tabpanels">
+ <tabpanel/>
+ <tabpanel/>
+ </tabpanels>
+ </tabbox>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/states/test_textbox.xhtml b/accessible/tests/mochitest/states/test_textbox.xhtml
new file mode 100644
index 0000000000..eaf455d994
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_textbox.xhtml
@@ -0,0 +1,78 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="nsIAccessible XUL textboxes states tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function getInput(aID)
+ {
+ return getNode(aID).inputField;
+ }
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // Search textbox without search button, searches as you type and filters
+ // a separate control.
+ testStates(getInput("searchbox"),
+ STATE_FOCUSABLE,
+ EXT_STATE_EDITABLE | EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ STATE_PROTECTED | STATE_UNAVAILABLE,
+ 0,
+ "searchbox");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Search textbox with search button, does not support autoCompletion.
+ testStates(getInput("searchfield"),
+ STATE_FOCUSABLE,
+ EXT_STATE_EDITABLE,
+ STATE_PROTECTED | STATE_UNAVAILABLE,
+ EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "searchfield");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=442648">
+ Mozilla Bug 442648
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=648235"
+ title="XUL textbox can inherit more states from underlying HTML input">
+ Mozilla Bug 648235
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <search-textbox id="searchbox" flex="1" results="historyTree"/>
+ <search-textbox id="searchfield" placeholder="Search all add-ons"
+ searchbutton="true"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/states/test_tree.xhtml b/accessible/tests/mochitest/states/test_tree.xhtml
new file mode 100644
index 0000000000..d81be4a54b
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_tree.xhtml
@@ -0,0 +1,146 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../treeview.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree states tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ /**
+ * Event queue invoker object to test accessible states for XUL tree
+ * accessible.
+ */
+ function statesChecker(aTreeID, aView)
+ {
+ this.DOMNode = getNode(aTreeID);
+
+ this.invoke = function statesChecker_invoke()
+ {
+ this.DOMNode.view = aView;
+ }
+
+ this.check = function statesChecker_check()
+ {
+ var tree = getAccessible(this.DOMNode);
+
+ // tree states
+ testStates(tree, STATE_READONLY);
+
+ if (this.DOMNode.getAttribute("seltype") != "single")
+ testStates(tree, STATE_MULTISELECTABLE);
+ else
+ testStates(tree, 0, 0, STATE_MULTISELECTABLE);
+
+ // tree item states
+ var expandedItem = tree.getChildAt(2);
+ testStates(expandedItem,
+ STATE_SELECTABLE | STATE_FOCUSABLE | STATE_EXPANDED);
+
+ var collapsedItem = tree.getChildAt(5);
+ testStates(collapsedItem,
+ STATE_SELECTABLE | STATE_FOCUSABLE | STATE_COLLAPSED);
+
+ // cells states if any
+ var cells = collapsedItem.children;
+ if (cells && cells.length) {
+ for (var idx = 0; idx < cells.length; idx++) {
+ var cell = cells.queryElementAt(idx, nsIAccessible);
+ testStates(cell, STATE_SELECTABLE);
+ }
+
+ var checkboxCell = cells.queryElementAt(3, nsIAccessible);
+ testStates(checkboxCell, STATE_CHECKABLE | STATE_CHECKED);
+ }
+ }
+
+ this.getID = function statesChecker_getID()
+ {
+ return "tree processor for " + prettyName(aTreeID);
+ }
+ }
+
+ gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue(EVENT_REORDER);
+ gQueue.push(new statesChecker("tree", new nsTreeTreeView()));
+ gQueue.push(new statesChecker("treesingle", new nsTreeTreeView()));
+ gQueue.push(new statesChecker("tabletree", new nsTreeTreeView()));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="treesingle" flex="1" seltype="single">
+ <treecols>
+ <treecol id="col_single" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="tabletree" flex="1" editable="true">
+ <treecols>
+ <treecol id="tabletree_col1" cycler="true" label="cycler"/>
+ <treecol id="tabletree_col2" flex="1" primary="true" label="column1"/>
+ <treecol id="tabletree_col3" flex="1" label="column2"/>
+ <treecol id="tabletree_col4" flex="1" label="checker"
+ type="checkbox" editable="true"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/states/test_visibility.html b/accessible/tests/mochitest/states/test_visibility.html
new file mode 100644
index 0000000000..aa3643673a
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_visibility.html
@@ -0,0 +1,75 @@
+<html>
+<head>
+ <title>visibility state testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ // Tests
+
+ function doTests() {
+ testStates("div", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates("div_off", STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+ testStates("div_transformed", STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+ testStates("div_abschild", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates("ul", STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=591363"
+ title="(in)visible state is not always correct?">
+ Mozilla Bug 591363
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=768786"
+ title="Offscreen state is not exposed under certain circumstances">
+ Mozilla Bug 768786
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="outer_div">
+
+ <!-- trivial cases -->
+ <div id="div">div</div>
+ <div id="div_off" style="position: absolute; left:-999px; top:-999px">
+ offscreen!
+ </div>
+ <div id="div_transformed" style="transform: translate(-999px, -999px);">
+ transformed!
+ </div>
+
+ <!-- edge case: no rect but has out of flow child -->
+ <div id="div_abschild">
+ <p style="position: absolute; left: 120px; top:120px;">absolute</p>
+ </div>
+
+ <ul id="ul" style="display: contents;">
+ <li>Supermarket 1</li>
+ <li>Supermarket 2</li>
+ </ul>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_visibility.xhtml b/accessible/tests/mochitest/states/test_visibility.xhtml
new file mode 100644
index 0000000000..cc23b6b4bc
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_visibility.xhtml
@@ -0,0 +1,162 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="XUL elements visibility states testing">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function openMenu(aID, aSubID, aOffscreenSubID)
+ {
+ this.menuNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.menuNode)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ this.menuNode.open = true;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ testStates(aID, 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates(aSubID, 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ if (aOffscreenSubID)
+ testStates(aOffscreenSubID, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+ }
+
+ this.getID = function openMenu_invoke()
+ {
+ return "open menu '" + aID + "' and test states";
+ }
+ }
+
+ function closeMenu(aID, aSubID, aSub2ID)
+ {
+ this.menuNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, document)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ this.menuNode.open = false;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ testStates(aID, 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates(aSubID, STATE_INVISIBLE, 0, STATE_OFFSCREEN);
+ testStates(aSub2ID, STATE_INVISIBLE, 0, STATE_OFFSCREEN);
+ }
+
+ this.getID = function openMenu_invoke()
+ {
+ return "open menu and test states";
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ testStates("deck_pane2", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates("tabs_pane1", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates("tabs_pane2", STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+
+ gQueue = new eventQueue();
+ gQueue.push(new openMenu("mi_file1", "mi_file1.1"));
+ gQueue.push(new openMenu("mi_file1.2", "mi_file1.2.1", "mi_file1.2.4"));
+ gQueue.push(new closeMenu("mi_file1", "mi_file1.1", "mi_file1.2.1"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+ <html:style>
+ <![CDATA[
+ /* We want to control the height of the menu and which elements are visible,
+ and the Windows menu padding interferes with this. */
+ menupopup::part(arrowscrollbox) {
+ margin: 0 !important;
+ padding-block: 0 !important;
+ }
+ ]]>
+ </html:style>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=810260"
+ title="xul:deck hidden pages shouldn't be offscreen">
+ Mozilla Bug 810260
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=865591"
+ title="Visible menu item have offscreen state">
+ Mozilla Bug 865591
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <deck selectedIndex="1">
+ <description value="This is the first page" id="deck_pane1"/>
+ <button label="This is the second page" id="deck_pane2"/>
+ </deck>
+
+ <tabbox>
+ <tabs>
+ <tab>tab1</tab>
+ <tab>tab2</tab>
+ </tabs>
+ <tabpanels>
+ <description value="This is the first page" id="tabs_pane1"/>
+ <button label="This is the second page" id="tabs_pane2"/>
+ </tabpanels>
+ </tabbox>
+
+ <menubar>
+ <menu label="File" id="mi_file1">
+ <menupopup>
+ <menuitem label="SubFile" id="mi_file1.1"/>
+ <menu label="SubFile2" id="mi_file1.2">
+ <menupopup style="max-height: 3em;">
+ <menuitem style="appearance: none; height: 1em" label="SubSubFile" id="mi_file1.2.1"/>
+ <menuitem style="appearance: none; height: 1em" label="SubSubFile2" id="mi_file1.2.2"/>
+ <menuitem style="appearance: none; height: 1em" label="SubSubFile3" id="mi_file1.2.3"/>
+ <menuitem style="appearance: none; height: 1em" label="SubSubFile4" id="mi_file1.2.4"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menubar>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/states/z_frames.html b/accessible/tests/mochitest/states/z_frames.html
new file mode 100644
index 0000000000..819adee63e
--- /dev/null
+++ b/accessible/tests/mochitest/states/z_frames.html
@@ -0,0 +1,11 @@
+<html>
+<!--
+Auxilliary file used as frame source.
+-->
+<head>
+</head>
+<body>
+<p>Frame source body has no role</p>
+</body>
+</html>
+
diff --git a/accessible/tests/mochitest/states/z_frames_article.html b/accessible/tests/mochitest/states/z_frames_article.html
new file mode 100644
index 0000000000..a7a69b4dae
--- /dev/null
+++ b/accessible/tests/mochitest/states/z_frames_article.html
@@ -0,0 +1,11 @@
+<html>
+<!--
+Auxilliary file used as frame source.
+-->
+<head>
+</head>
+<body role="article">
+<p>Article</p>
+</body>
+</html>
+
diff --git a/accessible/tests/mochitest/states/z_frames_checkbox.html b/accessible/tests/mochitest/states/z_frames_checkbox.html
new file mode 100644
index 0000000000..7997644243
--- /dev/null
+++ b/accessible/tests/mochitest/states/z_frames_checkbox.html
@@ -0,0 +1,11 @@
+<html>
+<!--
+Auxilliary file used as frame source.
+-->
+<head>
+</head>
+<body role="checkbox">
+<p>Checkbox</p>
+</body>
+</html>
+
diff --git a/accessible/tests/mochitest/states/z_frames_textbox.html b/accessible/tests/mochitest/states/z_frames_textbox.html
new file mode 100644
index 0000000000..0f4e1b9d66
--- /dev/null
+++ b/accessible/tests/mochitest/states/z_frames_textbox.html
@@ -0,0 +1,11 @@
+<html>
+<!--
+Auxilliary file used as frame source.
+-->
+<head>
+</head>
+<body role="textbox">
+<p>Texbox</p>
+</body>
+</html>
+
diff --git a/accessible/tests/mochitest/states/z_frames_update.html b/accessible/tests/mochitest/states/z_frames_update.html
new file mode 100644
index 0000000000..7e2cc83539
--- /dev/null
+++ b/accessible/tests/mochitest/states/z_frames_update.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<script>
+function replaceBody() {
+ var accService = Cc["@mozilla.org/accessibilityService;1"].
+ getService(Ci.nsIAccessibilityService);
+ accService.getAccessibleFor(document);
+
+ var newBody = document.createElement("body");
+ newBody.setAttribute("contentEditable", "true");
+ newBody.textContent = "New Hello";
+ document.documentElement.replaceChild(newBody, document.body);
+ getComputedStyle(newBody, "").color;
+}
+</script>
+</head>
+<body onload="replaceBody();">
+OLD hello
+</body>
+</html>
+
diff --git a/accessible/tests/mochitest/table.js b/accessible/tests/mochitest/table.js
new file mode 100644
index 0000000000..9e4989cced
--- /dev/null
+++ b/accessible/tests/mochitest/table.js
@@ -0,0 +1,851 @@
+/**
+ * This file provides set of helper functions to test nsIAccessibleTable
+ * interface.
+ *
+ * Required:
+ * common.js
+ * role.js
+ * states.js
+ */
+/* import-globals-from common.js */
+/* import-globals-from role.js */
+/* import-globals-from states.js */
+
+/**
+ * Constants used to describe cells array.
+ */
+const kDataCell = 1; // Indicates the cell is origin data cell
+const kRowHeaderCell = 2; // Indicates the cell is row header cell
+const kColHeaderCell = 4; // Indicated the cell is column header cell
+const kOrigin = kDataCell | kRowHeaderCell | kColHeaderCell;
+
+const kRowSpanned = 8; // Indicates the cell is not origin and row spanned
+const kColSpanned = 16; // Indicates the cell is not origin and column spanned
+const kSpanned = kRowSpanned | kColSpanned;
+
+/**
+ * Constants to define column header type.
+ */
+const kNoColumnHeader = 0;
+const kListboxColumnHeader = 1;
+const kTreeColumnHeader = 2;
+
+/**
+ * Constants to define table type.
+ */
+const kTable = 0;
+const kTreeTable = 1;
+const kMathTable = 2;
+
+/**
+ * Test table structure and related methods.
+ *
+ * @param aIdentifier [in] table accessible identifier
+ * @param aCellsArray [in] two dimensional array (row X columns) of
+ * cell types (see constants defined above).
+ * @param aColHeaderType [in] specifies wether column header cells are
+ * arranged into the list.
+ * @param aCaption [in] caption text if any
+ * @param aSummary [in] summary text if any
+ * @param aTableType [in] specifies the table type.
+ * @param aRowRoles [in] array of row roles.
+ */
+function testTableStruct(
+ aIdentifier,
+ aCellsArray,
+ aColHeaderType,
+ aCaption,
+ aSummary,
+ aTableType,
+ aRowRoles
+) {
+ var tableNode = getNode(aIdentifier);
+ var isGrid =
+ tableNode.getAttribute("role") == "grid" ||
+ tableNode.getAttribute("role") == "treegrid" ||
+ tableNode.localName == "tree";
+
+ var rowCount = aCellsArray.length;
+ var colsCount = aCellsArray[0] ? aCellsArray[0].length : 0;
+
+ // Test table accessible tree.
+ var tableObj = {
+ children: [],
+ };
+ switch (aTableType) {
+ case kTable:
+ tableObj.role = ROLE_TABLE;
+ break;
+ case kTreeTable:
+ tableObj.role = ROLE_TREE_TABLE;
+ break;
+ case kMathTable:
+ tableObj.role = ROLE_MATHML_TABLE;
+ break;
+ }
+
+ // caption accessible handling
+ if (aCaption) {
+ var captionObj = {
+ role: ROLE_CAPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: aCaption,
+ },
+ ],
+ };
+
+ tableObj.children.push(captionObj);
+ }
+
+ // special types of column headers handling
+ if (aColHeaderType) {
+ var headersObj = {
+ role: ROLE_LIST,
+ children: [],
+ };
+
+ for (let idx = 0; idx < colsCount; idx++) {
+ var headerCellObj = {
+ role: ROLE_COLUMNHEADER,
+ };
+ headersObj.children.push(headerCellObj);
+ }
+
+ if (aColHeaderType == kTreeColumnHeader) {
+ headersObj.children.push({
+ role: ROLE_PUSHBUTTON,
+ });
+ headersObj.children.push({
+ role: ROLE_MENUPOPUP,
+ });
+ }
+
+ tableObj.children.push(headersObj);
+ }
+
+ // rows and cells accessibles
+ for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ let rowObj = {
+ role: aRowRoles ? aRowRoles[rowIdx] : ROLE_ROW,
+ children: [],
+ };
+
+ for (let colIdx = 0; colIdx < colsCount; colIdx++) {
+ let celltype = aCellsArray[rowIdx][colIdx];
+
+ var role = ROLE_NOTHING;
+ switch (celltype) {
+ case kDataCell:
+ role =
+ aTableType == kMathTable
+ ? ROLE_MATHML_CELL
+ : isGrid
+ ? ROLE_GRID_CELL
+ : ROLE_CELL;
+ break;
+ case kRowHeaderCell:
+ role = ROLE_ROWHEADER;
+ break;
+ case kColHeaderCell:
+ role = ROLE_COLUMNHEADER;
+ break;
+ }
+
+ if (role != ROLE_NOTHING) {
+ var cellObj = { role };
+ rowObj.children.push(cellObj);
+ }
+ }
+
+ tableObj.children.push(rowObj);
+ }
+
+ testAccessibleTree(aIdentifier, tableObj);
+
+ // Test table table interface.
+ var table = getAccessible(aIdentifier, [nsIAccessibleTable]);
+
+ // summary
+ if (aSummary) {
+ is(
+ table.summary,
+ aSummary,
+ "Wrong summary of the table " + prettyName(aIdentifier)
+ );
+ }
+
+ // rowCount and columnCount
+ is(
+ table.rowCount,
+ rowCount,
+ "Wrong rows count of " + prettyName(aIdentifier)
+ );
+ is(
+ table.columnCount,
+ colsCount,
+ "Wrong columns count of " + prettyName(aIdentifier)
+ );
+
+ // rows and columns extents
+ for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (let colIdx = 0; colIdx < colsCount; colIdx++) {
+ let celltype = aCellsArray[rowIdx][colIdx];
+ if (celltype & kOrigin) {
+ // table getRowExtentAt
+ var rowExtent = table.getRowExtentAt(rowIdx, colIdx);
+ let idx;
+ /* eslint-disable no-empty */
+ for (
+ idx = rowIdx + 1;
+ idx < rowCount && aCellsArray[idx][colIdx] & kRowSpanned;
+ idx++
+ ) {}
+ /* eslint-enable no-empty */
+
+ var expectedRowExtent = idx - rowIdx;
+ is(
+ rowExtent,
+ expectedRowExtent,
+ "getRowExtentAt: Wrong number of spanned rows at (" +
+ rowIdx +
+ ", " +
+ colIdx +
+ ") for " +
+ prettyName(aIdentifier)
+ );
+
+ // table getColumnExtentAt
+ var colExtent = table.getColumnExtentAt(rowIdx, colIdx);
+ /* eslint-disable no-empty */
+ for (
+ idx = colIdx + 1;
+ idx < colsCount && aCellsArray[rowIdx][idx] & kColSpanned;
+ idx++
+ ) {}
+ /* eslint-enable no-empty */
+
+ var expectedColExtent = idx - colIdx;
+ is(
+ colExtent,
+ expectedColExtent,
+ "getColumnExtentAt: Wrong number of spanned columns at (" +
+ rowIdx +
+ ", " +
+ colIdx +
+ ") for " +
+ prettyName(aIdentifier)
+ );
+
+ // cell rowExtent and columnExtent
+ var cell = getAccessible(table.getCellAt(rowIdx, colIdx), [
+ nsIAccessibleTableCell,
+ ]);
+
+ is(
+ cell.rowExtent,
+ expectedRowExtent,
+ "rowExtent: Wrong number of spanned rows at (" +
+ rowIdx +
+ ", " +
+ colIdx +
+ ") for " +
+ prettyName(aIdentifier)
+ );
+
+ is(
+ cell.columnExtent,
+ expectedColExtent,
+ "columnExtent: Wrong number of spanned column at (" +
+ rowIdx +
+ ", " +
+ colIdx +
+ ") for " +
+ prettyName(aIdentifier)
+ );
+ }
+ }
+ }
+}
+
+/**
+ * Test table indexes.
+ *
+ * @param aIdentifier [in] table accessible identifier
+ * @param aIdxes [in] two dimensional array of cell indexes
+ */
+function testTableIndexes(aIdentifier, aIdxes) {
+ var tableAcc = getAccessible(aIdentifier, [nsIAccessibleTable]);
+ if (!tableAcc) {
+ return;
+ }
+
+ var obtainedRowIdx, obtainedColIdx, obtainedIdx;
+ var cellAcc;
+
+ var id = prettyName(aIdentifier);
+
+ var rowCount = aIdxes.length;
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ var colCount = aIdxes[rowIdx].length;
+ for (var colIdx = 0; colIdx < colCount; colIdx++) {
+ var idx = aIdxes[rowIdx][colIdx];
+
+ // getCellAt
+ try {
+ cellAcc = null;
+ cellAcc = tableAcc.getCellAt(rowIdx, colIdx);
+ } catch (e) {}
+
+ ok(
+ (idx != -1 && cellAcc) || (idx == -1 && !cellAcc),
+ id +
+ ": Can't get cell accessible at row = " +
+ rowIdx +
+ ", column = " +
+ colIdx
+ );
+
+ if (idx != -1) {
+ // getRowIndexAt
+ var origRowIdx = rowIdx;
+ while (
+ origRowIdx > 0 &&
+ aIdxes[rowIdx][colIdx] == aIdxes[origRowIdx - 1][colIdx]
+ ) {
+ origRowIdx--;
+ }
+
+ try {
+ obtainedRowIdx = tableAcc.getRowIndexAt(idx);
+ } catch (e) {
+ ok(
+ false,
+ id + ": can't get row index for cell index " + idx + "," + e
+ );
+ }
+
+ is(
+ obtainedRowIdx,
+ origRowIdx,
+ id + ": row for index " + idx + " is not correct (getRowIndexAt)"
+ );
+
+ // getColumnIndexAt
+ var origColIdx = colIdx;
+ while (
+ origColIdx > 0 &&
+ aIdxes[rowIdx][colIdx] == aIdxes[rowIdx][origColIdx - 1]
+ ) {
+ origColIdx--;
+ }
+
+ try {
+ obtainedColIdx = tableAcc.getColumnIndexAt(idx);
+ } catch (e) {
+ ok(
+ false,
+ id + ": can't get column index for cell index " + idx + "," + e
+ );
+ }
+
+ is(
+ obtainedColIdx,
+ origColIdx,
+ id +
+ ": column for index " +
+ idx +
+ " is not correct (getColumnIndexAt)"
+ );
+
+ // getRowAndColumnIndicesAt
+ var obtainedRowIdxObj = {},
+ obtainedColIdxObj = {};
+ try {
+ tableAcc.getRowAndColumnIndicesAt(
+ idx,
+ obtainedRowIdxObj,
+ obtainedColIdxObj
+ );
+ } catch (e) {
+ ok(
+ false,
+ id +
+ ": can't get row and column indices for cell index " +
+ idx +
+ "," +
+ e
+ );
+ }
+
+ is(
+ obtainedRowIdxObj.value,
+ origRowIdx,
+ id +
+ ": row for index " +
+ idx +
+ " is not correct (getRowAndColumnIndicesAt)"
+ );
+ is(
+ obtainedColIdxObj.value,
+ origColIdx,
+ id +
+ ": column for index " +
+ idx +
+ " is not correct (getRowAndColumnIndicesAt)"
+ );
+
+ if (cellAcc) {
+ var cellId = prettyName(cellAcc);
+ cellAcc = getAccessible(cellAcc, [nsIAccessibleTableCell]);
+
+ // cell: 'table-cell-index' attribute
+ var attrs = cellAcc.attributes;
+ var strIdx = "";
+ try {
+ strIdx = attrs.getStringProperty("table-cell-index");
+ } catch (e) {
+ ok(
+ false,
+ cellId +
+ ": no cell index from object attributes on the cell accessible at index " +
+ idx +
+ "."
+ );
+ }
+
+ if (strIdx) {
+ is(
+ parseInt(strIdx),
+ idx,
+ cellId +
+ ": cell index from object attributes of cell accessible isn't corrent."
+ );
+ }
+
+ // cell: table
+ try {
+ is(
+ cellAcc.table,
+ tableAcc,
+ cellId + ": wrong table accessible for the cell."
+ );
+ } catch (e) {
+ ok(false, cellId + ": can't get table accessible from the cell.");
+ }
+
+ // cell: getRowIndex
+ try {
+ obtainedRowIdx = cellAcc.rowIndex;
+ } catch (e) {
+ ok(
+ false,
+ cellId +
+ ": can't get row index of the cell at index " +
+ idx +
+ "," +
+ e
+ );
+ }
+
+ is(
+ obtainedRowIdx,
+ origRowIdx,
+ cellId + ": row for the cell at index " + idx + " is not correct"
+ );
+
+ // cell: getColumnIndex
+ try {
+ obtainedColIdx = cellAcc.columnIndex;
+ } catch (e) {
+ ok(
+ false,
+ cellId +
+ ": can't get column index of the cell at index " +
+ idx +
+ "," +
+ e
+ );
+ }
+
+ is(
+ obtainedColIdx,
+ origColIdx,
+ id + ": column for the cell at index " + idx + " is not correct"
+ );
+ }
+ }
+
+ // getCellIndexAt
+ try {
+ obtainedIdx = tableAcc.getCellIndexAt(rowIdx, colIdx);
+ } catch (e) {
+ obtainedIdx = -1;
+ }
+
+ is(
+ obtainedIdx,
+ idx,
+ id +
+ ": row " +
+ rowIdx +
+ " /column " +
+ colIdx +
+ " and index " +
+ obtainedIdx +
+ " aren't inconsistent."
+ );
+ }
+ }
+}
+
+/**
+ * Test table getters selection methods.
+ *
+ * @param aIdentifier [in] table accessible identifier
+ * @param aCellsArray [in] two dimensional array (row X columns) of cells
+ * states (either boolean (selected/unselected) if cell is
+ * origin, otherwise kRowSpanned or kColSpanned constant).
+ * @param aMsg [in] text appended before every message
+ */
+function testTableSelection(aIdentifier, aCellsArray, aMsg) {
+ var msg = aMsg ? aMsg : "";
+ var acc = getAccessible(aIdentifier, [nsIAccessibleTable]);
+ if (!acc) {
+ return;
+ }
+
+ var rowCount = aCellsArray.length;
+ var colsCount = aCellsArray[0].length;
+
+ // Columns selection tests.
+ var selCols = [];
+
+ // isColumnSelected test
+ for (let colIdx = 0; colIdx < colsCount; colIdx++) {
+ var isColSelected = true;
+ for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ if (
+ !aCellsArray[rowIdx][colIdx] ||
+ aCellsArray[rowIdx][colIdx] == undefined
+ ) {
+ isColSelected = false;
+ break;
+ }
+ }
+
+ is(
+ acc.isColumnSelected(colIdx),
+ isColSelected,
+ msg +
+ "Wrong selection state of " +
+ colIdx +
+ " column for " +
+ prettyName(aIdentifier)
+ );
+
+ if (isColSelected) {
+ selCols.push(colIdx);
+ }
+ }
+
+ // selectedColsCount test
+ is(
+ acc.selectedColumnCount,
+ selCols.length,
+ msg + "Wrong count of selected columns for " + prettyName(aIdentifier)
+ );
+
+ // getSelectedColumns test
+ var actualSelCols = acc.getSelectedColumnIndices();
+
+ var actualSelColsCount = actualSelCols.length;
+ is(
+ actualSelColsCount,
+ selCols.length,
+ msg +
+ "Wrong count of selected columns for " +
+ prettyName(aIdentifier) +
+ "from getSelectedColumns."
+ );
+
+ for (let i = 0; i < actualSelColsCount; i++) {
+ is(
+ actualSelCols[i],
+ selCols[i],
+ msg + "Column at index " + selCols[i] + " should be selected."
+ );
+ }
+
+ // Rows selection tests.
+ var selRows = [];
+
+ // isRowSelected test
+ for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ var isRowSelected = true;
+ for (let colIdx = 0; colIdx < colsCount; colIdx++) {
+ if (
+ !aCellsArray[rowIdx][colIdx] ||
+ aCellsArray[rowIdx][colIdx] == undefined
+ ) {
+ isRowSelected = false;
+ break;
+ }
+ }
+
+ is(
+ acc.isRowSelected(rowIdx),
+ isRowSelected,
+ msg +
+ "Wrong selection state of " +
+ rowIdx +
+ " row for " +
+ prettyName(aIdentifier)
+ );
+
+ if (isRowSelected) {
+ selRows.push(rowIdx);
+ }
+ }
+
+ // selectedRowCount test
+ is(
+ acc.selectedRowCount,
+ selRows.length,
+ msg + "Wrong count of selected rows for " + prettyName(aIdentifier)
+ );
+
+ // getSelectedRows test
+ var actualSelRows = acc.getSelectedRowIndices();
+
+ var actualSelrowCount = actualSelRows.length;
+ is(
+ actualSelrowCount,
+ selRows.length,
+ msg +
+ "Wrong count of selected rows for " +
+ prettyName(aIdentifier) +
+ "from getSelectedRows."
+ );
+
+ for (let i = 0; i < actualSelrowCount; i++) {
+ is(
+ actualSelRows[i],
+ selRows[i],
+ msg + "Row at index " + selRows[i] + " should be selected."
+ );
+ }
+
+ // Cells selection tests.
+ var selCells = [];
+
+ // isCellSelected test
+ for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (let colIdx = 0; colIdx < colsCount; colIdx++) {
+ if (aCellsArray[rowIdx][colIdx] & kSpanned) {
+ continue;
+ }
+
+ var isSelected = !!aCellsArray[rowIdx][colIdx];
+ is(
+ acc.isCellSelected(rowIdx, colIdx),
+ isSelected,
+ msg +
+ "Wrong selection state of cell at " +
+ rowIdx +
+ " row and " +
+ colIdx +
+ " column for " +
+ prettyName(aIdentifier)
+ );
+
+ if (aCellsArray[rowIdx][colIdx]) {
+ selCells.push(acc.getCellIndexAt(rowIdx, colIdx));
+ }
+ }
+ }
+
+ // selectedCellCount tests
+ is(
+ acc.selectedCellCount,
+ selCells.length,
+ msg + "Wrong count of selected cells for " + prettyName(aIdentifier)
+ );
+
+ // getSelectedCellIndices test
+ var actualSelCells = acc.getSelectedCellIndices();
+
+ var actualSelCellsCount = actualSelCells.length;
+ is(
+ actualSelCellsCount,
+ selCells.length,
+ msg +
+ "Wrong count of selected cells for " +
+ prettyName(aIdentifier) +
+ "from getSelectedCells."
+ );
+
+ for (let i = 0; i < actualSelCellsCount; i++) {
+ is(
+ actualSelCells[i],
+ selCells[i],
+ msg +
+ "getSelectedCellIndices: Cell at index " +
+ selCells[i] +
+ " should be selected."
+ );
+ }
+
+ // selectedCells and isSelected tests
+ var actualSelCellsArray = acc.selectedCells;
+ for (let i = 0; i < actualSelCellsCount; i++) {
+ var actualSelCellAccessible = actualSelCellsArray.queryElementAt(
+ i,
+ nsIAccessibleTableCell
+ );
+
+ let colIdx = acc.getColumnIndexAt(selCells[i]);
+ let rowIdx = acc.getRowIndexAt(selCells[i]);
+ var expectedSelCellAccessible = acc.getCellAt(rowIdx, colIdx);
+
+ is(
+ actualSelCellAccessible,
+ expectedSelCellAccessible,
+ msg +
+ "getSelectedCells: Cell at index " +
+ selCells[i] +
+ " should be selected."
+ );
+
+ ok(
+ actualSelCellAccessible.isSelected(),
+ "isSelected: Cell at index " + selCells[i] + " should be selected."
+ );
+ }
+
+ // selected states tests
+ for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (let colIdx = 0; colIdx < colsCount; colIdx++) {
+ if (aCellsArray[rowIdx][colIdx] & kSpanned) {
+ continue;
+ }
+
+ var cell = acc.getCellAt(rowIdx, colIdx);
+ var isSel = aCellsArray[rowIdx][colIdx];
+ if (isSel == undefined) {
+ testStates(cell, 0, 0, STATE_SELECTABLE | STATE_SELECTED);
+ } else if (isSel) {
+ testStates(cell, STATE_SELECTED);
+ } else {
+ testStates(cell, STATE_SELECTABLE, 0, STATE_SELECTED);
+ }
+ }
+ }
+}
+
+/**
+ * Test columnHeaderCells and rowHeaderCells of accessible table.
+ */
+function testHeaderCells(aHeaderInfoMap) {
+ for (var testIdx = 0; testIdx < aHeaderInfoMap.length; testIdx++) {
+ var dataCellIdentifier = aHeaderInfoMap[testIdx].cell;
+ var dataCell = getAccessible(dataCellIdentifier, [nsIAccessibleTableCell]);
+
+ // row header cells
+ var rowHeaderCells = aHeaderInfoMap[testIdx].rowHeaderCells;
+ var rowHeaderCellsCount = rowHeaderCells.length;
+ var actualRowHeaderCells = dataCell.rowHeaderCells;
+ var actualRowHeaderCellsCount = actualRowHeaderCells.length;
+
+ is(
+ actualRowHeaderCellsCount,
+ rowHeaderCellsCount,
+ "Wrong number of row header cells for the cell " +
+ prettyName(dataCellIdentifier)
+ );
+
+ if (actualRowHeaderCellsCount == rowHeaderCellsCount) {
+ for (let idx = 0; idx < rowHeaderCellsCount; idx++) {
+ var rowHeaderCell = getAccessible(rowHeaderCells[idx]);
+ var actualRowHeaderCell = actualRowHeaderCells.queryElementAt(
+ idx,
+ nsIAccessible
+ );
+ isObject(
+ actualRowHeaderCell,
+ rowHeaderCell,
+ "Wrong row header cell at index " +
+ idx +
+ " for the cell " +
+ dataCellIdentifier
+ );
+ }
+ }
+
+ // column header cells
+ var colHeaderCells = aHeaderInfoMap[testIdx].columnHeaderCells;
+ var colHeaderCellsCount = colHeaderCells.length;
+ var actualColHeaderCells = dataCell.columnHeaderCells;
+ var actualColHeaderCellsCount = actualColHeaderCells.length;
+
+ is(
+ actualColHeaderCellsCount,
+ colHeaderCellsCount,
+ "Wrong number of column header cells for the cell " +
+ prettyName(dataCellIdentifier)
+ );
+
+ if (actualColHeaderCellsCount == colHeaderCellsCount) {
+ for (let idx = 0; idx < colHeaderCellsCount; idx++) {
+ var colHeaderCell = getAccessible(colHeaderCells[idx]);
+ var actualColHeaderCell = actualColHeaderCells.queryElementAt(
+ idx,
+ nsIAccessible
+ );
+ isObject(
+ actualColHeaderCell,
+ colHeaderCell,
+ "Wrong column header cell at index " +
+ idx +
+ " for the cell " +
+ dataCellIdentifier
+ );
+ }
+ }
+ }
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// private implementation
+
+/**
+ * Return row and column of orig cell for the given spanned cell.
+ */
+function getOrigRowAndColumn(aCellsArray, aRowIdx, aColIdx) {
+ var cellState = aCellsArray[aRowIdx][aColIdx];
+
+ var origRowIdx = aRowIdx,
+ origColIdx = aColIdx;
+ if (cellState & kRowSpanned) {
+ for (var prevRowIdx = aRowIdx - 1; prevRowIdx >= 0; prevRowIdx--) {
+ let prevCellState = aCellsArray[prevRowIdx][aColIdx];
+ if (!(prevCellState & kRowSpanned)) {
+ origRowIdx = prevRowIdx;
+ break;
+ }
+ }
+ }
+
+ if (cellState & kColSpanned) {
+ for (var prevColIdx = aColIdx - 1; prevColIdx >= 0; prevColIdx--) {
+ let prevCellState = aCellsArray[aRowIdx][prevColIdx];
+ if (!(prevCellState & kColSpanned)) {
+ origColIdx = prevColIdx;
+ break;
+ }
+ }
+ }
+
+ return [origRowIdx, origColIdx];
+}
diff --git a/accessible/tests/mochitest/table/a11y.ini b/accessible/tests/mochitest/table/a11y.ini
new file mode 100644
index 0000000000..550fc54e04
--- /dev/null
+++ b/accessible/tests/mochitest/table/a11y.ini
@@ -0,0 +1,24 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_css_tables.html]
+[test_headers_ariagrid.html]
+[test_headers_ariatable.html]
+[test_headers_table.html]
+[test_headers_tree.xhtml]
+[test_indexes_ariagrid.html]
+[test_indexes_table.html]
+[test_indexes_tree.xhtml]
+[test_layoutguess.html]
+[test_mtable.html]
+[test_sels_ariagrid.html]
+[test_sels_table.html]
+[test_sels_tree.xhtml]
+[test_struct_ariagrid.html]
+[test_struct_ariatreegrid.html]
+[test_struct_table.html]
+[test_struct_tree.xhtml]
+[test_table_1.html]
+[test_table_2.html]
+[test_table_mutation.html]
diff --git a/accessible/tests/mochitest/table/test_css_tables.html b/accessible/tests/mochitest/table/test_css_tables.html
new file mode 100644
index 0000000000..65877564e4
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_css_tables.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <title>CSS display:table is not a table</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // elements with display:table
+
+ // only display:table
+ var accTree =
+ { SECTION: [
+ { TEXT_LEAF: [ ] },
+ ] };
+ testAccessibleTree("table1", accTree);
+
+ // only display:table and display:table-cell
+ accTree =
+ { SECTION: [
+ { SECTION: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] };
+ testAccessibleTree("table2", accTree);
+
+ // display:table, display:table-row, and display:table-cell
+ accTree =
+ { SECTION: [
+ { SECTION: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] };
+ testAccessibleTree("table3", accTree);
+
+ // display:table, display:table-row-group, display:table-row, and display:table-cell
+ accTree =
+ { SECTION: [
+ { SECTION: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] };
+ testAccessibleTree("table4", accTree);
+
+ // display:inline-table
+ accTree =
+ { TEXT_CONTAINER: [
+ { TEXT_CONTAINER: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] };
+ testAccessibleTree("table5", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title=" div with display:table exposes table semantics"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1007975">Mozilla Bug 1007975</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="table1" style="display:table">
+ table1
+ </div>
+
+ <div id="table2" style="display:table">
+ <div style="display:table-cell">table2</div>
+ </div>
+
+ <div id="table3" style="display:table">
+ <div style="display:table-row">
+ <div style="display:table-cell">table3</div>
+ </div>
+ </div>
+
+ <div id="table4" style="display:table">
+ <div style="display:table-row-group">
+ <div style="display:table-row">
+ <div style="display:table-cell">table4</div>
+ </div>
+ </div>
+ </div>
+
+ <div>
+ <span id="table5" style="display:inline-table">
+ <span style="display:table-row">
+ <span style="display:table-cell">table5</div>
+ </span>
+ </span>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_headers_ariagrid.html b/accessible/tests/mochitest/table/test_headers_ariagrid.html
new file mode 100644
index 0000000000..7b2c3f3dbf
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_headers_ariagrid.html
@@ -0,0 +1,183 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <title>Table header information cells for ARIA grid</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // column and row headers from markup
+
+ let headerInfoMap = [
+ {
+ cell: "table_dc_1",
+ rowHeaderCells: [ "table_rh_1" ],
+ columnHeaderCells: [ "table_ch_2" ],
+ },
+ {
+ cell: "table_dc_2",
+ rowHeaderCells: [ "table_rh_1" ],
+ columnHeaderCells: [ "table_ch_3" ],
+ },
+ {
+ cell: "table_dc_3",
+ rowHeaderCells: [ "table_rh_2" ],
+ columnHeaderCells: [ "table_ch_2" ],
+ },
+ {
+ cell: "table_dc_4",
+ rowHeaderCells: [ "table_rh_2" ],
+ columnHeaderCells: [ "table_ch_3" ],
+ },
+ {
+ cell: "table_rh_1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table_ch_1" ],
+ },
+ {
+ cell: "table_rh_2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table_ch_1" ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+
+ // ////////////////////////////////////////////////////////////////////////
+ // column and row headers from markup for grid.
+
+ headerInfoMap = [
+ {
+ // not focusable cell (ARIAGridCellAccessible is used)
+ cell: "table2_dc_1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table2_ch_1" ],
+ },
+ {
+ // focusable cell (ARIAGridCellAccessible is used)
+ cell: "table2_dc_2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table2_ch_2" ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+
+ // ////////////////////////////////////////////////////////////////////////
+ // column and row headers from markup for one more grid.
+
+ headerInfoMap = [
+ {
+ // ARIAGridCellAccessible is used
+ cell: "t3_dc_1",
+ rowHeaderCells: [ "t3_rh_1" ],
+ columnHeaderCells: [ ],
+ },
+ {
+ // ARIAGridCellAccessible is used (inside rowgroup)
+ cell: "t3_dc_2",
+ rowHeaderCells: [ "t3_rh_2" ],
+ columnHeaderCells: [ ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="implement IAccessibleTable2"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424">Mozilla Bug 512424</a>
+ <a target="_blank"
+ title="nsHTMLTableCellAccessible is used in dojo's ARIA grid"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Mozilla Bug 513848</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="grid">
+ <div role="row">
+ <span id="table_ch_1" role="columnheader">col_1</span>
+ <span id="table_ch_2" role="columnheader">col_2</span>
+ <span id="table_ch_3" role="columnheader">col_3</span>
+ </div>
+ <div role="row">
+ <span id="table_rh_1" role="rowheader">row_1</span>
+ <span id="table_dc_1" role="gridcell">cell1</span>
+ <span id="table_dc_2" role="gridcell">cell2</span>
+ </div>
+ <div role="row">
+ <span id="table_rh_2" role="rowheader">row_2</span>
+ <span id="table_dc_3" role="gridcell">cell3</span>
+ <span id="table_dc_4" role="gridcell">cell4</span>
+ </div>
+ </div>
+
+ <div role="grid">
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td id="table2_ch_1" role="columnheader">header1</td>
+ <td id="table2_ch_2" role="columnheader">header2</td>
+ </tr>
+ </table>
+ </div>
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td id="table2_dc_1" role="gridcell">cell1</td>
+ <td id="table2_dc_2" role="gridcell" tabindex="-1">cell2</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <div role="grid">
+ <table role="presentation">
+ <tbody role="presentation">
+ <tr role="row">
+ <th id="t3_rh_1" role="rowheader">Row 1</th>
+ <td id="t3_dc_1" role="gridcell" tabindex="-1">
+ Apple Inc.
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <div role="rowgroup" tabindex="0">
+ <table role="presentation">
+ <tbody role="presentation">
+ <tr role="row">
+ <th id="t3_rh_2" role="rowheader">Row 2</th>
+ <td id="t3_dc_2" role="gridcell" tabindex="-1">
+ Apple-Shmapple Inc.
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_headers_ariatable.html b/accessible/tests/mochitest/table/test_headers_ariatable.html
new file mode 100644
index 0000000000..1af3813cdc
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_headers_ariatable.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <title>Table header information cells for ARIA table</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // column and row headers from markup
+
+ const headerInfoMap = [
+ {
+ cell: "table_dc_1",
+ rowHeaderCells: [ "table_rh_1" ],
+ columnHeaderCells: [ "table_ch_2" ],
+ },
+ {
+ cell: "table_dc_2",
+ rowHeaderCells: [ "table_rh_1" ],
+ columnHeaderCells: [ "table_ch_3" ],
+ },
+ {
+ cell: "table_dc_3",
+ rowHeaderCells: [ "table_rh_2" ],
+ columnHeaderCells: [ "table_ch_2" ],
+ },
+ {
+ cell: "table_dc_4",
+ rowHeaderCells: [ "table_rh_2" ],
+ columnHeaderCells: [ "table_ch_3" ],
+ },
+ {
+ cell: "table_rh_1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table_ch_1" ],
+ },
+ {
+ cell: "table_rh_2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table_ch_1" ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="support ARIA table and cell roles"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173364">Bug 1173364</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="table">
+ <div role="row">
+ <span id="table_ch_1" role="columnheader">col_1</span>
+ <span id="table_ch_2" role="columnheader">col_2</span>
+ <span id="table_ch_3" role="columnheader">col_3</span>
+ </div>
+ <div role="row">
+ <span id="table_rh_1" role="rowheader">row_1</span>
+ <span id="table_dc_1" role="cell">cell1</span>
+ <span id="table_dc_2" role="cell">cell2</span>
+ </div>
+ <div role="row">
+ <span id="table_rh_2" role="rowheader">row_2</span>
+ <span id="table_dc_3" role="cell">cell3</span>
+ <span id="table_dc_4" role="cell">cell4</span>
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_headers_table.html b/accessible/tests/mochitest/table/test_headers_table.html
new file mode 100644
index 0000000000..0d7dafff4b
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_headers_table.html
@@ -0,0 +1,756 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <title>Table header information cells for HTML table</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // column header from thead and row header from @scope inside of tfoot
+
+ var headerInfoMap = [
+ {
+ cell: "table1_cell_1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table1_weekday", "table1_date" ],
+ },
+ {
+ cell: "table1_cell_2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table1_day", "table1_date" ],
+ },
+ {
+ cell: "table1_cell_3",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table1_qty" ],
+ },
+ {
+ cell: "table1_cell_4",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table1_weekday", "table1_date" ],
+ },
+ {
+ cell: "table1_cell_5",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table1_day", "table1_date" ],
+ },
+ {
+ cell: "table1_cell_6",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table1_qty" ],
+ },
+ {
+ cell: "table1_cell_7",
+ rowHeaderCells: [ "table1_total" ],
+ columnHeaderCells: [ "table1_qty" ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // column and row headers from thead and @scope
+
+ headerInfoMap = [
+ {
+ cell: "table2_cell_2",
+ rowHeaderCells: [ "table2_rh_1" ],
+ columnHeaderCells: [ "table2_ch_2" ],
+ },
+ {
+ cell: "table2_cell_3",
+ rowHeaderCells: [ "table2_rh_1" ],
+ columnHeaderCells: [ "table2_ch_3" ],
+ },
+ {
+ cell: "table2_cell_5",
+ rowHeaderCells: [ "table2_rh_2" ],
+ columnHeaderCells: [ "table2_ch_2" ],
+ },
+ {
+ cell: "table2_cell_6",
+ rowHeaderCells: [ "table2_rh_2" ],
+ columnHeaderCells: [ "table2_ch_3" ],
+ },
+ {
+ cell: "table2_rh_1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table2_ch_1" ],
+ },
+ {
+ cell: "table2_rh_2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table2_ch_1" ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // column headers from @headers
+
+ headerInfoMap = [
+ {
+ cell: "table3_cell_1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table3_ch_1" ],
+ },
+ {
+ cell: "table3_cell_2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table3_ch_2" ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // table consisted of one column
+
+ headerInfoMap = [
+ {
+ cell: "table4_cell",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table4_ch" ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // table consisted of one row
+
+ headerInfoMap = [
+ {
+ cell: "table5_cell",
+ rowHeaderCells: [ "table5_rh" ],
+ columnHeaderCells: [ ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // @headers points to table cells
+
+ headerInfoMap = [
+ {
+ cell: "table6_cell",
+ rowHeaderCells: [ "table6_rh" ],
+ columnHeaderCells: [ "table6_ch" ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // @scope="rowgroup" and @scope="row"
+
+ headerInfoMap = [
+ {
+ cell: "t7_r1c1",
+ rowHeaderCells: [ "t7_Mary", "t7_Females" ],
+ columnHeaderCells: [ "t7_1km" ],
+ },
+ {
+ cell: "t7_r1c2",
+ rowHeaderCells: [ "t7_Mary", "t7_Females" ],
+ columnHeaderCells: [ "t7_5km" ],
+ },
+ {
+ cell: "t7_r1c3",
+ rowHeaderCells: [ "t7_Mary", "t7_Females" ],
+ columnHeaderCells: [ "t7_10km" ],
+ },
+ {
+ cell: "t7_r2c1",
+ rowHeaderCells: [ "t7_Betsy", "t7_Females" ],
+ columnHeaderCells: [ "t7_1km" ],
+ },
+ {
+ cell: "t7_r2c2",
+ rowHeaderCells: [ "t7_Betsy", "t7_Females" ],
+ columnHeaderCells: [ "t7_5km" ],
+ },
+ {
+ cell: "t7_r2c3",
+ rowHeaderCells: [ "t7_Betsy", "t7_Females" ],
+ columnHeaderCells: [ "t7_10km" ],
+ },
+ {
+ cell: "t7_r3c1",
+ rowHeaderCells: [ "t7_Matt", "t7_Males" ],
+ columnHeaderCells: [ "t7_1km" ],
+ },
+ {
+ cell: "t7_r3c2",
+ rowHeaderCells: [ "t7_Matt", "t7_Males" ],
+ columnHeaderCells: [ "t7_5km" ],
+ },
+ {
+ cell: "t7_r3c3",
+ rowHeaderCells: [ "t7_Matt", "t7_Males" ],
+ columnHeaderCells: [ "t7_10km" ],
+ },
+ {
+ cell: "t7_r4c1",
+ rowHeaderCells: [ "t7_Todd", "t7_Males" ],
+ columnHeaderCells: [ "t7_1km" ],
+ },
+ {
+ cell: "t7_r4c2",
+ rowHeaderCells: [ "t7_Todd", "t7_Males" ],
+ columnHeaderCells: [ "t7_5km" ],
+ },
+ {
+ cell: "t7_r4c3",
+ rowHeaderCells: [ "t7_Todd", "t7_Males" ],
+ columnHeaderCells: [ "t7_10km" ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // @scope="colgroup" and @scope="col"
+
+ headerInfoMap = [
+ {
+ cell: "t8_r1c1",
+ rowHeaderCells: [ "t8_1km" ],
+ columnHeaderCells: [ "t8_Mary", "t8_Females" ],
+ },
+ {
+ cell: "t8_r1c2",
+ rowHeaderCells: [ "t8_1km" ],
+ columnHeaderCells: [ "t8_Betsy", "t8_Females" ],
+ },
+ {
+ cell: "t8_r1c3",
+ rowHeaderCells: [ "t8_1km" ],
+ columnHeaderCells: [ "t8_Matt", "t8_Males" ],
+ },
+ {
+ cell: "t8_r1c4",
+ rowHeaderCells: [ "t8_1km" ],
+ columnHeaderCells: [ "t8_Todd", "t8_Males" ],
+ },
+ {
+ cell: "t8_r2c1",
+ rowHeaderCells: [ "t8_5km" ],
+ columnHeaderCells: [ "t8_Mary", "t8_Females" ],
+ },
+ {
+ cell: "t8_r2c2",
+ rowHeaderCells: [ "t8_5km" ],
+ columnHeaderCells: [ "t8_Betsy", "t8_Females" ],
+ },
+ {
+ cell: "t8_r2c3",
+ rowHeaderCells: [ "t8_5km" ],
+ columnHeaderCells: [ "t8_Matt", "t8_Males" ],
+ },
+ {
+ cell: "t8_r2c4",
+ rowHeaderCells: [ "t8_5km" ],
+ columnHeaderCells: [ "t8_Todd", "t8_Males" ],
+ },
+ {
+ cell: "t8_r3c1",
+ rowHeaderCells: [ "t8_10km" ],
+ columnHeaderCells: [ "t8_Mary", "t8_Females" ],
+ },
+ {
+ cell: "t8_r3c2",
+ rowHeaderCells: [ "t8_10km" ],
+ columnHeaderCells: [ "t8_Betsy", "t8_Females" ],
+ },
+ {
+ cell: "t8_r3c3",
+ rowHeaderCells: [ "t8_10km" ],
+ columnHeaderCells: [ "t8_Matt", "t8_Males" ],
+ },
+ {
+ cell: "t8_r3c4",
+ rowHeaderCells: [ "t8_10km" ],
+ columnHeaderCells: [ "t8_Todd", "t8_Males" ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // spanned table header cells (v1), @headers define header order
+
+ headerInfoMap = [
+ {
+ cell: "t9_r1c1",
+ rowHeaderCells: [ "t9_females", "t9_mary" ],
+ columnHeaderCells: [ "t9_1km" ],
+ },
+ {
+ cell: "t9_r1c2",
+ rowHeaderCells: [ "t9_females", "t9_mary" ],
+ columnHeaderCells: [ "t9_5km" ],
+ },
+ {
+ cell: "t9_r1c3",
+ rowHeaderCells: [ "t9_females", "t9_mary" ],
+ columnHeaderCells: [ "t9_10km" ],
+ },
+ {
+ cell: "t9_r2c1",
+ rowHeaderCells: [ "t9_females", "t9_betsy" ],
+ columnHeaderCells: [ "t9_1km" ],
+ },
+ {
+ cell: "t9_r2c2",
+ rowHeaderCells: [ "t9_females", "t9_betsy" ],
+ columnHeaderCells: [ "t9_5km" ],
+ },
+ {
+ cell: "t9_r2c3",
+ rowHeaderCells: [ "t9_females", "t9_betsy" ],
+ columnHeaderCells: [ "t9_10km" ],
+ },
+ {
+ cell: "t9_r3c1",
+ rowHeaderCells: [ "t9_males", "t9_matt" ],
+ columnHeaderCells: [ "t9_1km" ],
+ },
+ {
+ cell: "t9_r3c2",
+ rowHeaderCells: [ "t9_males", "t9_matt" ],
+ columnHeaderCells: [ "t9_5km" ],
+ },
+ {
+ cell: "t9_r3c3",
+ rowHeaderCells: [ "t9_males", "t9_matt" ],
+ columnHeaderCells: [ "t9_10km" ],
+ },
+ {
+ cell: "t9_r4c1",
+ rowHeaderCells: [ "t9_males", "t9_todd" ],
+ columnHeaderCells: [ "t9_1km" ],
+ },
+ {
+ cell: "t9_r4c2",
+ rowHeaderCells: [ "t9_males", "t9_todd" ],
+ columnHeaderCells: [ "t9_5km" ],
+ },
+ {
+ cell: "t9_r4c3",
+ rowHeaderCells: [ "t9_males", "t9_todd" ],
+ columnHeaderCells: [ "t9_10km" ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // spanned table header cells (v2), @headers define header order
+
+ headerInfoMap = [
+ {
+ cell: "t10_r1c1",
+ rowHeaderCells: [ "t10_1km" ],
+ columnHeaderCells: [ "t10_females", "t10_mary" ],
+ },
+ {
+ cell: "t10_r1c2",
+ rowHeaderCells: [ "t10_1km" ],
+ columnHeaderCells: [ "t10_females", "t10_betsy" ],
+ },
+ {
+ cell: "t10_r1c3",
+ rowHeaderCells: [ "t10_1km" ],
+ columnHeaderCells: [ "t10_males", "t10_matt" ],
+ },
+ {
+ cell: "t10_r1c4",
+ rowHeaderCells: [ "t10_1km" ],
+ columnHeaderCells: [ "t10_males", "t10_todd" ],
+ },
+ {
+ cell: "t10_r2c1",
+ rowHeaderCells: [ "t10_5km" ],
+ columnHeaderCells: [ "t10_females", "t10_mary" ],
+ },
+ {
+ cell: "t10_r2c2",
+ rowHeaderCells: [ "t10_5km" ],
+ columnHeaderCells: [ "t10_females", "t10_betsy" ],
+ },
+ {
+ cell: "t10_r2c3",
+ rowHeaderCells: [ "t10_5km" ],
+ columnHeaderCells: [ "t10_males", "t10_matt" ],
+ },
+ {
+ cell: "t10_r2c4",
+ rowHeaderCells: [ "t10_5km" ],
+ columnHeaderCells: [ "t10_males", "t10_todd" ],
+ },
+ {
+ cell: "t10_r3c1",
+ rowHeaderCells: [ "t10_10km" ],
+ columnHeaderCells: [ "t10_females", "t10_mary" ],
+ },
+ {
+ cell: "t10_r3c2",
+ rowHeaderCells: [ "t10_10km" ],
+ columnHeaderCells: [ "t10_females", "t10_betsy" ],
+ },
+ {
+ cell: "t10_r3c3",
+ rowHeaderCells: [ "t10_10km" ],
+ columnHeaderCells: [ "t10_males", "t10_matt" ],
+ },
+ {
+ cell: "t10_r3c4",
+ rowHeaderCells: [ "t10_10km" ],
+ columnHeaderCells: [ "t10_males", "t10_todd" ],
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Ensure correct column headers after colspan in a previous row.
+ headerInfoMap = [
+ {
+ cell: "t11r1c1",
+ columnHeaderCells: [],
+ rowHeaderCells: [],
+ },
+ {
+ cell: "t11r1c2",
+ columnHeaderCells: [],
+ rowHeaderCells: [],
+ },
+ {
+ cell: "t11r2c1_2",
+ columnHeaderCells: ["t11r1c1"],
+ rowHeaderCells: [],
+ },
+ {
+ cell: "t11r3c1",
+ columnHeaderCells: ["t11r1c1"],
+ rowHeaderCells: [],
+ },
+ {
+ cell: "t11r3c2",
+ columnHeaderCells: ["t11r1c2"],
+ rowHeaderCells: [],
+ },
+ ];
+ testHeaderCells(headerInfoMap);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="implement IAccessibleTable2"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424">
+ Bug 512424
+ </a>
+ <a target="_blank"
+ title="Table headers not associated when header is a td element with no scope"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=704465">
+ Bug 704465
+ </a>
+ <a target="_blank"
+ title="Support rowgroup and colgroup HTML scope"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1141978">
+ Bug 1141978
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table id="table1" border="1">
+ <thead>
+ <tr>
+ <th id="table1_date" colspan="2">Date</th>
+ <th id="table1_qty" rowspan="2">Qty</th>
+ </tr>
+ <tr>
+ <th id="table1_weekday">Weekday</th>
+ <th id="table1_day">Day</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td id="table1_cell_1">Mon</td>
+ <td id="table1_cell_2">1</td>
+ <td id="table1_cell_3">20</td>
+ </tr>
+ <tr>
+ <td id="table1_cell_4">Thu</td>
+ <td id="table1_cell_5">2</td>
+ <td id="table1_cell_6">15</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <th id="table1_total" scope="row" colspan="2">Total</th>
+ <td id="table1_cell_7">35</td>
+ </tr>
+ </tfoot>
+ </table>
+
+ <table id="table2" border="1">
+ <thead>
+ <tr>
+ <th id="table2_ch_1">col1</th>
+ <th id="table2_ch_2">col2</th>
+ <td id="table2_ch_3" scope="col">col3</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th id="table2_rh_1">row1</th>
+ <td id="table2_cell_2">cell1</td>
+ <td id="table2_cell_3">cell2</td>
+ </tr>
+ <tr>
+ <td id="table2_rh_2" scope="row">row2</td>
+ <td id="table2_cell_5">cell3</td>
+ <td id="table2_cell_6">cell4</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table3" border="1">
+ <tr>
+ <td id="table3_cell_1" headers="table3_ch_1">cell1</td>
+ <td id="table3_cell_2" headers="table3_ch_2">cell2</td>
+ </tr>
+ <tr>
+ <td id="table3_ch_1" scope="col">col1</td>
+ <td id="table3_ch_2" scope="col">col2</td>
+ </tr>
+ </table>
+
+ <table id="table4">
+ <thead>
+ <tr>
+ <th id="table4_ch">colheader</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td id="table4_cell">bla</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table5">
+ <tr>
+ <th id="table5_rh">rowheader</th>
+ <td id="table5_cell">cell</td>
+ </tr>
+ </table>
+
+ <table id="table6">
+ <tr>
+ <td>empty cell</th>
+ <td id="table6_ch">colheader</td>
+ </tr>
+ <tr>
+ <td id="table6_rh">rowheader</th>
+ <td id="table6_cell" headers="table6_ch table6_rh">cell</td>
+ </tr>
+ </table>
+
+ <table id="table7" class="data complex" border="1">
+ <caption>Version 1 with rowgroup</caption>
+ <thead>
+ <tr>
+ <td colspan="2">&nbsp;</td>
+ <th id="t7_1km" scope="col">1 km</th>
+ <th id="t7_5km" scope="col">5 km</th>
+ <th id="t7_10km" scope="col">10 km</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th id="t7_Females" rowspan="2" scope="rowgroup">Females</th>
+ <th id="t7_Mary" scope="row">Mary</th>
+ <td id="t7_r1c1">8:32</td>
+ <td id="t7_r1c2">28:04</td>
+ <td id="t7_r1c3">1:01:16</td>
+ </tr>
+ <tr>
+ <th id="t7_Betsy" scope="row">Betsy</th>
+ <td id="t7_r2c1">7:43</td>
+ <td id="t7_r2c2">26:47</td>
+ <td id="t7_r2c3">55:38</td>
+ </tr>
+ <tr>
+ <th id="t7_Males" rowspan="2" scope="rowgroup">Males</th>
+ <th id="t7_Matt" scope="row">Matt</th>
+ <td id="t7_r3c1">7:55</td>
+ <td id="t7_r3c2">27:29</td>
+ <td id="t7_r3c3">57:04</td>
+ </tr>
+ <tr>
+ <th id="t7_Todd" scope="row">Todd</th>
+ <td id="t7_r4c1">7:01</td>
+ <td id="t7_r4c2">24:21</td>
+ <td id="t7_r4c3">50:35</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table8" class="data complex" border="1">
+ <caption>Version 2 with colgroup</caption>
+ <thead>
+ <tr>
+ <td rowspan="2">&nbsp;</td>
+ <th id="t8_Females" colspan="2" scope="colgroup">Females</th>
+ <th id="t8_Males" colspan="2" scope="colgroup">Males</th>
+ </tr>
+ <tr>
+ <th id="t8_Mary" scope="col">Mary</th>
+ <th id="t8_Betsy" scope="col">Betsy</th>
+ <th id="t8_Matt" scope="col">Matt</th>
+ <th id="t8_Todd" scope="col">Todd</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th id="t8_1km" scope="row">1 km</th>
+ <td id="t8_r1c1">8:32</td>
+ <td id="t8_r1c2">7:43</td>
+ <td id="t8_r1c3">7:55</td>
+ <td id="t8_r1c4">7:01</td>
+ </tr>
+ <tr>
+ <th id="t8_5km" scope="row">5 km</th>
+ <td id="t8_r2c1">28:04</td>
+ <td id="t8_r2c2">26:47</td>
+ <td id="t8_r2c3">27:27</td>
+ <td id="t8_r2c4">24:21</td>
+ </tr>
+ <tr>
+ <th id="t8_10km" scope="row">10 km</th>
+ <td id="t8_r3c1">1:01:16</td>
+ <td id="t8_r3c2">55:38</td>
+ <td id="t8_r3c3">57:04</td>
+ <td id="t8_r3c4">50:35</td>
+ </tr>
+
+ </tbody>
+ </table>
+
+ <table id="table9" border="1">
+ <caption>
+ Example 1 (row group headers):
+ </caption>
+ <tr>
+ <td colspan="2"><span class="offscreen">empty</span></td>
+ <th id="t9_1km" width="40">1 km</th>
+ <th id="t9_5km" width="35">5 km</th>
+ <th id="t9_10km" width="42">10 km</th>
+ </tr>
+ <tr>
+ <th id="t9_females" width="56" rowspan="2">Females</th>
+ <th id="t9_mary" width="39">Mary</th>
+ <td id="t9_r1c1" headers="t9_females t9_mary t9_1km">8:32</td>
+ <td id="t9_r1c2" headers="t9_females t9_mary t9_5km">28:04</td>
+ <td id="t9_r1c3" headers="t9_females t9_mary t9_10km">1:01:16</td>
+ </tr>
+ <tr>
+ <th id="t9_betsy">Betsy</th>
+ <td id="t9_r2c1" headers="t9_females t9_betsy t9_1km">7:43</td>
+ <td id="t9_r2c2" headers="t9_females t9_betsy t9_5km">26:47</td>
+ <td id="t9_r2c3" headers="t9_females t9_betsy t9_10km">55:38</td>
+ </tr>
+ <tr>
+ <th id="t9_males" rowspan="2">Males</th>
+ <th id="t9_matt">Matt</th>
+ <td id="t9_r3c1" headers="t9_males t9_matt t9_1km">7:55</td>
+ <td id="t9_r3c2" headers="t9_males t9_matt t9_5km">27:29</td>
+ <td id="t9_r3c3" headers="t9_males t9_matt t9_10km">57:04</td>
+ </tr>
+ <tr>
+ <th id="t9_todd">Todd</th>
+ <td id="t9_r4c1" headers="t9_males t9_todd t9_1km">7:01</td>
+ <td id="t9_r4c2" headers="t9_males t9_todd t9_5km">24:21</td>
+ <td id="t9_r4c3" headers="t9_males t9_todd t9_10km">50:35</td>
+ </tr>
+ </table>
+
+ <table id="table10" border="1">
+ <caption>
+ Example 2 (column group headers):
+ </caption>
+ <tr>
+ <td rowspan="2"><span class="offscreen">empty</span></td>
+ <th colspan="2" id="t10_females">Females</th>
+ <th colspan="2" id="t10_males">Males</th>
+ </tr>
+ <tr>
+ <th width="40" id="t10_mary">Mary</th>
+ <th width="35" id="t10_betsy">Betsy</th>
+ <th width="42" id="t10_matt">Matt</th>
+ <th width="42" id="t10_todd">Todd</th>
+ </tr>
+ <tr>
+ <th width="39" id="t10_1km">1 km</th>
+ <td headers="t10_females t10_mary t10_1km" id="t10_r1c1">8:32</td>
+ <td headers="t10_females t10_betsy t10_1km" id="t10_r1c2">7:43</td>
+ <td headers="t10_males t10_matt t10_1km" id="t10_r1c3">7:55</td>
+ <td headers="t10_males t10_todd t10_1km" id="t10_r1c4">7:01</td>
+ </tr>
+ <tr>
+ <th id="t10_5km">5 km</th>
+ <td headers="t10_females t10_mary t10_5km" id="t10_r2c1">28:04</td>
+ <td headers="t10_females t10_betsy t10_5km" id="t10_r2c2">26:47</td>
+ <td headers="t10_males t10_matt t10_5km" id="t10_r2c3">27:29</td>
+ <td headers="t10_males t10_todd t10_5km" id="t10_r2c4">24:21</td>
+ </tr>
+ <tr>
+ <th id="t10_10km">10 km</th>
+ <td headers="t10_females t10_mary t10_10km" id="t10_r3c1">1:01:16</td>
+ <td headers="t10_females t10_betsy t10_10km" id="t10_r3c2">55:38</td>
+ <td headers="t10_males t10_matt t10_10km" id="t10_r3c3">57:04</td>
+ <td headers="t10_males t10_todd t10_10km" id="t10_r3c4">50:35</td>
+ </tr>
+ </table>
+
+ <table id="table11">
+ <tr>
+ <th id="t11r1c1">a</th>
+ <th id="t11r1c2">b</th>
+ </tr>
+ <tr>
+ <td id="t11r2c1_2" colspan="2"></td>
+ </tr>
+ <tr>
+ <td id="t11r3c1">e</td>
+ <td id="t11r3c2">f</td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_headers_tree.xhtml b/accessible/tests/mochitest/table/test_headers_tree.xhtml
new file mode 100644
index 0000000000..46e8f43126
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_headers_tree.xhtml
@@ -0,0 +1,100 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Table header information cells for XUL tree">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../table.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+
+ function doTest()
+ {
+ var treeAcc = getAccessible("tree", [nsIAccessibleTable]);
+
+ var headerInfoMap = [
+ {
+ cell: treeAcc.getCellAt(0, 0),
+ rowHeaderCells: [],
+ columnHeaderCells: [ "col" ]
+ },
+ {
+ cell: treeAcc.getCellAt(0, 1),
+ rowHeaderCells: [],
+ columnHeaderCells: [ "scol" ]
+ },
+ {
+ cell: treeAcc.getCellAt(1, 0),
+ rowHeaderCells: [],
+ columnHeaderCells: [ "col" ]
+ },
+ {
+ cell: treeAcc.getCellAt(1, 1),
+ rowHeaderCells: [],
+ columnHeaderCells: [ "scol" ]
+ },
+ {
+ cell: treeAcc.getCellAt(2, 0),
+ rowHeaderCells: [],
+ columnHeaderCells: [ "col" ]
+ },
+ {
+ cell: treeAcc.getCellAt(2, 1),
+ rowHeaderCells: [],
+ columnHeaderCells: [ "scol" ]
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(3));
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424"
+ title="implement IAccessibleTable2">
+ Mozilla Bug 512424
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="debug"/>
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ <treecol id="scol" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/table/test_indexes_ariagrid.html b/accessible/tests/mochitest/table/test_indexes_ariagrid.html
new file mode 100644
index 0000000000..564e141a70
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_indexes_ariagrid.html
@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Table indexes for ARIA grid tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA grid
+ var idxes = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 8],
+ [9, 10, 11],
+ ];
+ testTableIndexes("grid", idxes);
+
+ idxes = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 8],
+ [9, 10, 11],
+ ];
+ testTableIndexes("grid-rowgroups", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // a bit strange ARIA grid
+ idxes = [
+ [0, 1],
+ [2, 3],
+ ];
+ testTableIndexes("grid2", idxes);
+
+ // an ARIA grid with div wrapping cell and div wrapping row
+ idxes = [
+ [0, 1],
+ [2, 3],
+ [4, 5],
+ ];
+ testTableIndexes("grid3", idxes);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=386813"
+ title="support nsIAccessibleTable on ARIA grid/treegrid">Mozilla Bug 386813</a>
+ <a target="_blank"
+ title="nsHTMLTableCellAccessible is used in dojo's ARIA grid"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Mozilla Bug 513848</a>
+ <a target="_blank"
+ title="ARIA grid with rowgroup breaks table row/col counting and indices"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=761853">Mozilla Bug 761853</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="grid" id="grid">
+ <div role="row">
+ <span role="columnheader">column1</span>
+ <span role="columnheader">column2</span>
+ <span role="columnheader">column3</span>
+ </div>
+ <div role="row">
+ <span role="rowheader">row1</span>
+ <span role="gridcell">cell1</span>
+ <span role="gridcell">cell2</span>
+ </div>
+ <div role="row">
+ <span role="rowheader">row2</span>
+ <span role="gridcell">cell3</span>
+ <span role="gridcell">cell4</span>
+ </div>
+ <div role="row">
+ <span role="rowheader">row3</span>
+ <span role="gridcell">cell5</span>
+ <span role="gridcell">cell6</span>
+ </div>
+ </div>
+
+ <div role="grid" id="grid-rowgroups">
+ <div role="row">
+ <span role="columnheader">grid-rowgroups-col1</span>
+ <span role="columnheader">grid-rowgroups-col2</span>
+ <span role="columnheader">grid-rowgroups-col3</span>
+ </div>
+ <div role="rowgroup">
+ <div role="row">
+ <span role="rowheader">grid-rowgroups-row1</span>
+ <span role="gridcell">grid-rowgroups-cell1</span>
+ <span role="gridcell">grid-rowgroups-cell2</span>
+ </div>
+ <div role="row">
+ <span role="rowheader">grid-rowgroups-row2</span>
+ <span role="gridcell">grid-rowgroups-cell3</span>
+ <span role="gridcell">grid-rowgroups-cell4</span>
+ </div>
+ </div>
+ <div role="row">
+ <span role="rowheader">grid-rowgroups-row3</span>
+ <span role="gridcell">grid-rowgroups-cell5</span>
+ <span role="gridcell">grid-rowgroups-cell6</span>
+ </div>
+ </div>
+
+ <div role="grid" id="grid2">
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td role="columnheader">header1</td>
+ <td role="columnheader">header2</td>
+ </tr>
+ </table>
+ </div>
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td role="gridcell">cell1</td>
+ <td role="gridcell" tabindex="-1">cell2</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <div role="grid" id="grid3">
+ <div role="row">
+ <div role="gridcell">Normal cell</div>
+ <div role="gridcell">1</div>
+ </div>
+ <div role="row">
+ <div role="gridcell">Div</div>
+ <div><div role="gridcell">2</div></div>
+ </div>
+ <div tabindex="-1"><div role="row">
+ <div role="gridcell">Cell in row in div</div>
+ <div role="gridcell">3</div>
+ </div></div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_indexes_table.html b/accessible/tests/mochitest/table/test_indexes_table.html
new file mode 100644
index 0000000000..c43dbad8f7
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_indexes_table.html
@@ -0,0 +1,481 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=410052
+-->
+<head>
+ <title>Table indexes chrome tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // table
+ var idxes = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 7],
+ [6, 8, 9],
+ ];
+
+ testTableIndexes("table", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // tableborder
+ idxes = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 7],
+ [6, 8, 9],
+ ];
+
+ testTableIndexes("tableborder", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // table
+ idxes = [
+ [ 0, 1, 2, 2, 3, 4, 5, 6],
+ [ 7, 8, 9, 10, 11, 12, 13, 6],
+ [14, 15, 15, 16, 17, 18, 19, 6],
+ [20, 15, 15, 21, 22, 18, 23, 6],
+ ];
+
+ testTableIndexes("table2", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // tableinsane1 (empty row groups)
+ idxes = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 7],
+ [6, 8, 9],
+ ];
+
+ testTableIndexes("tableinsane1", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // tableinsane2 (empry rows)
+ idxes = [
+ [-1, -1, -1],
+ [-1, -1, -1],
+ [ 0, 1, 2],
+ ];
+
+ testTableIndexes("tableinsane2", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // tableinsane3 (cell holes)
+ idxes = [
+ [0, 1, -1],
+ [2, 3, 4],
+ ];
+
+ testTableIndexes("tableinsane3", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // tableinsane3.2 (cell holes, row spans, fixed in bug 417912)
+ idxes = [
+ [0, 1, 2],
+ [3, -1, 2],
+ [4, 5, 2],
+ ];
+
+ testTableIndexes("tableinsane3.2", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // tableinsane4 (empty row groups/rows and cell holes)
+ idxes = [
+ [ 0, 1, 2],
+ [-1, -1, -1],
+ [ 3, 4, 5],
+ [ 6, 6, 7],
+ [ 8, -1, 7],
+ [ 9, 9, 9],
+ ];
+ testTableIndexes("tableinsane4", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // tableinsane5 (just a strange table)
+ idxes = [
+ [ 0, 1, 2, -1, -1],
+ [-1, -1, -1, -1, -1],
+ [ 3, 4, 5, -1, -1],
+ [ 6, 7, -1, -1, -1],
+ [ 6, 8, 9, -1, -1],
+ [ 6, 10, 9, 11, 12],
+ ];
+ testTableIndexes("tableinsane5", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // tableinsane6 (overlapping cells, mad table)
+ idxes = [
+ [ 0, 1, 2, -1, -1],
+ [-1, -1, -1, -1, -1],
+ [ 3, 4, 5, -1, -1],
+ [ 6, 6, 7, -1, -1],
+ [ 8, 9, 7, -1, -1],
+ [ 10, 9, 7, 11, 12],
+ ];
+ testTableIndexes("tableinsane6", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Table with a cell that has display: block; style
+ idxes = [
+ [0, 1],
+ ];
+ testTableIndexes("tablewithcelldisplayblock", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // A table with a cell that has display: block; and a cell with colspan.
+ // This makes us fall back to the ARIAGridCellAccessible implementation.
+ idxes = [
+ [0, 0, 1],
+ ];
+ testTableIndexes("tablewithcolspanandcelldisplayblock", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // A table with all elements being display:block, including a row group.
+ // This makes us fall back to the ARIAGridRowAccessible, and we must
+ // make sure the index is 0. Strange example from Gmail.
+ idxes = [
+ [0],
+ ];
+ testTableIndexes("tablealldisplayblock", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Table that has display: block; style
+ idxes = [
+ [0, 1],
+ ];
+ testTableIndexes("tablewithdisplayblock", idxes);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // tbody that has display: block; style
+ idxes = [
+ [0, 1],
+ ];
+ testTableIndexes("tbodywithdisplayblock", idxes);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="GetIndexAt and GetRowAtIndex and GetColumnAtIndex on HTML tables are inconsistent"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052">
+ Bug 410052
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!--
+ If you change the structure of the table please make sure to change
+ the indexes count in 'for' statement in the script above.
+ -->
+ <table border="1" id="table">
+ <caption><strong><b><font size="29">this is a caption for this table</font></b></strong></caption>
+ <thead>
+ <tr>
+ <th>col1</th>
+ <th>col2</th>
+ <th>col3</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="0">4</td>
+ <td colspan="2">5</td>
+ </tr>
+ <tr>
+ <td>6</td>
+ <td>7</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table border="1" id="tableborder" style="border-collapse:collapse">
+ <caption><strong><b><font size="29">this is a caption for this bc table</font></b></strong></caption>
+ <thead>
+ <tr>
+ <th>col1</th>
+ <th>col2</th>
+ <th>col3</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="2">4</td>
+ <td colspan="2">5</td>
+ </tr>
+ <tr>
+ <td>6</td>
+ <td>7</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table cellpadding="2" cellspacing="2" border="1" width="50%" id="table2">
+ <caption>column and row spans</caption>
+ <tbody>
+ <tr>
+ <td>0</td>
+ <td>1</td>
+ <td rowspan="1" colspan="2">2</td>
+ <td>3</td>
+ <td>4</td>
+ <td>5</td>
+ <td rowspan="4" colspan="1">6</td>
+ </tr>
+ <tr>
+ <td>7</td>
+ <td>8</td>
+ <td>8</td>
+ <td>10</td>
+ <td>11</td>
+ <td>12</td>
+ <td>13</td>
+ </tr>
+ <tr>
+ <td>14</td>
+ <td rowspan="2" colspan="2">15</td>
+ <td>16</td>
+ <td>17</td>
+ <td rowspan="2" colspan="1">18</td>
+ <td>19</td>
+ </tr>
+ <tr>
+ <td>20</td>
+ <td>21</td>
+ <td>22</td>
+ <td>23</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table border="1" id="tableinsane1">
+ <caption>test empty row groups</caption>
+ <thead>
+ <tr>
+ <th>col1</th>
+ <th>col2</th>
+ <th>col3</th>
+ </tr>
+ </thead>
+ <tbody></tbody>
+ <tbody></tbody>
+ <tbody></tbody>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="2">4</td>
+ <td colspan="2">5</td>
+ </tr>
+ <tr>
+ <td>6</td>
+ <td>7</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table border="1" id="tableinsane2">
+ <caption>empty rows</caption>
+ <tbody><tr></tr><tr></tr></tbody>
+ <tbody></tbody>
+ <tbody>
+ <tr>
+ <td>0</td>
+ <td>1</td>
+ <td>2</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table border="1" id="tableinsane3">
+ <caption>missed cell</caption>
+ <tbody>
+ <tr>
+ <td>0</td>
+ <td>1</td>
+ </tr>
+ </tbody>
+ <tbody>
+ <tr>
+ <td>2</td>
+ <td>3</td>
+ <td>4</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table cellpadding="2" cellspacing="2" border="1" id="tableinsane3.2">
+ <tr><td>1</td><td>2</td><td rowspan=3>3</td>
+ <tr><td>4</td>
+ <tr><td>5</td><td>6</td>
+ </table>
+
+ <table border="1" id="tableinsane4">
+ <caption>test empty rows + cellmap holes</caption>
+ <thead>
+ <tr>
+ <th>col1</th>
+ <th>col2</th>
+ <th>col3</th>
+ </tr>
+ </thead>
+ <tbody><tr></tr></tbody>
+ <tbody></tbody>
+ <tbody></tbody>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td colspan="2">4</td>
+ <td rowspan="2">5</td>
+ </tr>
+ <tr>
+ <td>6</td>
+ </tr>
+ <tr>
+ <td colspan="3">7</td>
+ </tr>
+
+ </tbody>
+ </table>
+
+ <table border="1" id="tableinsane5">
+ <caption>just a strange table</caption>
+ <thead>
+ <tr>
+ <th>col1</th>
+ <th>col2</th>
+ <th>col3</th>
+ </tr>
+ </thead>
+ <tbody><tr></tr></tbody>
+ <tbody></tbody>
+ <tbody></tbody>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="0">4</td>
+ <td colspan="0">5</td>
+ </tr>
+ <tr>
+ <td>6</td>
+ <td rowspan="0">7</td>
+ </tr>
+ <tr>
+ <td>8</td>
+ <td>9</td>
+ <td>10</td>
+ </tr>
+
+ </tbody>
+ </table>
+
+ <table border="1" id="tableinsane6" >
+ <caption>overlapping cells</caption>
+ <thead>
+ <tr>
+ <th>header cell 0</th>
+ <th>header cell 1</th>
+ <th>header cell 2</th>
+ </tr>
+ </thead>
+ <tbody><tr></tr></tbody>
+ <tbody></tbody>
+ <tbody></tbody>
+ <tbody>
+ <tr>
+ <td>3</td>
+ <td>4</td>
+ <td>5</td>
+ </tr>
+ <tr>
+ <td colspan="2">6</td>
+ <td rowspan="0">7</td>
+ </tr>
+ <tr>
+ <td>8</td>
+ <td rowspan="0">9</td>
+ </tr>
+ <tr>
+ <td colspan="3">10</td>
+ <td>11</td>
+ <td>12</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="tablewithcelldisplayblock">
+ <tr>
+ <th>a</th>
+ <td style="display: block;">b</td>
+ </tr>
+ </table>
+
+ <table id="tablewithcolspanandcelldisplayblock">
+ <tr>
+ <th colspan="2">a</th>
+ <td style="display: block;" >b</td>
+ </tr>
+ </table>
+
+ <table id="tablealldisplayblock" style="display:block;">
+ <tbody style="display:block;">
+ <tr style="display:block;">
+ <td style="display:block;">text</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="tablewithdisplayblock" style="display: block;">
+ <tr><th>a</th><td>b</td></tr>
+ </table>
+
+ <table id="tbodywithdisplayblock">
+ <tbody style="display: block;">
+ <tr>
+ <th>a</th>
+ <td>b</td>
+ </tr>
+ </tbody>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_indexes_tree.xhtml b/accessible/tests/mochitest/table/test_indexes_tree.xhtml
new file mode 100644
index 0000000000..0b1b6b2625
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_indexes_tree.xhtml
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible Table indexes tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../table.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+
+ function doTest()
+ {
+ var idxes = [
+ [0, 1],
+ [2, 3],
+ [4, 5]
+ ];
+ testTableIndexes("tree", idxes);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(3));
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="debug"/>
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ <treecol id="scol" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/table/test_layoutguess.html b/accessible/tests/mochitest/table/test_layoutguess.html
new file mode 100644
index 0000000000..f3e94c5a78
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_layoutguess.html
@@ -0,0 +1,554 @@
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=495388 -->
+<head>
+ <title>test HTMLTableAccessible::IsProbablyForLayout implementation</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+ function isLayoutTable(id) {
+ // This helps us know if the absence of layout-guess is simply because
+ // it is not a table.
+ ok(isAccessible(id, nsIAccessibleTable), `${id} has table interface`);
+ testAttrs(id, { "layout-guess": "true" }, true);
+ }
+ function isDataTable(id) {
+ testAbsentAttrs(id, { "layout-guess": "true" });
+ }
+
+ function doTest() {
+ // table with role of grid
+ isDataTable("table1");
+ // table with role of grid and datatable="0"
+ isDataTable("table1.1");
+
+ // table with landmark role
+ isDataTable("table2");
+
+ // table with summary
+ isDataTable("table3");
+
+ // table with caption
+ isDataTable("table4");
+
+ // layout table with empty caption
+ isLayoutTable("table4.2");
+
+ // table with thead element
+ isDataTable("table5");
+
+ // table with tfoot element
+ isDataTable("table5.1");
+
+ // table with colgroup or col elements
+ isDataTable("table5.2");
+ isDataTable("table5.3");
+
+ // table with th element
+ isDataTable("table6");
+
+ // table with headers attribute
+ isDataTable("table6.2");
+
+ // table with scope attribute
+ isDataTable("table6.2.2");
+
+ // table with abbr attribute
+ isDataTable("table6.2.3");
+
+ // table with abbr element
+ isDataTable("table6.3");
+
+ // table with abbr element having empty text node
+ isDataTable("table6.4");
+
+ // table with abbr element and non-empty text node
+ isLayoutTable("table6.5");
+
+ // layout table with nested table
+ isLayoutTable("table9");
+
+ // layout table with 1 column
+ isLayoutTable("table10");
+
+ // layout table with 1 row
+ isLayoutTable("table11");
+
+ // table with 5 columns
+ isDataTable("table12");
+
+ // table with a bordered cell
+ isDataTable("table13");
+
+ // table with alternating row background colors
+ isDataTable("table14");
+
+ // table with 3 columns and 21 rows
+ isDataTable("table15");
+
+ // layout table that has a 100% width
+ isLayoutTable("table16");
+
+ // layout table that has a 95% width in pixels
+ isLayoutTable("table17");
+
+ // layout table with less than 10 columns
+ isLayoutTable("table18");
+
+ // layout table with embedded iframe
+ isLayoutTable("table19");
+
+ // tree grid, no layout table
+ isDataTable("table20");
+
+ // layout table containing nested data table (having data structures)
+ isLayoutTable("table21");
+ isLayoutTable("table21.2");
+ isLayoutTable("table21.3");
+ isLayoutTable("table21.4");
+ isLayoutTable("table21.5");
+ isLayoutTable("table21.6");
+
+ // layout table having datatable="0" attribute and containing data table structure (tfoot element)
+ isLayoutTable("table22");
+
+ // repurposed table for tabbed UI
+ isLayoutTable("table23");
+
+ // layout display:block table with 1 column
+ isLayoutTable("displayblock_table1");
+
+ // matrix
+ isDataTable("mtable1");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=495388"
+ title="Don't treat tables that have a landmark role as layout table">
+ Mozilla Bug 495388
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=690222"
+ title="Data table elements used to determine layout-guess attribute shouldn't be picked from nested tables">
+ Mozilla Bug 690222
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=696975"
+ title="Extend the list of legitimate data table structures">
+ Mozilla Bug 696975
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Table with role of grid -->
+ <table id="table1" role="grid">
+ <tr>
+ <th>Sender</th>
+ <th>Subject</th>
+ <th>Date</th>
+ </tr>
+ <tr>
+ <td>Marco</td>
+ <td>Test</td>
+ <td>June 12</td>
+ </tr>
+ <tr>
+ <td>David</td>
+ <td>Another test</td>
+ <td>June 12</td>
+ </tr>
+ <tr>
+ <td>Alex</td>
+ <td>Third test</td>
+ <td>June 12</td>
+ </tr>
+ </table>
+ <!-- table with role of grid and datatable="0"-->
+ <table id="table1.1" role="grid" datatable="0">
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <!-- table with landmark role -->
+ <table id="table2" role="main">
+ <tr>
+ <th>Sender</th>
+ <th>Subject</th>
+ <th>Date</th>
+ </tr>
+ <tr>
+ <td>Marco</td>
+ <td>Test</td>
+ <td>June 12</td>
+ </tr>
+ <tr>
+ <td>David</td>
+ <td>Another test</td>
+ <td>June 12</td>
+ </tr>
+ <tr>
+ <td>Alex</td>
+ <td>Third test</td>
+ <td>June 12</td>
+ </tr>
+ </table>
+
+ <!-- table with summary -->
+ <table id="table3" summary="This is a table">
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <!-- table with caption -->
+ <table id="table4">
+ <caption>This is a table</caption>
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <!-- layout table with empty caption -->
+ <table id="table4.2">
+ <caption> </caption>
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <!-- table with thead element -->
+ <table id="table5">
+ <thead>
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </thead>
+ </table>
+
+ <!-- table with tfoot element -->
+ <table id="table5.1">
+ <tfoot>
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </tfoot>
+ </table>
+
+ <!-- table with colgroup and col elements -->
+ <table id="table5.2">
+ <colgroup width="20"></colgroup>
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </table>
+ <table id="table5.3">
+ <col width="20">
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <!-- table with th element -->
+ <table id="table6">
+ <tr>
+ <th>Cell1</th><th>cell2</th>
+ </tr>
+ </table>
+
+ <!-- table with headers attribute -->
+ <table id="table6.2">
+ <tr>
+ <td headers="a">table6.2 cell</td>
+ </tr>
+ </table>
+
+ <!-- table with scope attribute -->
+ <table id="table6.2.2">
+ <tr>
+ <td scope="a">table6.2.2 cell</td>
+ </tr>
+ </table>
+
+ <!-- table with abbr attribute -->
+ <table id="table6.2.3">
+ <tr>
+ <td abbr="table6.2.3">table6.2.3 cell1</td>
+ </tr>
+ </table>
+
+ <!-- table with abbr element -->
+ <table id="table6.3">
+ <tr>
+ <td>table6.3 cell1</td>
+ <td><abbr>table6.3 cell2</abbr></td>
+ </tr>
+ </table>
+
+ <!-- table with abbr element having empty text node -->
+ <table id="table6.4">
+ <tr>
+ <td>
+ <abbr>abbr</abbr>
+ </td>
+ </tr>
+ </table>
+
+ <!-- table with abbr element and non-empty text node -->
+ <table id="table6.5">
+ <tr>
+ <td>
+ This is a really long text (<abbr>tiarlt</abbr>) inside layout table
+ </td>
+ </tr>
+ </table>
+
+ <!-- layout table with nested table -->
+ <table id="table9">
+ <tr>
+ <td><table><tr><td>Cell</td></tr></table></td>
+ </tr>
+ </table>
+
+ <!-- layout table with 1 column -->
+ <table id="table10">
+ <tr><td>Row1</td></tr>
+ <tr><td>Row2</td></tr>
+ </table>
+
+ <!-- layout table with 1 row and purposely many columns -->
+ <table id="table11">
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td><td>Col4</td><td>Col5</td></tr>
+ </table>
+
+ <!-- table with 5 columns -->
+ <table id="table12">
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td><td>Col4</td><td>Col5</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td><td>Col4</td><td>Col5</td></tr>
+ </table>
+
+ <!-- table with a bordered cell -->
+ <table id="table13" border="1" width="100%" bordercolor="#0000FF">
+ <tr>
+ <td bordercolor="#000000"> </td>
+ <td bordercolor="#000000"> </td>
+ <td bordercolor="#000000"> </td>
+ </tr>
+ <tr>
+ <td bordercolor="#000000"> </td>
+ <td bordercolor="#000000"> </td>
+ <td bordercolor="#000000"> </td>
+ </tr>
+ </table>
+
+ <!-- table with alternating row background colors -->
+ <table id="table14" width="100%">
+ <tr style="background-color: #0000FF;">
+ <td> </td>
+ <td> </td>
+ <td> </td>
+ </tr>
+ <tr style="background-color: #00FF00;">
+ <td> </td>
+ <td> </td>
+ <td> </td>
+ </tr>
+ </table>
+
+ <!-- table with 3 columns and 21 rows -->
+ <table id="table15" border="0">
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ </table>
+
+ <!-- layout table that has a 100% width -->
+ <table id="table16" width="100%">
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ </table>
+
+ <!-- layout table that has a 95% width in pixels -->
+ <table id="table17" width="98%">
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ </table>
+
+ <!-- layout table with less than 10 columns -->
+ <table id="table18">
+ <tr>
+ <td>Marco</td>
+ <td>Test</td>
+ <td>June 12</td>
+ </tr>
+ <tr>
+ <td>David</td>
+ <td>Another test</td>
+ <td>June 12</td>
+ </tr>
+ <tr>
+ <td>Alex</td>
+ <td>Third test</td>
+ <td>June 12</td>
+ </tr>
+ </table>
+
+ <!-- layout table with embedded iframe -->
+ <table id="table19">
+ <tr><td><iframe id="frame"></iframe></td><td> </td><td> </td></tr>
+ <tr><td> </td><td> </td><td> </td></tr>
+ <tr><td> </td><td> </td><td> </td></tr>
+ <tr><td> </td><td> </td><td> </td></tr>
+ </table>
+
+ <!-- tree grid, no layout table -->
+ <table id="table20" role="treegrid">
+ <tr role="treeitem"><td>Cell1</td><td>Cell2</td></tr>
+ </table>
+
+ <!-- layout table with nested data table containing data table elements -->
+ <table id="table21">
+ <tr>
+ <td>
+ <table>
+ <caption>table</caption>
+ <tr><td>Cell</td></tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <table id="table21.2">
+ <tr>
+ <td>
+ <table>
+ <colgroup width="20"></colgroup>
+ <tr><th>Cell</th></tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <table id="table21.3">
+ <tr>
+ <td>
+ <table>
+ <col width="20"></col>
+ <tr><th>Cell</th></tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <table id="table21.4">
+ <tr>
+ <td>
+ <table>
+ <tr><th>Cell</th></tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <table id="table21.5">
+ <tr>
+ <td>
+ <table>
+ <thead>
+ <tr><td>Cell</td></tr>
+ </thead>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <table id="table21.6">
+ <tr>
+ <td>
+ <table>
+ <tfoot>
+ <tr><td>Cell</td></tr>
+ </tfoot>
+ </table>
+ </td>
+ </tr>
+ </table>
+
+ <!-- layout table with datatable="0" and tfoot element-->
+ <table id="table22" datatable="0">
+ <tfoot>
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </tfoot>
+ </table>
+
+ <table id="table23" border="1">
+ <tr role="tablist">
+ <td role="tab">Tab 1</td><td role="tab">Tab 2</td>
+ </tr>
+ <tr>
+ <td role="tabpanel" colspan="2">Hello</td>
+ </tr>
+ </table>
+
+ <!-- display:block table -->
+ <table id="displayblock_table1" style="display:block">
+ <tr><td>Row1</td></tr>
+ <tr><td>Row2</td></tr>
+ </table>
+
+ <!-- MathML matrix -->
+ <math>
+ <mtable id="mtable1">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>0</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>0</mn>
+ </mtd>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_mtable.html b/accessible/tests/mochitest/table/test_mtable.html
new file mode 100644
index 0000000000..aa79b3b98c
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_mtable.html
@@ -0,0 +1,160 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>MathML table tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // 'Simple' table
+ var idxes = [
+ [0, 1],
+ [2, 3],
+ ];
+ testTableIndexes("simple", idxes);
+ var cellsArray = [
+ [kDataCell, kDataCell],
+ [kDataCell, kDataCell],
+ ];
+ var rowsArray = [ROLE_MATHML_TABLE_ROW, ROLE_MATHML_TABLE_ROW];
+ testTableStruct("simple", cellsArray, kNoColumnHeader,
+ "", "", kMathTable, rowsArray);
+
+ // 'Complex' table
+ idxes = [
+ [0, 0, 0],
+ [1, 1, 2],
+ [1, 1, 3],
+ ];
+ testTableIndexes("complex", idxes);
+ cellsArray = [
+ [kDataCell, kColSpanned, kColSpanned],
+ [kDataCell, kColSpanned, kDataCell],
+ [kRowSpanned, kSpanned, kDataCell],
+ ];
+ rowsArray = [
+ ROLE_MATHML_TABLE_ROW,
+ ROLE_MATHML_TABLE_ROW,
+ ROLE_MATHML_TABLE_ROW,
+ ];
+ testTableStruct("complex", cellsArray, kNoColumnHeader,
+ "", "", kMathTable, rowsArray);
+
+ // 'Simple' table with mlabeledtr
+ // At the moment we do not implement mlabeledtr but just hide the label
+ // with display: none. Thus we just test the role for now. See bug 689641.
+ idxes = [[0]];
+ testTableIndexes("simple_label", idxes);
+ cellsArray = [[kDataCell]];
+ rowsArray = [ROLE_MATHML_LABELED_ROW];
+ testTableStruct("simple_label", cellsArray, kNoColumnHeader,
+ "", "", kMathTable, rowsArray);
+
+ // Test that a non-table display style still generates the proper
+ // roles in the accessibility tree.
+ const table_tree = {
+ MATHML_TABLE: [{
+ MATHML_TABLE_ROW: [{ MATHML_CELL: [{ TEXT_LEAF: [] }] }]
+ }],
+ };
+ testAccessibleTree("table_with_display_block_mtd", table_tree);
+
+ // Equivalent to the above test but with display: block mtr.
+ testAccessibleTree("table_with_display_block_mtr", table_tree);
+
+ // Equivalent to the above test but with display: block mtable.
+ testAccessibleTree("table_with_display_block", table_tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <math>
+ <mtable id="simple">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>0</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>0</mn>
+ </mtd>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+
+ <mtable id="complex">
+ <mtr>
+ <mtd columnspan="3">
+ <mtext>1 x 3</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowspan="2" columnspan="2">
+ <mtext>2 x 2</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1 x 1</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1 x 1</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+
+ <mtable id="simple_label">
+ <mlabeledtr>
+ <mtd><mtext>1</mtext></mtd>
+ <mtd><mtext>label</mtext></mtd>
+ </mlabeledtr>
+ </mtable>
+
+ <mtable id="table_with_display_block_mtd">
+ <mtr>
+ <mtd style="display: block">test</mtd>
+ </mtr>
+ </mtable>
+
+ <mtable id="table_with_display_block_mtr">
+ <mtr style="display: block">
+ <mtd>test</mtd>
+ </mtr>
+ </mtable>
+
+ <mtable id="table_with_display_block" style="display: block">
+ <mtr>
+ <mtd>test</mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_sels_ariagrid.html b/accessible/tests/mochitest/table/test_sels_ariagrid.html
new file mode 100644
index 0000000000..6a4f065bd8
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_sels_ariagrid.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=410052
+-->
+<head>
+ <title>nsIAccesible selection methods testing for ARIA grid</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA grid
+ var cellsArray =
+ [
+ [ true, true, false, true],
+ [ true, false, true, true],
+ [ true, false, false, true],
+ [ true, true, true, true],
+ [ true, true, true, true],
+ ];
+
+ testTableSelection("table", cellsArray);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // a bit strange ARIA grid
+ cellsArray =
+ [
+ [ false, false],
+ [ false, false],
+ ];
+
+ testTableSelection("grid2", cellsArray);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA grid (column and row headers)
+
+ cellsArray =
+ [
+ [ undefined, true, false],
+ [ undefined, true, false],
+ ];
+
+ testTableSelection("grid3", cellsArray);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="implement nsIAccessibleTable selection methods for ARIA grids"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052">Bug 410052</a>
+ <a target="_blank"
+ title="nsHTMLTableCellAccessible is used in dojo's ARIA grid"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Bug 513848</a>
+ <a target="_blank"
+ title="ARIA columnheader/rowheader shouldn't be selectable by default"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=888247">Bug 888247</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="grid" id="table">
+ <div role="row">
+ <span role="gridcell" aria-selected="true">cell1</span>
+ <span role="gridcell" aria-selected="true">cell2</span>
+ <span role="gridcell">cell3</span>
+ <span role="gridcell" aria-selected="true">cell4</span>
+ </div>
+ <div role="row">
+ <span role="gridcell" aria-selected="true">cell5</span>
+ <span role="gridcell">cell6</span>
+ <span role="gridcell" aria-selected="true">cell7</span>
+ <span role="gridcell" aria-selected="true">cell8</span>
+ </div>
+ <div role="row">
+ <span role="gridcell" aria-selected="true">cell9</span>
+ <span role="gridcell">cell10</span>
+ <span role="gridcell">cell11</span>
+ <span role="gridcell" aria-selected="true">cell12</span>
+ </div>
+ <div role="row" aria-selected="true">
+ <span role="gridcell">cell13</span>
+ <span role="gridcell">cell14</span>
+ <span role="gridcell">cell15</span>
+ <span role="gridcell">cell16</span>
+ </div>
+ <div role="row">
+ <span role="gridcell" aria-selected="true">cell17</span>
+ <span role="gridcell" aria-selected="true">cell18</span>
+ <span role="gridcell" aria-selected="true">cell19</span>
+ <span role="gridcell" aria-selected="true">cell20</span>
+ </div>
+ </div>
+
+ <div role="grid" id="grid2">
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td role="columnheader" aria-selected="false">header1</td>
+ <td role="columnheader" aria-selected="false">header2</td>
+ </tr>
+ </table>
+ </div>
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td role="gridcell">cell1</td>
+ <td role="gridcell" tabindex="-1">cell2</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <div role="grid" id="grid3">
+ <div role="row">
+ <div role="columnheader" id="colheader_default">col header1</div>
+ <div role="columnheader" id="colheader_selected" aria-selected="true">col header2</div>
+ <div role="columnheader" id="colheader_notselected" aria-selected="false">col header3</div>
+ </div>
+ <div role="row">
+ <div role="rowheader" id="rowheader_default">row header1</div>
+ <div role="rowheader" id="rowheader_selected" aria-selected="true">row header2</div>
+ <div role="rowheader" id="rowheader_notselected" aria-selected="false">row header3</div>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_sels_table.html b/accessible/tests/mochitest/table/test_sels_table.html
new file mode 100644
index 0000000000..b0d53ab42c
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_sels_table.html
@@ -0,0 +1,155 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>nsIAccesible selection methods testing for HTML table</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="text/javascript">
+
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // table
+
+ var cellsArray =
+ [
+ [false, false, false, kColSpanned, false, false, false, false],
+ [false, false, false, false, false, false, false, kRowSpanned],
+ [false, false, kColSpanned, false, false, false, false, kRowSpanned],
+ [false, kRowSpanned, kSpanned, false, false, kRowSpanned, false, kRowSpanned],
+ ];
+
+ testTableSelection("table", cellsArray);
+
+ var accTable = getAccessible("table", [nsIAccessibleTable]);
+ ok(!accTable.isProbablyForLayout(), "table is not for layout");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // table instane
+
+ cellsArray =
+ [
+ [false, false, false, -1, -1],
+ [false, false, false, -1, -1],
+ [false, false, kColSpanned, kColSpanned, -1],
+ [kRowSpanned, false, false, -1, -1],
+ [kRowSpanned, false, kRowSpanned, false, false],
+ ];
+
+ testTableSelection("tableinsane", cellsArray);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+ </head>
+ <body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052"
+ title="Fix our nsHTMLAccessibleTable class so GetIndexAt and GetRowAtIndex and GetColumnAtIndex behave consistently">
+ Mozilla Bug 410052
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=501635"
+ title="nsHTMLTableAccessible::GetSelectedCells contains index duplicates for spanned rows or columns">
+ Mozilla Bug 501635
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=501659"
+ title="HTML table's isRowSelected/isColumnSelected shouldn't fail if row or column has cell holes">
+ Mozilla Bug 501659
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Test Table -->
+ <br><br><b> Testing Table:</b><br><br>
+ <center>
+ <table cellpadding="2" cellspacing="2" border="1" width="50%" id="table">
+ <tbody>
+ <tr>
+ <td><br></td>
+ <td><br></td>
+ <td rowspan="1" colspan="2"><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td rowspan="4" colspan="1"><br></td>
+ </tr>
+ <tr>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ </tr>
+ <tr>
+ <td><br></td>
+ <td rowspan="2" colspan="2">c1</td>
+ <td><br></td>
+ <td><br></td>
+ <td rowspan="2" colspan="1"><br></td>
+ <td><br></td>
+ </tr>
+ <tr>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table border="1" id="tableinsane">
+ <thead>
+ <tr>
+ <th>col1</th>
+ <th>col2</th>
+ <th>col3</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="3">4</td>
+ <td colspan="4">5</td>
+ </tr>
+ <tr>
+ <td>6</td>
+ <td rowspan="2">7</td>
+ </tr>
+ <tr>
+ <td>8</td>
+ <td>9</td>
+ <td>10</td>
+ </tr>
+ </tbody>
+ </table>
+
+ </center>
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_sels_tree.xhtml b/accessible/tests/mochitest/table/test_sels_tree.xhtml
new file mode 100644
index 0000000000..7b93d59d47
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_sels_tree.xhtml
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible Table selection tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../table.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+
+ function doTest()
+ {
+ var cellsArray =
+ [
+ [false, false],
+ [false, false],
+ [false, false]
+ ];
+
+ testTableSelection("tree", cellsArray);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(3));
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="debug"/>
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ <treecol id="scol" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/table/test_struct_ariagrid.html b/accessible/tests/mochitest/table/test_struct_ariagrid.html
new file mode 100644
index 0000000000..92821e19c2
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_struct_ariagrid.html
@@ -0,0 +1,163 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Table accessible tree and table interface tests for ARIA grid</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // Pure ARIA grid
+ var cellsArray = [
+ [kColHeaderCell, kColHeaderCell, kColHeaderCell],
+ [kRowHeaderCell, kDataCell, kDataCell],
+ [kRowHeaderCell, kDataCell, kDataCell],
+ ];
+
+ testTableStruct("table", cellsArray);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML table based ARIA grid
+ cellsArray = [
+ [kColHeaderCell, kColHeaderCell, kColHeaderCell],
+ [kDataCell, kDataCell, kDataCell],
+ [kDataCell, kDataCell, kDataCell],
+ ];
+
+ testTableStruct("grid", cellsArray);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA grid with HTML table elements
+ cellsArray = [
+ [kColHeaderCell, kColHeaderCell],
+ [kDataCell, kDataCell],
+ ];
+
+ testTableStruct("grid2", cellsArray);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA grid of wrong markup
+ cellsArray = [ ];
+ testTableStruct("grid3", cellsArray);
+
+ cellsArray = [ [] ];
+ testTableStruct("grid4", cellsArray);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA table with tr inside a shadow root (bug 1698097).
+ testTableStruct("tableShadow", [ [ kDataCell ] ]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="ARIA grid based on HTML table"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=491683">Mozilla Bug 491683</a>
+ <a target="_blank"
+ title="implement IAccessibleTable2"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424">Mozilla Bug 512424</a>
+ <a target="_blank"
+ title="nsHTMLTableCellAccessible is used in dojo's ARIA grid"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Mozilla Bug 513848</a>
+ <a target="_blank"
+ title="Crash [@ AccIterator::GetNext()]"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=675861">Mozilla Bug 675861</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Not usual markup to avoid text accessible between cell accessibles -->
+ <div id="table" role="grid">
+ <div role="row"><span
+ id="table_ch_1" role="columnheader">col_1</span><span
+ id="table_ch_2" role="columnheader">col_2</span><span
+ id="table_ch_3" role="columnheader">col_3</span></div>
+ <div role="row"><span
+ id="table_rh_1" role="rowheader">row_1</span><span
+ id="table_dc_1" role="gridcell">cell1</span><span
+ id="table_dc_2" role="gridcell">cell2</span></div>
+ <div role="row"><span
+ id="table_rh_2" role="rowheader">row_2</span><span
+ id="table_dc_3" role="gridcell">cell3</span><span
+ id="table_dc_4" role="gridcell">cell4</span></div>
+ </div>
+
+ <table role="grid" id="grid" border="1" cellpadding="10" cellspacing="0">
+ <thead>
+ <tr role="row">
+ <th role="columnheader">subject</td>
+ <th role="columnheader">sender</th>
+ <th role="columnheader">date</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr role="row">
+ <td role="gridcell" tabindex="0">about everything</td>
+ <td role="gridcell">president</td>
+ <td role="gridcell">today</td>
+ </tr>
+ <tr role="row">
+ <td role="gridcell">new bugs</td>
+ <td role="gridcell">mozilla team</td>
+ <td role="gridcell">today</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <!-- ARIA grid containing presentational HTML:table with HTML:td used as ARIA
+ grid cells (focusable and not focusable cells) -->
+ <div role="grid" id="grid2">
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td role="columnheader">header1</td>
+ <td role="columnheader">header2</td>
+ </tr>
+ </table>
+ </div>
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td role="gridcell">cell1</td>
+ <td role="gridcell" tabindex="-1">cell2</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <!-- Wrong markup ARIA grid -->
+ <div role="grid" id="grid3"></div>
+ <div role="grid" id="grid4"><div role="row"></div></div>
+
+ <div id="tableShadow" role="table"></div>
+ <script>
+ let host = document.getElementById("tableShadow");
+ let shadow = host.attachShadow({mode: "open"});
+ let tr = document.createElement("tr");
+ shadow.append(tr);
+ tr.setAttribute("role", "row");
+ let td = document.createElement("td");
+ tr.append(td);
+ td.textContent = "test";
+ </script>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_struct_ariatreegrid.html b/accessible/tests/mochitest/table/test_struct_ariatreegrid.html
new file mode 100644
index 0000000000..8d36a2b350
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_struct_ariatreegrid.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Table accessible tree and table interface tests for ARIA tree grid</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML based ARIA tree grid
+
+ var cellsArray = [
+ [kColHeaderCell, kColHeaderCell, kColHeaderCell],
+ [kDataCell, kDataCell, kDataCell],
+ [kDataCell, kDataCell, kDataCell],
+ ];
+
+ testTableStruct("treegrid", cellsArray, kNoColumnHeader, "", "",
+ kTreeTable);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="ARIA treegrid role on HTML:table makes thead/tbody accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=491683">Mozilla Bug 516133</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table role="treegrid" id="treegrid"
+ border="1" cellpadding="10" cellspacing="0">
+ <thead>
+ <tr role="row">
+ <th role="columnheader">subject</td>
+ <th role="columnheader">sender</th>
+ <th role="columnheader">date</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr role="row">
+ <td role="gridcell">about everything</td>
+ <td role="gridcell">president</td>
+ <td role="gridcell">today</td>
+ </tr>
+ <tr role="row">
+ <td role="gridcell">new bugs</td>
+ <td role="gridcell">mozilla team</td>
+ <td role="gridcell">today</td>
+ </tr>
+ </tbody>
+ </table>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_struct_table.html b/accessible/tests/mochitest/table/test_struct_table.html
new file mode 100644
index 0000000000..46bad05c62
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_struct_table.html
@@ -0,0 +1,217 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Table accessible tree and table interface tests for HTML tables</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // column headers from thead and tfoot
+
+ cellsArray = [
+ [kColHeaderCell, kColHeaderCell, kColSpanned],
+ [kRowSpanned, kColHeaderCell, kColHeaderCell],
+ [kDataCell, kDataCell, kDataCell],
+ [kColHeaderCell, kColHeaderCell, kColHeaderCell],
+ ];
+
+ testTableStruct("table1", cellsArray);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // row and column headers from thead and @scope
+
+ var cellsArray = [
+ [kColHeaderCell, kColHeaderCell, kColHeaderCell],
+ [kRowHeaderCell, kDataCell, kDataCell],
+ [kRowHeaderCell, kDataCell, kDataCell],
+ ];
+
+ testTableStruct("table2", cellsArray);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // caption and @summary
+
+ cellsArray = [
+ [kColHeaderCell, kColHeaderCell, kColHeaderCell, kColHeaderCell],
+ [kRowHeaderCell, kDataCell, kDataCell, kDataCell],
+ [kRowHeaderCell, kDataCell, kDataCell, kDataCell],
+ ];
+
+ testTableStruct("table3", cellsArray, kNoColumnHeader,
+ "Test Table",
+ "this is a test table for nsIAccessibleTable");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // row and column spans
+
+ cellsArray = [
+ [kDataCell, kDataCell, kDataCell, kColSpanned, kDataCell, kDataCell, kDataCell, kDataCell],
+ [kDataCell, kDataCell, kDataCell, kDataCell, kDataCell, kDataCell, kDataCell, kRowSpanned],
+ [kDataCell, kDataCell, kColSpanned, kDataCell, kDataCell, kDataCell, kDataCell, kRowSpanned],
+ [kDataCell, kRowSpanned, kSpanned, kDataCell, kDataCell, kRowSpanned, kDataCell, kRowSpanned],
+ ];
+
+ testTableStruct("table4", cellsArray);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Table with a cell that has display: block; style
+
+ cellsArray = [
+ [kRowHeaderCell, kDataCell],
+ ];
+
+ testTableStruct("table5", cellsArray);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Fix our nsHTMLAccessibleTable class so GetIndexAt and GetRowAtIndex and GetColumnAtIndex behave consistently"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052">Mozilla Bug 410052</a>
+ <a target="_blank"
+ title="GetCellDataAt callers that expect an error if no cell is found are wrong"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=417912">Mozilla Bug 417912</a>
+ <a target="_blank"
+ title="create accessibles for HTML tr"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=493695">Mozilla Bug 493695</a>
+ <a target="_blank"
+ title="implement IAccessibleTable2"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424">Mozilla Bug 512424</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table id="table1">
+ <thead>
+ <tr>
+ <th rowspan="2">col1</th><th colspan="2">col2</th>
+ </tr>
+ <tr>
+ <th>col2sub1</th><th>col2sub2</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>cell1</td><td>cell2</td><td>cell3</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <th>col1</th><th>col2</th><th>col3</th>
+ </tr>
+ </tfoot>
+ </table>
+
+ <table id="table2">
+ <thead>
+ <tr>
+ <th id="table1_0">col1</th>
+ <th id="table1_1">col2</th>
+ <td id="table1_2" scope="col">col3</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th id="table1_3">row1</th>
+ <td id="table1_4">cell1</td>
+ <td id="table1_5">cell2</td>
+ </tr>
+ <tr>
+ <td id="table1_6" scope="row">row2</td>
+ <td id="table1_7">cell3</td>
+ <td id="table1_8">cell4</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table3" border="1"
+ summary="this is a test table for nsIAccessibleTable">
+ <caption>Test Table</caption>
+ <thead>
+ <tr>
+ <th></th>
+ <th>columnHeader_1</th>
+ <th id ="col2a">columnHeader_2</th>
+ <th>columnHeader_3</th>
+ </tr>
+ </thead>
+ <tr>
+ <th id="row2a">rowHeader_1</th>
+ <td id="row2b">row1_column1</td>
+ <td id ="col2b">row1_column2</td>
+ <td id="row2c">row1_column3</td>
+ </tr>
+ <tr>
+ <th>rowHeader_2</th>
+ <td>row2_column1</td>
+ <td id ="col2c">row2_column2</td>
+ <td>row2_column3</td>
+ </tr>
+ </table>
+
+ <table id="table4" cellpadding="2" cellspacing="2" border="1" width="50%">
+ <tbody>
+ <tr>
+ <td><br></td>
+ <td><br></td>
+ <td rowspan="1" colspan="2"><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td rowspan="4" colspan="1"><br></td>
+ </tr>
+ <tr>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ </tr>
+ <tr>
+ <td><br></td>
+ <td rowspan="2" colspan="2">c1</td>
+ <td><br></td>
+ <td><br></td>
+ <td rowspan="2" colspan="1"><br></td>
+ <td><br></td>
+ </tr>
+ <tr>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table5">
+ <tr>
+ <th>a</th>
+ <td style="display: block;">b</td>
+ </tr>
+ </table>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_struct_tree.xhtml b/accessible/tests/mochitest/table/test_struct_tree.xhtml
new file mode 100644
index 0000000000..6710bd2e8b
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_struct_tree.xhtml
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Table accessible tree and table interface tests for XUL trees">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../table.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+
+ function doTest()
+ {
+ var cellsArray = [
+ [kDataCell, kDataCell],
+ [kDataCell, kDataCell],
+ [kDataCell, kDataCell]
+ ];
+
+ testTableStruct("table", cellsArray, kTreeColumnHeader);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "table", new nsTableTreeView(3));
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424"
+ title="implement IAccessibleTable2">
+ Mozilla Bug 512424
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="debug"/>
+ <tree id="table" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" label="column"/>
+ <treecol id="scol" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/table/test_table_1.html b/accessible/tests/mochitest/table/test_table_1.html
new file mode 100644
index 0000000000..b1331a5cc3
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_table_1.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+
+ <script type="application/javascript">
+
+function doTest() {
+ var accTable = getAccessible("table", [nsIAccessibleTable]);
+
+ var s = window.getSelection();
+ if (s.rangeCount > 0)
+ s.removeAllRanges();
+
+ var cell = getNode("col2b");
+ var range = document.createRange();
+ range.selectNode(cell);
+ s.addRange(range);
+
+ is(accTable.selectedCellCount, 1, "only one cell selected");
+ cell = getNode("col2a");
+ range = document.createRange();
+ range.selectNode(cell);
+ s.addRange(range);
+ cell = getNode("col2c");
+ range = document.createRange();
+ range.selectNode(cell);
+ s.addRange(range);
+ is(accTable.selectedColumnCount, 1, "only one column selected");
+
+ cell = getNode("row2a");
+ range = document.createRange();
+ range.selectNode(cell);
+ s.addRange(range);
+ cell = getNode("row2b");
+ range = document.createRange();
+ range.selectNode(cell);
+ s.addRange(range);
+ range = document.createRange();
+ cell = getNode("row2c");
+ range.selectNode(cell);
+ s.addRange(range);
+
+ is(accTable.selectedRowCount, 1, "no cells selected");
+
+ // These shouldn't throw.
+ try {
+ accTable.getColumnDescription(1);
+ accTable.getRowDescription(1);
+ } catch (ex) {
+ ok(false, "getColumnDescription/getRowDescription shouldn't throw.");
+ }
+ SimpleTest.finish();
+}
+SimpleTest.waitForExplicitFinish();
+addA11yLoadEvent(doTest);
+ </script>
+ </head>
+ <body >
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052">Mozilla Bug 410052</a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=760878"
+ title="decomtaminate Get Row / Column Description() on accessible tables">
+ Mozilla Bug 760878
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Test Table -->
+ <br><br><b> Testing Table:</b><br><br>
+ <center>
+ <table id="table" border="1"
+ summary="this is a test table for nsIAccessibleTable" >
+ <caption>Test Table</caption>
+ <thead>
+ <tr>
+ <th></th>
+ <th>columnHeader_1</th>
+ <th id ="col2a">columnHeader_2</th>
+ <th>columnHeader_3</th>
+ </tr>
+ </thead>
+ <tr>
+ <th id="row2a">rowHeader_1</th>
+ <td id="row2b">row1_column1</td>
+ <td id ="col2b">row1_column2</td>
+ <td id="row2c">row1_column3</td>
+ </tr>
+ <tr>
+ <th>rowHeader_2</th>
+ <td>row2_column1</td>
+ <td id ="col2c">row2_column2</td>
+ <td>row2_column3</td>
+ </tr>
+ </table>
+ </center>
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_table_2.html b/accessible/tests/mochitest/table/test_table_2.html
new file mode 100644
index 0000000000..6bd7c56b37
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_table_2.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="text/javascript">
+
+function doTest() {
+ // Test table with role=alert.
+ var tableInterfaceExposed = true;
+ var accTable3 = getAccessible("table3", [nsIAccessibleTable], null, DONOTFAIL_IF_NO_INTERFACE);
+ if (!accTable3)
+ tableInterfaceExposed = false;
+ ok(tableInterfaceExposed, "table interface is not exposed");
+
+ if (tableInterfaceExposed) {
+ testRole(accTable3, ROLE_ALERT);
+
+ is(accTable3.getCellAt(0, 0).firstChild.name, "cell0", "wrong cell");
+ is(accTable3.getCellAt(0, 1).firstChild.name, "cell1", "wrong cell");
+ }
+
+ // Test table with role=log and aria property in tr. We create accessible for
+ // tr in this case.
+ tableInterfaceExposed = true;
+ var accTable4 = getAccessible("table4", [nsIAccessibleTable], null, DONOTFAIL_IF_NO_INTERFACE);
+ if (!accTable4)
+ tableInterfaceExposed = false;
+ ok(tableInterfaceExposed, "table interface is not exposed");
+
+ if (tableInterfaceExposed) {
+ let accNotCreated = (!isAccessible("tr"));
+ ok(!accNotCreated, "missed tr accessible");
+
+ testRole(accTable4, ROLE_TABLE);
+
+ is(accTable4.getCellAt(0, 0).firstChild.name, "cell0", "wrong cell");
+ is(accTable4.getCellAt(0, 1).firstChild.name, "cell1", "wrong cell");
+ is(accTable4.getCellAt(1, 0).firstChild.name, "cell2", "wrong cell");
+ is(accTable4.getCellAt(1, 1).firstChild.name, "cell3", "wrong cell");
+ }
+
+ SimpleTest.finish();
+}
+SimpleTest.waitForExplicitFinish();
+addA11yLoadEvent(doTest);
+ </script>
+ </head>
+
+ <body >
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=419811">Mozilla Bug 419811</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Test Table -->
+ <br><br><b> Testing Table:</b><br><br>
+ <center>
+ <table id="table3" border="1" role="alert">
+ <tr>
+ <td>cell0</td>
+ <td>cell1</td>
+ </tr>
+ </table>
+
+ <table id="table4" border="1" role="log">
+ <tr aria-live="polite" id="tr">
+ <td>cell0</td>
+ <td>cell1</td>
+ </tr>
+ <tr>
+ <td>cell2</td>
+ <td>cell3</td>
+ </tr>
+ </table>
+
+ </center>
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_table_mutation.html b/accessible/tests/mochitest/table/test_table_mutation.html
new file mode 100644
index 0000000000..671e627244
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_table_mutation.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <title>Table mutation</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+
+ async function doTest() {
+ let headers = [
+ {
+ cell: "t1r1c1",
+ columnHeaderCells: [],
+ rowHeaderCells: [],
+ },
+ // t1r2 is hidden
+ {
+ cell: "t1r3c1",
+ columnHeaderCells: ["t1r1c1"],
+ rowHeaderCells: [],
+ },
+ ];
+ testHeaderCells(headers);
+
+ info("Remove row");
+ let reordered = waitForEvent(EVENT_REORDER, "t1");
+ getNode("t1r1").hidden = true;
+ await reordered;
+ headers = [
+ // t1r1 and t1r2 are hidden
+ {
+ cell: "t1r3c1",
+ columnHeaderCells: [],
+ rowHeaderCells: [],
+ },
+ ];
+ testHeaderCells(headers);
+
+ info("Add rows");
+ reordered = waitForEvent(EVENT_REORDER, "t1");
+ getNode("t1r1").hidden = false;
+ getNode("t1r2").hidden = false;
+ await reordered;
+ headers = [
+ {
+ cell: "t1r1c1",
+ columnHeaderCells: [],
+ rowHeaderCells: [],
+ },
+ {
+ cell: "t1r2c1",
+ columnHeaderCells: ["t1r1c1"],
+ rowHeaderCells: [],
+ },
+ {
+ cell: "t1r3c1",
+ columnHeaderCells: ["t1r2c1", "t1r1c1"],
+ rowHeaderCells: [],
+ },
+ ];
+ testHeaderCells(headers);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table id="t1">
+ <tr id="t1r1">
+ <th id="t1r1c1"></th>
+ </tr>
+ <tr id="t1r2" hidden>
+ <th id="t1r2c1"></th>
+ </tr>
+ <tr id="t1r3">
+ <td id="t1r3c1"></td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/test_OuterDocAccessible.html b/accessible/tests/mochitest/test_OuterDocAccessible.html
new file mode 100644
index 0000000000..b7a719aba5
--- /dev/null
+++ b/accessible/tests/mochitest/test_OuterDocAccessible.html
@@ -0,0 +1,87 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=441519
+-->
+<head>
+ <title>nsOuterDocAccessible chrome tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="common.js"></script>
+ <script type="application/javascript"
+ src="states.js"></script>
+ <script type="application/javascript"
+ src="role.js"></script>
+
+ <script type="application/javascript">
+ // needed error return value
+ const ns_error_invalid_arg = Cr.NS_ERROR_INVALID_ARG;
+
+ function doTest() {
+ // Get accessible for body tag.
+ var docAcc = getAccessible(document);
+
+ if (docAcc) {
+ var outerDocAcc = getAccessible(docAcc.parent);
+
+ if (outerDocAcc) {
+ testRole(outerDocAcc, ROLE_INTERNAL_FRAME);
+
+ // check if it is focusable.
+ testStates(outerDocAcc, STATE_FOCUSABLE, 0);
+
+ // see bug 428954: No name wanted for internal frame
+ is(outerDocAcc.name, null, "Wrong name for internal frame!");
+
+ // see bug 440770, no actions wanted on outer doc
+ is(outerDocAcc.actionCount, 0,
+ "Wrong number of actions for internal frame!");
+
+ try {
+ outerDocAcc.getActionName(0);
+ throw new Error("No exception thrown for actionName!");
+ } catch (e) {
+ is(e.result, ns_error_invalid_arg,
+ "Wrong return value for actionName call!");
+ }
+
+ try {
+ outerDocAcc.getActionDescription(0);
+ throw new Error("No exception thrown for actionDescription!");
+ } catch (e) {
+ is(e.result, ns_error_invalid_arg,
+ "Wrong return value for actionDescription call!");
+ }
+
+ try {
+ outerDocAcc.doAction(0);
+ throw new Error("No exception thrown for doAction!");
+ } catch (e) {
+ is(e.result, ns_error_invalid_arg,
+ "Wrong return value for doAction call!");
+ }
+ }
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=441519"
+ title="nsOuterDocAccessible chrome tests">
+ Mozilla Bug 441519
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/test_aria_token_attrs.html b/accessible/tests/mochitest/test_aria_token_attrs.html
new file mode 100644
index 0000000000..ad0bc25970
--- /dev/null
+++ b/accessible/tests/mochitest/test_aria_token_attrs.html
@@ -0,0 +1,417 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=452388
+-->
+<head>
+ <title>An NMTOKEN based ARIA property is undefined if the ARIA attribute is not present, or is set to "" or "undefined"</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="common.js"></script>
+ <script type="application/javascript"
+ src="role.js"></script>
+ <script type="application/javascript"
+ src="states.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // test aria-pressed state mapping to roles PUSHBUTTON vs TOGGLEBUTTON
+ testRole("button_pressed_true", ROLE_TOGGLE_BUTTON);
+ testRole("button_pressed_false", ROLE_TOGGLE_BUTTON);
+ testRole("button_pressed_empty", ROLE_PUSHBUTTON);
+ testRole("button_pressed_undefined", ROLE_PUSHBUTTON);
+ testRole("button_pressed_absent", ROLE_PUSHBUTTON);
+
+ // test button aria-pressed states
+ testStates("button_pressed_true", STATE_PRESSED, 0, STATE_CHECKABLE);
+ testStates("button_pressed_false", 0, 0, STATE_CHECKABLE | STATE_PRESSED);
+ testStates("button_pressed_empty", 0, 0, STATE_PRESSED | STATE_CHECKABLE);
+ testStates("button_pressed_undefined", 0, 0, STATE_PRESSED | STATE_CHECKABLE);
+ testStates("button_pressed_absent", 0, 0, STATE_PRESSED | STATE_CHECKABLE);
+
+ // test (checkbox) checkable and checked states
+ testStates("checkbox_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("checkbox_checked_mixed", (STATE_CHECKABLE | STATE_MIXED), 0, STATE_CHECKED);
+ testStates("checkbox_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("checkbox_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("checkbox_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("checkbox_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
+
+ // test native checkbox checked state and aria-checked state (if conflict, native wins)
+ testStates("native_checkbox_nativechecked_ariatrue", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("native_checkbox_nativechecked_ariafalse", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("native_checkbox_nativechecked_ariaempty", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("native_checkbox_nativechecked_ariaundefined", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("native_checkbox_nativechecked_ariaabsent", (STATE_CHECKABLE | STATE_CHECKED));
+
+ testStates("native_checkbox_nativeunchecked_ariatrue", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("native_checkbox_nativeunchecked_ariafalse", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("native_checkbox_nativeunchecked_ariaempty", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("native_checkbox_nativeunchecked_ariaundefined", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("native_checkbox_nativeunchecked_ariaabsent", STATE_CHECKABLE, 0, STATE_CHECKED);
+
+ // test (checkbox) readonly states
+ testStates("checkbox_readonly_true", STATE_READONLY);
+ testStates("checkbox_readonly_false", 0, 0, STATE_READONLY);
+ testStates("checkbox_readonly_empty", 0, 0, STATE_READONLY);
+ testStates("checkbox_readonly_undefined", 0, 0, STATE_READONLY);
+ testStates("checkbox_readonly_absent", 0, 0, STATE_READONLY);
+
+ // test (checkbox) required states
+ testStates("checkbox_required_true", STATE_REQUIRED);
+ testStates("checkbox_required_false", 0, 0, STATE_REQUIRED);
+ testStates("checkbox_required_empty", 0, 0, STATE_REQUIRED);
+ testStates("checkbox_required_undefined", 0, 0, STATE_REQUIRED);
+ testStates("checkbox_required_absent", 0, 0, STATE_REQUIRED);
+
+ // test (checkbox) invalid states
+ testStates("checkbox_invalid_true", STATE_INVALID);
+ testStates("checkbox_invalid_false", 0, 0, STATE_INVALID);
+ testStates("checkbox_invalid_empty", 0, 0, STATE_INVALID);
+ testStates("checkbox_invalid_undefined", 0, 0, STATE_INVALID);
+ testStates("checkbox_invalid_absent", 0, 0, STATE_INVALID);
+
+ // test (checkbox) disabled states
+ testStates("checkbox_disabled_true", STATE_UNAVAILABLE);
+ testStates("checkbox_disabled_false", 0, 0, STATE_UNAVAILABLE);
+ testStates("checkbox_disabled_empty", 0, 0, STATE_UNAVAILABLE);
+ testStates("checkbox_disabled_undefined", 0, 0, STATE_UNAVAILABLE);
+ testStates("checkbox_disabled_absent", 0, 0, STATE_UNAVAILABLE);
+
+ // test (listbox) multiselectable states
+ testStates("listbox_multiselectable_true", STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+ testStates("listbox_multiselectable_false", 0, 0, STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+ testStates("listbox_multiselectable_empty", 0, 0, STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+ testStates("listbox_multiselectable_undefined", 0, 0, STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+ testStates("listbox_multiselectable_absent", 0, 0, STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+
+ // test (option) checkable and checked states
+ testStates("option_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("option_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("option_checked_empty", 0, 0, STATE_CHECKABLE | STATE_CHECKED);
+ testStates("option_checked_undefined", 0, 0, STATE_CHECKABLE | STATE_CHECKED);
+ testStates("option_checked_absent", 0, 0, STATE_CHECKABLE | STATE_CHECKED);
+
+ // test (menuitem) checkable and checked states, which are unsupported on this role
+ testStates("menuitem_checked_true", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("menuitem_checked_mixed", 0, 0, (STATE_CHECKABLE | STATE_CHECKED | STATE_MIXED));
+ testStates("menuitem_checked_false", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("menuitem_checked_empty", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("menuitem_checked_undefined", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("menuitem_checked_absent", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
+
+ // test (menuitemcheckbox) checkable and checked states
+ testStates("menuitemcheckbox_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("menuitemcheckbox_checked_mixed", (STATE_CHECKABLE | STATE_MIXED), 0, STATE_CHECKED);
+ testStates("menuitemcheckbox_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("menuitemcheckbox_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("menuitemcheckbox_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("menuitemcheckbox_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
+
+ // test (menuitemcheckbox) readonly states
+ testStates("menuitemcheckbox_readonly_true", STATE_READONLY);
+ testStates("menuitemcheckbox_readonly_false", 0, 0, STATE_READONLY);
+ testStates("menuitemcheckbox_readonly_empty", 0, 0, STATE_READONLY);
+ testStates("menuitemcheckbox_readonly_undefined", 0, 0, STATE_READONLY);
+ testStates("menuitemcheckbox_readonly_absent", 0, 0, STATE_READONLY);
+
+ // test (menuitemradio) checkable and checked states
+ testStates("menuitemradio_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("menuitemradio_checked_mixed", STATE_CHECKABLE, 0, (STATE_MIXED | STATE_CHECKED));
+ testStates("menuitemradio_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("menuitemradio_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("menuitemradio_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("menuitemradio_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
+
+ // test (menuitemradio) readonly states
+ testStates("menuitemradio_readonly_true", STATE_READONLY);
+ testStates("menuitemradio_readonly_false", 0, 0, STATE_READONLY);
+ testStates("menuitemradio_readonly_empty", 0, 0, STATE_READONLY);
+ testStates("menuitemradio_readonly_undefined", 0, 0, STATE_READONLY);
+ testStates("menuitemradio_readonly_absent", 0, 0, STATE_READONLY);
+
+ // test (radio) checkable and checked states
+ testStates("radio_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("radio_checked_mixed", STATE_CHECKABLE, 0, (STATE_MIXED | STATE_CHECKED));
+ testStates("radio_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("radio_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("radio_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("radio_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
+
+ // test (radiogroup) readonly states
+ testStates("radiogroup_readonly_true", STATE_READONLY);
+ testStates("radiogroup_readonly_false", 0, 0, STATE_READONLY);
+ testStates("radiogroup_readonly_empty", 0, 0, STATE_READONLY);
+ testStates("radiogroup_readonly_undefined", 0, 0, STATE_READONLY);
+ testStates("radiogroup_readonly_absent", 0, 0, STATE_READONLY);
+
+ // test (switch) readonly states
+ testStates("switch_readonly_true", STATE_READONLY);
+ testStates("switch_readonly_false", 0, 0, STATE_READONLY);
+ testStates("switch_readonly_empty", 0, 0, STATE_READONLY);
+ testStates("switch_readonly_undefined", 0, 0, STATE_READONLY);
+ testStates("switch_readonly_absent", 0, 0, STATE_READONLY);
+
+ // test (textbox) multiline states
+ testStates("textbox_multiline_true", 0, EXT_STATE_MULTI_LINE);
+ testStates("textbox_multiline_false", 0, EXT_STATE_SINGLE_LINE);
+ testStates("textbox_multiline_empty", 0, EXT_STATE_SINGLE_LINE);
+ testStates("textbox_multiline_undefined", 0, EXT_STATE_SINGLE_LINE);
+ testStates("textbox_multiline_absent", 0, EXT_STATE_SINGLE_LINE);
+
+ // test (textbox) readonly states
+ testStates("textbox_readonly_true", STATE_READONLY);
+ testStates("textbox_readonly_false", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("textbox_readonly_empty", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("textbox_readonly_undefined", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("textbox_readonly_absent", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+
+ // test native textbox readonly state and aria-readonly state (if conflict, native wins)
+ testStates("native_textbox_nativereadonly_ariatrue", STATE_READONLY);
+ testStates("native_textbox_nativereadonly_ariafalse", STATE_READONLY);
+ testStates("native_textbox_nativereadonly_ariaempty", STATE_READONLY);
+ testStates("native_textbox_nativereadonly_ariaundefined", STATE_READONLY);
+ testStates("native_textbox_nativereadonly_ariaabsent", STATE_READONLY);
+
+ testStates("native_textbox_nativeeditable_ariatrue", 0, 0, STATE_READONLY);
+ testStates("native_textbox_nativeeditable_ariafalse", 0, 0, STATE_READONLY);
+ testStates("native_textbox_nativeeditable_ariaempty", 0, 0, STATE_READONLY);
+ testStates("native_textbox_nativeeditable_ariaundefined", 0, 0, STATE_READONLY);
+ testStates("native_textbox_nativeeditable_ariaabsent", 0, 0, STATE_READONLY);
+
+ // test (treeitem) selectable and selected states
+ testStates("treeitem_selected_true", (STATE_SELECTABLE | STATE_SELECTED));
+ testStates("treeitem_selected_false", STATE_SELECTABLE, 0, STATE_SELECTED);
+ testStates("treeitem_selected_empty", STATE_SELECTABLE, 0, STATE_SELECTED);
+ testStates("treeitem_selected_undefined", STATE_SELECTABLE, 0, STATE_SELECTED);
+ testStates("treeitem_selected_absent", STATE_SELECTABLE, 0, STATE_SELECTED);
+
+ // test (treeitem) haspopup states
+ testStates("treeitem_haspopup_true", STATE_HASPOPUP);
+ testStates("treeitem_haspopup_false", 0, 0, STATE_HASPOPUP);
+ testStates("treeitem_haspopup_empty", 0, 0, STATE_HASPOPUP);
+ testStates("treeitem_haspopup_undefined", 0, 0, STATE_HASPOPUP);
+ testStates("treeitem_haspopup_absent", 0, 0, STATE_HASPOPUP);
+
+ // test (treeitem) expandable and expanded/collapsed states
+ testStates("treeitem_expanded_true", STATE_EXPANDED, EXT_STATE_EXPANDABLE);
+ testStates("treeitem_expanded_false", STATE_COLLAPSED, EXT_STATE_EXPANDABLE);
+ testStates("treeitem_expanded_empty", 0, 0, STATE_EXPANDED | STATE_COLLAPSED, EXT_STATE_EXPANDABLE);
+ testStates("treeitem_expanded_undefined", 0, 0, STATE_EXPANDED | STATE_COLLAPSED, EXT_STATE_EXPANDABLE);
+ testStates("treeitem_expanded_absent", 0, 0, STATE_EXPANDED | STATE_COLLAPSED, EXT_STATE_EXPANDABLE);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=452388">
+ Mozilla Bug 452388
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=499653"
+ title="Unify ARIA state attributes mapping rules">
+ Mozilla Bug 499653
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=989958"
+ title="Pressed state is not exposed on a button element with aria-pressed attribute"
+ Mozilla Bug 989958
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="button_pressed_true" role="button" aria-pressed="true">This button has aria-pressed="true" and should get ROLE_TOGGLE_BUTTON. It should also get STATE_PRESSED.</div>
+ <div id="button_pressed_false" role="button" aria-pressed="false">This button has aria-pressed="false" and should get ROLE_TOGGLE_BUTTON.</div>
+ <div id="button_pressed_empty" role="button" aria-pressed="">This button has aria-pressed="" and should <emph>not</emph> get ROLE_BUTTON.</div>
+ <div id="button_pressed_undefined" role="button" aria-pressed="undefined">This button has aria-pressed="undefined" and should <emph>not</emph> get ROLE_TOGGLE_BUTTON.</div>
+ <div id="button_pressed_absent" role="button">This button has <emph>no</emph> aria-pressed attribute and should <emph>not</emph> get ROLE_TOGGLE_BUTTON.</div>
+
+ <div id="checkbox_checked_true" role="checkbox" aria-checked="true">This checkbox has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div>
+ <div id="checkbox_checked_mixed" role="checkbox" aria-checked="mixed">This checkbox has aria-checked="mixed" and should get STATE_CHECKABLE. It should also get STATE_MIXED.</div>
+ <div id="checkbox_checked_false" role="checkbox" aria-checked="false">This checkbox has aria-checked="false" and should get STATE_CHECKABLE.</div>
+ <div id="checkbox_checked_empty" role="checkbox" aria-checked="">This checkbox has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="checkbox_checked_undefined" role="checkbox" aria-checked="undefined">This checkbox has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="checkbox_checked_absent" role="checkbox">This checkbox has <emph>no</emph> aria-checked attribute and should get STATE_CHECKABLE.</div>
+
+ <form action="">
+ <input id="native_checkbox_nativechecked_ariatrue" type="checkbox" checked="checked" aria-checked="true"/>
+ <input id="native_checkbox_nativechecked_ariafalse" type="checkbox" checked="checked" aria-checked="false"/>
+ <input id="native_checkbox_nativechecked_ariaempty" type="checkbox" checked="checked" aria-checked=""/>
+ <input id="native_checkbox_nativechecked_ariaundefined" type="checkbox" checked="checked" aria-checked="undefined"/>
+ <input id="native_checkbox_nativechecked_ariaabsent" type="checkbox" checked="checked"/>
+
+ <input id="native_checkbox_nativeunchecked_ariatrue" type="checkbox" aria-checked="true"/>
+ <input id="native_checkbox_nativeunchecked_ariafalse" type="checkbox" aria-checked="false"/>
+ <input id="native_checkbox_nativeunchecked_ariaempty" type="checkbox" aria-checked=""/>
+ <input id="native_checkbox_nativeunchecked_ariaundefined" type="checkbox" aria-checked="undefined"/>
+ <input id="native_checkbox_nativeunchecked_ariaabsent" type="checkbox"/>
+ </form>
+
+ <div id="checkbox_readonly_true" role="checkbox" aria-readonly="true">This checkbox has aria-readonly="true" and should get STATE_READONLY.</div>
+ <div id="checkbox_readonly_false" role="checkbox" aria-readonly="false">This checkbox has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="checkbox_readonly_empty" role="checkbox" aria-readonly="">This checkbox has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="checkbox_readonly_undefined" role="checkbox" aria-readonly="undefined">This checkbox has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="checkbox_readonly_absent" role="checkbox">This checkbox has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div>
+
+ <div id="checkbox_required_true" role="checkbox" aria-required="true">This checkbox has aria-required="true" and should get STATE_REQUIRED.</div>
+ <div id="checkbox_required_false" role="checkbox" aria-required="false">This checkbox has aria-required="false" and should <emph>not</emph> get STATE_REQUIRED.</div>
+ <div id="checkbox_required_empty" role="checkbox" aria-required="">This checkbox has aria-required="" and should <emph>not</emph> get STATE_REQUIRED.</div>
+ <div id="checkbox_required_undefined" role="checkbox" aria-required="undefined">This checkbox has aria-required="undefined" and should <emph>not</emph> get STATE_REQUIRED.</div>
+ <div id="checkbox_required_absent" role="checkbox">This checkbox has <emph>no</emph> aria-required attribute and should <emph>not</emph> get STATE_REQUIRED.</div>
+
+ <div id="checkbox_invalid_true" role="checkbox" aria-invalid="true">This checkbox has aria-invalid="true" and should get STATE_INVALID.</div>
+ <div id="checkbox_invalid_false" role="checkbox" aria-invalid="false">This checkbox has aria-invalid="false" and should <emph>not</emph> get STATE_INVALID.</div>
+ <div id="checkbox_invalid_empty" role="checkbox" aria-invalid="">This checkbox has aria-invalid="" and should <emph>not</emph> get STATE_INVALID.</div>
+ <div id="checkbox_invalid_undefined" role="checkbox" aria-invalid="undefined">This checkbox has aria-invalid="undefined" and should <emph>not</emph> get STATE_INVALID.</div>
+ <div id="checkbox_invalid_absent" role="checkbox">This checkbox has <emph>no</emph> aria-invalid attribute and should <emph>not</emph> get STATE_INVALID.</div>
+
+ <div id="checkbox_disabled_true" role="checkbox" aria-disabled="true" tabindex="0">This checkbox has aria-disabled="true" and should get STATE_DISABLED.</div>
+ <div id="checkbox_disabled_false" role="checkbox" aria-disabled="false">This checkbox has aria-disabled="false" and should <emph>not</emph> get STATE_DISABLED.</div>
+ <div id="checkbox_disabled_empty" role="checkbox" aria-disabled="">This checkbox has aria-disabled="" and should <emph>not</emph> get STATE_DISABLED.</div>
+ <div id="checkbox_disabled_undefined" role="checkbox" aria-disabled="undefined">This checkbox has aria-disabled="undefined" and should <emph>not</emph> get STATE_DISABLED.</div>
+ <div id="checkbox_disabled_absent" role="checkbox">This checkbox has <emph>no</emph> aria-disabled attribute and should <emph>not</emph> get STATE_DISABLED.</div>
+
+ <div id="listbox_multiselectable_true" role="listbox" aria-multiselectable="true">
+ <div id="option_checked_true" role="option" aria-checked="true">item</div>
+ </div>
+ <div id="listbox_multiselectable_false" role="listbox" aria-multiselectable="false">
+ <div id="option_checked_false" role="option" aria-checked="false">item</div>
+ </div>
+ <div id="listbox_multiselectable_empty" role="listbox" aria-multiselectable="">
+ <div id="option_checked_empty" role="option" aria-checked="">item</div>
+ </div>
+ <div id="listbox_multiselectable_undefined" role="listbox" aria-multiselectable="undefined">
+ <div id="option_checked_undefined" role="option" aria-checked="undefined">item</div>
+ </div>
+ <div id="listbox_multiselectable_absent" role="listbox">
+ <div id="option_checked_absent" role="option">item</div>
+ </div>
+
+ <div role="menu">
+ <div id="menuitem_checked_true" role="menuitem" aria-checked="true">Generic menuitems don't support aria-checked.</div>
+ <div id="menuitem_checked_mixed" role="menuitem" aria-checked="mixed">Generic menuitems don't support aria-checked.</div>
+ <div id="menuitem_checked_false" role="menuitem" aria-checked="false">Generic menuitems don't support aria-checked.</div>
+ <div id="menuitem_checked_empty" role="menuitem" aria-checked="">Generic menuitems don't support aria-checked.</div>
+ <div id="menuitem_checked_undefined" role="menuitem" aria-checked="undefined">Generic menuitems don't support aria-checked.</div>
+ <div id="menuitem_checked_absent" role="menuitem">Generic menuitems don't support aria-checked.</div>
+
+ <div id="menuitemcheckbox_checked_true" role="menuitemcheckbox" aria-checked="true">This menuitemcheckbox has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div>
+ <div id="menuitemcheckbox_checked_mixed" role="menuitemcheckbox" aria-checked="mixed">This menuitemcheckbox has aria-checked="mixed" and should get STATE_CHECKABLE. It should also get STATE_MIXED.</div>
+ <div id="menuitemcheckbox_checked_false" role="menuitemcheckbox" aria-checked="false">This menuitemcheckbox has aria-checked="false" and should get STATE_CHECKABLE.</div>
+ <div id="menuitemcheckbox_checked_empty" role="menuitemcheckbox" aria-checked="">This menuitemcheckbox has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="menuitemcheckbox_checked_undefined" role="menuitemcheckbox" aria-checked="undefined">This menuitemcheckbox has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="menuitemcheckbox_checked_absent" role="menuitemcheckbox">This menuitemcheckbox has <emph>no</emph> aria-checked attribute and should <emph>not</emph> get STATE_CHECKABLE.</div>
+
+ <div id="menuitemcheckbox_readonly_true" role="menuitemcheckbox" aria-readonly="true">This menuitemcheckbox has aria-readonly="true" and should get STATE_READONLY.</div>
+ <div id="menuitemcheckbox_readonly_false" role="menuitemcheckbox" aria-readonly="false">This menuitemcheckbox has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="menuitemcheckbox_readonly_empty" role="menuitemcheckbox" aria-readonly="">This menuitemcheckbox has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="menuitemcheckbox_readonly_undefined" role="menuitemcheckbox" aria-readonly="undefined">This menuitemcheckbox has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="menuitemcheckbox_readonly_absent" role="menuitemcheckbox">This menuitemcheckbox has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div>
+
+ <div id="menuitemradio_checked_true" role="menuitemradio" aria-checked="true">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div>
+ <div id="menuitemradio_checked_mixed" role="menuitemradio" aria-checked="mixed">This menuitem has aria-checked="mixed" and should get STATE_CHECKABLE. It should not get STATE_MIXED.</div>
+ <div id="menuitemradio_checked_false" role="menuitemradio" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div>
+ <div id="menuitemradio_checked_empty" role="menuitemradio" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="menuitemradio_checked_undefined" role="menuitemradio" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="menuitemradio_checked_absent" role="menuitemradio">This menuitem has <emph>no</emph> aria-checked attribute but should get STATE_CHECKABLE.</div>
+ </div>
+
+ <div id="menuitemradio_readonly_true" role="menuitemradio" aria-readonly="true">This menuitemradio has aria-readonly="true" and should get STATE_READONLY.</div>
+ <div id="menuitemradio_readonly_false" role="menuitemradio" aria-readonly="false">This menuitemradio has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="menuitemradio_readonly_empty" role="menuitemradio" aria-readonly="">This menuitemradio has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="menuitemradio_readonly_undefined" role="menuitemradio" aria-readonly="undefined">This menuitemradio has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="menuitemradio_readonly_absent" role="menuitemradio">This menuitemradio has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div>
+
+ <div id="radio_checked_true" role="radio" aria-checked="true">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_CHECKED.</div>
+ <div id="radio_checked_mixed" role="radio" aria-checked="mixed">This radio button has aria-checked="mixed" and should get STATE_CHECKABLE. It should not get STATE_MIXED.</div>
+ <div id="radio_checked_false" role="radio" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div>
+ <div id="radio_checked_empty" role="radio" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="radio_checked_undefined" role="radio" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="radio_checked_absent" role="radio">This menuitem has <emph>no</emph> aria-checked attribute but should get STATE_CHECKABLE.</div>
+
+ <div id="radiogroup_readonly_true" role="radiogroup" aria-readonly="true">
+ <div role="radio">yes</div>
+ <div role="radio">no</div>
+ </div>
+ <div id="radiogroup_readonly_false" role="radiogroup" aria-readonly="false">
+ <div role="radio">yes</div>
+ <div role="radio">no</div>
+ </div>
+ <div id="radiogroup_readonly_empty" role="radiogroup" aria-readonly="">
+ <div role="radio">yes</div>
+ <div role="radio">no</div>
+ </div>
+ <div id="radiogroup_readonly_undefined" role="radiogroup" aria-readonly="undefined">
+ <div role="radio">yes</div>
+ <div role="radio">no</div>
+ </div>
+ <div id="radiogroup_readonly_absent" role="radiogroup">
+ <div role="radio">yes</div>
+ <div role="radio">no</div>
+ </div>
+
+ <div id="switch_readonly_true" role="switch" aria-readonly="true">This switch has aria-readonly="true" and should get STATE_READONLY.</div>
+ <div id="switch_readonly_false" role="switch" aria-readonly="false">This switch has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="switch_readonly_empty" role="switch" aria-readonly="">This switch has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="switch_readonly_undefined" role="switch" aria-readonly="undefined">This switch has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="switch_readonly_absent" role="switch">This switch has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div>
+
+ <div id="textbox_readonly_true" role="textbox" aria-readonly="true"></div>
+ <div id="textbox_readonly_false" role="textbox" aria-readonly="false"></div>
+ <div id="textbox_readonly_empty" role="textbox" aria-readonly=""></div>
+ <div id="textbox_readonly_undefined" role="textbox" aria-readonly="undefined"></div>
+ <div id="textbox_readonly_absent" role="textbox"></div>
+
+ <div id="textbox_multiline_true" role="textbox" aria-multiline="true"></div>
+ <div id="textbox_multiline_false" role="textbox" aria-multiline="false"></div>
+ <div id="textbox_multiline_empty" role="textbox" aria-multiline=""></div>
+ <div id="textbox_multiline_undefined" role="textbox" aria-multiline="undefined"></div>
+ <div id="textbox_multiline_absent" role="textbox"></div>
+
+ <form action="">
+ <input id="native_textbox_nativereadonly_ariatrue" readonly="readonly" aria-readonly="true"/>
+ <input id="native_textbox_nativereadonly_ariafalse" readonly="readonly" aria-readonly="false"/>
+ <input id="native_textbox_nativereadonly_ariaempty" readonly="readonly" aria-readonly=""/>
+ <input id="native_textbox_nativereadonly_ariaundefined" readonly="readonly" aria-readonly="undefined"/>
+ <input id="native_textbox_nativereadonly_ariaabsent" readonly="readonly"/>
+
+ <input id="native_textbox_nativeeditable_ariatrue" aria-readonly="true"/>
+ <input id="native_textbox_nativeeditable_ariafalse" aria-readonly="false"/>
+ <input id="native_textbox_nativeeditable_ariaempty" aria-readonly=""/>
+ <input id="native_textbox_nativeeditable_ariaundefined" aria-readonly="undefined"/>
+ <input id="native_textbox_nativeeditable_ariaabsent"/>
+ </form>
+
+ <div role="tree">
+ <div id="treeitem_selected_true" role="treeitem" aria-selected="true">This treeitem has aria-selected="true" and should get STATE_SELECTABLE. It should also get STATE_SELECTED.</div>
+ <div id="treeitem_selected_false" role="treeitem" aria-selected="false">This treeitem has aria-selected="false" and should get STATE_SELECTABLE.</div>
+ <div id="treeitem_selected_empty" role="treeitem" aria-selected="">This treeitem has aria-selected="" and should <emph>not</emph> get STATE_SELECTABLE.</div>
+ <div id="treeitem_selected_undefined" role="treeitem" aria-selected="undefined">This treeitem has aria-selected="undefined" and should <emph>not</emph> get STATE_SELECTABLE.</div>
+ <div id="treeitem_selected_absent" role="treeitem">This treeitem has <emph>no</emph> aria-selected attribute and should <emph>not</emph> get STATE_SELECTABLE.</div>
+
+ <div id="treeitem_haspopup_true" role="treeitem" aria-haspopup="true">This treeitem has aria-haspopup="true" and should get STATE_HASPOPUP.</div>
+ <div id="treeitem_haspopup_false" role="treeitem" aria-haspopup="false">This treeitem has aria-haspopup="false" and should get STATE_HASPOPUP.</div>
+ <div id="treeitem_haspopup_empty" role="treeitem" aria-haspopup="">This treeitem has aria-haspopup="" and should <emph>not</emph> get STATE_HASPOPUP.</div>
+ <div id="treeitem_haspopup_undefined" role="treeitem" aria-haspopup="undefined">This treeitem has aria-haspopup="undefined" and should <emph>not</emph> get STATE_HASPOPUP.</div>
+ <div id="treeitem_haspopup_absent" role="treeitem">This treeitem has <emph>no</emph> aria-haspopup attribute and should <emph>not</emph> get STATE_HASPOPUP.</div>
+
+ <div id="treeitem_expanded_true" role="treeitem" aria-expanded="true">This treeitem has aria-expanded="true" and should get STATE_EXPANDABLE. It should also get STATE_EXPANDED.</div>
+ <div id="treeitem_expanded_false" role="treeitem" aria-expanded="false">This treeitem has aria-expanded="false" and should get STATE_EXPANDABLE. It should also get STATE_COLLAPSED.</div>
+ <div id="treeitem_expanded_empty" role="treeitem" aria-expanded="">This treeitem has aria-expanded="" and should <emph>not</emph> get STATE_EXPANDABLE.</div>
+ <div id="treeitem_expanded_undefined" role="treeitem" aria-expanded="undefined">This treeitem has aria-expanded="undefined" and should <emph>not</emph> get STATE_EXPANDABLE.</div>
+ <div id="treeitem_expanded_absent" role="treeitem">This treeitem has <emph>no</emph> aria-expanded attribute and should <emph>not</emph> get STATE_EXPANDABLE.</div>
+ </div>
+
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/test_bug420863.html b/accessible/tests/mochitest/test_bug420863.html
new file mode 100644
index 0000000000..4f3a608fe1
--- /dev/null
+++ b/accessible/tests/mochitest/test_bug420863.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=420863
+-->
+<head>
+ <title>Table indexes chrome tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="common.js"></script>
+ <script type="application/javascript"
+ src="events.js"></script>
+ <script type="application/javascript"
+ src="actions.js"></script>
+
+ <script type="application/javascript">
+ var gClickHandler = null;
+
+ function doTest() {
+ // Actions should be exposed on any accessible having related DOM node
+ // with registered 'click' event handler.
+
+ // ////////////////////////////////////////////////////////////////////////
+ // generic td
+ var td1Acc = getAccessible("td1");
+ if (!td1Acc) {
+ SimpleTest.finish();
+ return;
+ }
+
+ is(td1Acc.actionCount, 0,
+ "Simple table cell shouldn't have any actions");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // one td with 'onclick' attribute and one with registered click handler
+ var td3Node = getNode("td3");
+
+ // register 'click' event handler
+ gClickHandler = {
+ handleEvent: function handleEvent(aEvent) {
+ },
+ };
+ td3Node.addEventListener("click", gClickHandler);
+
+ // check actions
+ var actionsArray = [
+ {
+ ID: "td2", // "onclick" attribute
+ actionName: "click",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: td3Node,
+ actionName: "click",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ checkOnClickEvent: function check(aEvent) {
+ // unregister click event handler
+ this.ID.removeEventListener("click", gClickHandler);
+
+ // check actions
+ is(getAccessible(this.ID).actionCount, 0,
+ "td3 shouldn't have actions");
+ },
+ },
+ ];
+
+ testActions(actionsArray); // will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=420863"
+ title="If an HTML element has an onClick attribute, expose its click action on the element rather than its child text leaf node."
+ target="_blank">Mozilla Bug 420863</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table>
+ <tr>
+ <td id="td1">Can't click this cell</td>
+ <td onclick="gTdClickAttr = true;"
+ id="td2">Cell with 'onclick' attribute</td>
+ <td id="td3">Cell with registered 'click' event handler</td>
+ </tr>
+ </table>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/test_custom_element_accessibility_defaults.html b/accessible/tests/mochitest/test_custom_element_accessibility_defaults.html
new file mode 100644
index 0000000000..28e337ed8e
--- /dev/null
+++ b/accessible/tests/mochitest/test_custom_element_accessibility_defaults.html
@@ -0,0 +1,383 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1665151
+-->
+<head>
+ <title>Test for default accessibility semantics for custom elements</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="common.js"></script>
+ <script src="attributes.js"></script>
+ <script src="role.js"></script>
+ <script src="states.js"></script>
+ <script src="value.js"></script>
+ <script src="name.js"></script>
+ <script src="promisified-events.js"></script>
+ <script>
+[
+ ["role", "math", () => {}],
+ ["atomic", "toolbar", internals => internals.ariaAtomic = "true"],
+ ["autocomplete", "textbox", internals => internals.ariaAutoComplete = "inline"],
+ ["busy", "feed", internals => internals.ariaBusy = "true"],
+ ["checked", "checkbox", internals => internals.ariaChecked = "true"],
+ ["colcount", "grid", internals => internals.ariaColCount = "1"],
+ ["col", "gridcell", internals => {
+ internals.ariaColIndex = "1";
+ internals.ariaColIndexText = "Default";
+ internals.ariaColSpan = "1";
+ }],
+ ["current", "listitem", internals => internals.ariaCurrent = "page"],
+ ["description", "note", internals => internals.ariaDescription = "Default"],
+ ["disabled", "button", internals => internals.ariaDisabled = "true"],
+ ["expanded", "button", internals => internals.ariaExpanded = "true"],
+ ["haspopup", "button", internals => internals.ariaHasPopup = "true"],
+ ["hidden", "region", internals => internals.ariaHidden = "false"],
+ ["invalid", "textbox", internals => internals.ariaInvalid = "true"],
+ ["keyshortcuts", "button", internals => internals.ariaKeyShortcuts = "Alt+Shift+A"],
+ ["label", "button", internals => internals.ariaLabel = "Default"],
+ ["level", "heading", internals => internals.ariaLevel = "1"],
+ ["live", "region", internals => internals.ariaLive = "polite"],
+ ["modal", "dialog", internals => internals.ariaModal = "true"],
+ ["multiline", "textbox", internals => internals.ariaMultiLine = "true"],
+ ["multiselectable", "listbox", internals => internals.ariaMultiSelectable = "true"],
+ ["orientation", "menu", internals => internals.ariaOrientation = "vertical"],
+ ["placeholder", "textbox", internals => internals.ariaPlaceholder = "Default"],
+ ["posinset", "option", internals => internals.ariaPosInSet = "1"],
+ ["pressed", "button", internals => internals.ariaPressed = "true"],
+ ["readonly", "textbox", internals => internals.ariaReadOnly = "true"],
+ ["relevant", "region", internals => internals.ariaRelevant = "all"],
+ ["required", "textbox", internals => internals.ariaRequired = "true"],
+ ["roledescription", "region", internals => internals.ariaRoleDescription = "Default"],
+ ["rowcount", "grid", internals => internals.ariaRowCount = "1"],
+ ["row", "row", internals => {
+ internals.ariaRowIndex = "1";
+ internals.ariaRowIndexText = "Default";
+ }],
+ ["rowspan", "cell", internals => internals.ariaRowSpan = "1"],
+ ["selected", "option", internals => internals.ariaSelected = "true"],
+ ["setsize", "listitem", internals => internals.ariaSetSize = "1"],
+ ["sort", "columnheader", internals => internals.ariaSort = "ascending"],
+ ["value", "slider", internals => {
+ internals.ariaValueNow = "5";
+ internals.ariaValueMin = "1";
+ internals.ariaValueMax = "10";
+ internals.ariaValueText = "Default";
+ }],
+].forEach(([name, role, apply]) => {
+ customElements.define(`custom-${name}`,
+ class extends HTMLElement {
+ constructor() {
+ super();
+ this._internals = this.attachInternals();
+ this._internals.role = role;
+ apply(this._internals);
+ }
+ get internals() {
+ return this._internals;
+ }
+ }
+ );
+});
+
+async function runTest() {
+ // Test for proper overriding of default attributes.
+ testAttrs("default-role", {"xml-roles": "math"}, true);
+ testAttrs("custom-role", {"xml-roles": "note"}, true);
+
+ testAttrs("default-atomic", {"atomic": "true"}, true);
+ testAbsentAttrs("custom-atomic", {"atomic": "false"});
+
+ testAttrs("default-autocomplete", {"autocomplete": "inline"}, true);
+ testAttrs("custom-autocomplete", {"autocomplete": "list"}, true);
+
+ testStates("default-busy", STATE_BUSY);
+ testStates("custom-busy", 0, 0, STATE_BUSY);
+
+ testStates("default-checked", STATE_CHECKED);
+ testStates("custom-checked", 0, 0, STATE_CHECKED);
+
+ testAttrs("default-colCount", {"colcount": "1"}, true);
+ testAttrs("default-col", {"colindex": "1"}, true);
+ testAttrs("default-col", {"colindextext": "Default"}, true);
+ testAttrs("default-col", {"colspan": "1"}, true);
+ testAttrs("custom-colCount", {"colcount": "3"}, true);
+ testAttrs("custom-col", {"colindex": "2"}, true);
+ testAttrs("custom-col", {"colindextext": "Custom"}, true);
+ testAttrs("custom-col", {"colspan": "2"}, true);
+
+ testAttrs("default-current", {"current": "page"}, true);
+ testAttrs("custom-current", {"current": "step"}, true);
+
+ testDescr("default-description", "Default");
+ testDescr("custom-description", "Custom");
+
+ testStates("default-disabled", STATE_UNAVAILABLE);
+ testStates("custom-disabled", 0, 0, STATE_UNAVAILABLE);
+
+ testStates("default-expanded", STATE_EXPANDED);
+ testStates("custom-expanded", STATE_COLLAPSED);
+
+ testAttrs("default-haspopup", {"haspopup": "true"}, true);
+ testAbsentAttrs("custom-haspopup", {"haspopup": "false"});
+
+ ok(isAccessible("default-hidden"), "Accessible for not aria-hidden");
+ ok(!isAccessible("custom-hidden"), "No accessible for aria-hidden");
+
+ testStates("default-invalid", STATE_INVALID);
+ testStates("custom-invalid", 0, 0, STATE_INVALID);
+
+ testAttrs("default-keyshortcuts", {"keyshortcuts": "Alt+Shift+A"}, true);
+ testAttrs("custom-keyshortcuts", {"keyshortcuts": "A"}, true);
+
+ testName("default-label", "Default");
+ testName("custom-label", "Custom");
+
+ testAttrs("default-level", {"level": "1"}, true);
+ testAttrs("custom-level", {"level": "2"}, true);
+
+ testAttrs("default-live", {"live": "polite"}, true);
+ testAttrs("custom-live", {"live": "assertive"}, true);
+
+ testStates("default-modal", 0, EXT_STATE_MODAL);
+ testStates("custom-modal", 0, 0, 0, EXT_STATE_MODAL);
+
+ testStates("default-multiline", 0, EXT_STATE_MULTI_LINE, 0, EXT_STATE_SINGLE_LINE);
+ testStates("custom-multiline", 0, EXT_STATE_SINGLE_LINE, 0, EXT_STATE_MULTI_LINE);
+
+ testStates("default-multiselectable", STATE_MULTISELECTABLE);
+ testStates("custom-multiselectable", 0, 0, STATE_MULTISELECTABLE);
+
+ testStates("default-orientation", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("custom-orientation", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+
+ testAttrs("default-posinset", {"posinset": "1"}, true);
+ testAttrs("custom-posinset", {"posinset": "2"}, true);
+
+ testStates("default-pressed", STATE_PRESSED);
+ testStates("custom-pressed", 0, 0, STATE_PRESSED);
+
+ testStates("default-readonly", STATE_READONLY);
+ testStates("custom-readonly", 0, 0, STATE_READONLY);
+
+ testAttrs("default-relevant", {"relevant": "all"}, true);
+ testAttrs("custom-relevant", {"relevant": "text"}, true);
+
+ testStates("default-required", STATE_REQUIRED);
+ testStates("custom-required", 0, 0, STATE_REQUIRED);
+
+ testAttrs("default-roledescription", {"roledescription": "Default"}, true);
+ testAttrs("custom-roledescription", {"roledescription": "Custom"}, true);
+
+ testAttrs("default-rowcount", {"rowcount": "1"}, true);
+ testAttrs("default-row", {"rowindex": "1"}, true);
+ testAttrs("default-row", {"rowindextext": "Default"}, true);
+ testAttrs("default-rowspan", {"rowspan": "1"}, true);
+ testAttrs("custom-rowcount", {"rowcount": "3"}, true);
+ testAttrs("custom-row", {"rowindex": "2"}, true);
+ testAttrs("custom-row", {"rowindextext": "Custom"}, true);
+ testAttrs("custom-rowspan", {"rowspan": "2"}, true);
+
+ testStates("default-selected", STATE_SELECTED);
+ testStates("custom-selected", 0, 0, STATE_SELECTED);
+
+ testAttrs("default-setsize", {"setsize": "1"}, true);
+ testAttrs("custom-setsize", {"setsize": "2"}, true);
+
+ testAttrs("default-sort", {"sort": "ascending"}, true);
+ testAttrs("custom-sort", {"sort": "descending"}, true);
+
+ testValue("default-value", "Default", 5, 1, 10, 0);
+ testValue("custom-value", "Custom", 15, 10, 20, 0);
+
+ // Test that changes of defaults fire the proper events.
+ info("Changing ElementInternals ariaLabel");
+ let nameChanged = waitForEvent(EVENT_NAME_CHANGE, "default-label");
+ let customLabelElement = document.getElementById("default-label");
+ customLabelElement.internals.ariaLabel = "Changed Default";
+ await nameChanged;
+ testName("default-label", "Changed Default");
+
+ info("Changing ElementInternals ariaRequired");
+ let stateChanged = waitForEvent(EVENT_STATE_CHANGE, "default-required");
+ let requiredElement = document.getElementById("default-required");
+ requiredElement.internals.ariaRequired = "false";
+ await stateChanged;
+ testStates("default-required", 0, 0, STATE_REQUIRED);
+
+ info("Changing ElementInternals ariaSort");
+ let attributeChanged = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, "default-sort");
+ let sortElement = document.getElementById("default-sort");
+ sortElement.internals.ariaSort = "descending";
+ await attributeChanged;
+ testAttrs("default-sort", {"sort": "descending"}, true);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+Services.prefs.setBoolPref("accessibility.ARIAReflection.enabled", true);
+addA11yLoadEvent(runTest);
+ </script>
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1665151">Bug 1665151</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<custom-role id="default-role"></custom-role>
+<custom-role id="custom-role" role="note"></custom-role>
+
+<custom-autocomplete id="default-autocomplete"></custom-autocomplete>
+<custom-autocomplete id="custom-autocomplete" aria-autocomplete="list"></custom-autocomplete>
+
+<custom-atomic id="default-atomic"></custom-atomic>
+<custom-atomic id="custom-atomic" aria-atomic="false"></custom-atomic>
+
+<custom-busy id="default-busy"></custom-busy>
+<custom-busy id="custom-busy" aria-busy="false"></custom-busy>
+
+<custom-checked id="default-checked"></custom-checked>
+<custom-checked id="custom-checked" aria-checked="false"></custom-checked>
+
+<custom-colcount id="default-colCount">
+ <div role="rowgroup">
+ <div role="row">
+ <custom-col id="default-col"></custom-col>
+ </div>
+ </div>
+</custom-colcount>
+<custom-colcount id="custom-colCount" aria-colcount="3">
+ <div role="rowgroup">
+ <div role="row">
+ <custom-col
+ id="custom-col"
+ aria-colindex="2"
+ aria-colindextext="Custom"
+ aria-colspan="2">
+ </custom-col>
+ </div>
+ </div>
+</custom-colcount>
+
+<custom-current id="default-current"></custom-current>
+<custom-current id="custom-current" aria-current="step"></custom-current>
+
+<custom-description id="default-description"></custom-description>
+<custom-description id="custom-description" aria-description="Custom"></custom-description>
+
+<custom-disabled id="default-disabled"></custom-disabled>
+<custom-disabled id="custom-disabled" aria-disabled="false"></custom-disabled>
+
+<custom-expanded id="default-expanded"></custom-expanded>
+<custom-expanded id="custom-expanded" aria-expanded="false"></custom-expanded>
+
+<custom-haspopup id="default-haspopup"></custom-haspopup>
+<custom-haspopup id="custom-haspopup" aria-haspopup="false"></custom-haspopup>
+
+<custom-hidden id="default-hidden"></custom-hidden>
+<custom-hidden id="custom-hidden" aria-hidden="true"></custom-hidden>
+
+<custom-invalid id="default-invalid"></custom-invalid>
+<custom-invalid id="custom-invalid" aria-invalid="false"></custom-invalid>
+
+<custom-keyshortcuts id="default-keyshortcuts"></custom-keyshortcuts>
+<custom-keyshortcuts id="custom-keyshortcuts" aria-keyshortcuts="A"></custom-keyshortcuts>
+
+<custom-label id="default-label"></custom-label>
+<custom-label id="custom-label" aria-label="Custom"></custom-label>
+
+<custom-level id="default-level"></custom-level>
+<custom-level id="custom-level" aria-level="2"></custom-level>
+
+<custom-live id="default-live"></custom-live>
+<custom-live id="custom-live" aria-live="assertive"></custom-live>
+
+<custom-modal id="default-modal"></custom-modal>
+<custom-modal id="custom-modal" aria-modal="false"></custom-modal>
+
+<custom-multiline id="default-multiline"></custom-multiline>
+<custom-multiline id="custom-multiline" aria-multiline="false"></custom-multiline>
+
+<custom-multiselectable id="default-multiselectable"></custom-multiselectable>
+<custom-multiselectable id="custom-multiselectable" aria-multiselectable="false"></custom-multiselectable>
+
+<custom-orientation id="default-orientation"></custom-orientation>
+<custom-orientation id="custom-orientation" aria-orientation="horizontal"></custom-orientation>
+
+<custom-posinset id="default-posinset"></custom-posinset>
+<custom-posinset id="custom-posinset" aria-posinset="2"></custom-posinset>
+
+<custom-pressed id="default-pressed"></custom-pressed>
+<custom-pressed id="custom-pressed" aria-pressed="false"></custom-pressed>
+
+<custom-readonly id="default-readonly"></custom-readonly>
+<custom-readonly id="custom-readonly" aria-readonly="false"></custom-readonly>
+
+<custom-relevant id="default-relevant"></custom-relevant>
+<custom-relevant id="custom-relevant" aria-relevant="text"></custom-relevant>
+
+<custom-required id="default-required"></custom-required>
+<custom-required id="custom-required" aria-required="false"></custom-required>
+
+<custom-roledescription id="default-roledescription"></custom-roledescription>
+<custom-roledescription id="custom-roledescription" aria-roledescription="Custom"></custom-roledescription>
+
+<custom-rowcount id="default-rowcount">
+ <div role="rowgroup">
+ <custom-row id="default-row">
+ <custom-rowspan id="default-rowspan"></custom-rowspan>
+ </custom-row>
+ </div>
+</custom-rowcount>
+<custom-rowcount id="custom-rowcount" aria-rowcount="3">
+ <div role="rowgroup">
+ <custom-row
+ id="custom-row"
+ aria-rowindex="2"
+ aria-rowindextext="Custom">
+ <custom-rowspan id="custom-rowspan" aria-rowspan="2"></custom-rowspan>
+ </custom-row>
+ </div>
+</custom-rowcount>
+
+<custom-selected id="default-selected"></custom-selected>
+<custom-selected id="custom-selected" aria-selected="false"></custom-selected>
+
+<div role="listbox">
+ <custom-setsize id="default-setsize"></custom-setsize>
+</div>
+<div role="listbox">
+ <custom-setsize id="custom-setsize" aria-setsize="2"></custom-setsize>
+ <div role="listitem" aria-setsize="2"></div>
+</div>
+
+<div role="grid">
+ <div role="rowgroup">
+ <div role="row">
+ <custom-sort id="default-sort"></custom-sort>
+ </div>
+ </div>
+</div>
+<div role="grid">
+ <div role="rowgroup">
+ <div role="row">
+ <custom-sort id="custom-sort" aria-sort="descending"></custom-sort>
+ </div>
+ </div>
+</div>
+
+<custom-value id="default-value"></custom-value>
+<custom-value
+ id="custom-value"
+ aria-valuenow="15"
+ aria-valuemin="10"
+ aria-valuemax="20"
+ aria-valuetext="Custom">
+</custom-value>
+</pre>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/test_descr.html b/accessible/tests/mochitest/test_descr.html
new file mode 100644
index 0000000000..c386ee5dc1
--- /dev/null
+++ b/accessible/tests/mochitest/test_descr.html
@@ -0,0 +1,134 @@
+<html>
+
+<head>
+ <title>nsIAccessible::description tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="common.js"></script>
+ <script type="application/javascript"
+ src="name.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // Description from aria-describedby attribute
+ testDescr("img1", "aria description");
+
+ // No description from @title attribute because it is used to generate
+ // name.
+ testDescr("img2", "");
+
+ // Description from @title attribute, name is generated from @alt
+ // attribute.
+ testDescr("img3", "description");
+
+ // No description from aria-describedby since it is the same as the
+ // @alt attribute which is used as the name
+ testDescr("img4", "");
+
+ // No description from @title attribute since it is the same as the
+ // @alt attribute which is used as the name
+ testDescr("img5", "");
+
+ // Description from content of h2.
+ testDescr("p", "heading");
+
+ // Description from aria-description attribute
+ testDescr("p2", "I describe");
+
+ // Description from contents of h2 when both aria-describedby and
+ // aria-description are present
+ testDescr("p3", "heading");
+
+ // From table summary (caption is used as a name)
+ testDescr("table1", "summary");
+
+ // Empty (summary is used as a name)
+ testDescr("table2", "");
+
+ // From title (summary is used as a name)
+ testDescr("table3", "title");
+
+ // No description from <desc> element since it is the same as the
+ // <title> element.
+ testDescr("svg", "");
+
+ // role="alert" referenced by aria-describedby should include subtree.
+ testDescr("inputDescribedByAlert", "Error");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=489944"
+ title="@title attribute no longer exposed on accDescription">
+ Mozilla Bug 489944
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=666212"
+ title="summary attribute content mapped to accessible name in MSAA">
+ Mozilla Bug 666212
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi/id=1031188"
+ title="Ensure that accDescription never duplicates AccessibleName">
+ Mozilla Bug 1031188
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="description">aria description</p>
+ <img id="img1" aria-describedby="description" />
+ <img id="img2" title="title" />
+ <img id="img3" alt="name" title="description" />
+ <img id="img4" alt="aria description" aria-describedby="description">
+ <img id="img5" alt="image" title="image">
+
+ <h2 id="heading">heading</h2>
+ <p id="p" aria-describedby="heading" role="button">click me</p>
+ <p id="p2" aria-description="I describe" role="button">click me</p>
+ <p id="p3" aria-description="I do not describe" aria-describedby="heading" role="button">click me</p>
+
+ <table id="table1" summary="summary">
+ <caption>caption</caption>
+ <tr><td>cell</td></tr>
+ </table>
+
+ <table id="table2" summary="summary">
+ <tr><td>cell</td></tr>
+ </table>
+
+ <table id="table3" summary="summary" title="title">
+ <tr><td>cell</td></tr>
+ </table>
+
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
+ viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice"
+ id="svg"
+ style="width:100px; height:100px;">
+ <title>SVG Image</title>
+ <desc>SVG Image</desc>
+ <linearGradient id="gradient">
+ <stop class="begin" offset="0%"/>
+ <stop class="end" offset="100%"/>
+ </linearGradient>
+ <rect x="0" y="0" width="100" height="100" style="fill:url(#gradient)" />
+ <circle cx="50" cy="50" r="30" style="fill:url(#gradient)" />
+ </svg>
+
+ <div id="alert" role="alert">Error</div>
+ <input type="text" id="inputDescribedByAlert" aria-describedby="alert">
+</body>
+</html>
diff --git a/accessible/tests/mochitest/test_nsIAccessibleDocument.html b/accessible/tests/mochitest/test_nsIAccessibleDocument.html
new file mode 100644
index 0000000000..7758f63805
--- /dev/null
+++ b/accessible/tests/mochitest/test_nsIAccessibleDocument.html
@@ -0,0 +1,94 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=441737
+-->
+<head>
+ <title>nsIAccessibleDocument chrome tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="common.js"></script>
+ <script type="application/javascript"
+ src="role.js"></script>
+ <script type="application/javascript"
+ src="states.js"></script>
+
+ <script src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var docAcc = getAccessible(document, [nsIAccessibleDocument]);
+ if (docAcc) {
+ // nsIAccessible
+ is(docAcc.name, "nsIAccessibleDocument chrome tests",
+ "Name for document accessible not correct!");
+
+ testRole(docAcc, ROLE_DOCUMENT);
+
+ // check if it is focusable, read-only.
+ testStates(docAcc, (STATE_READONLY | STATE_FOCUSABLE));
+
+ // No actions wanted on doc
+ is(docAcc.actionCount, 0, "Wrong number of actions for document!");
+
+ // attributes should contain tag:body
+ const attributes = docAcc.attributes;
+ is(attributes.getStringProperty("tag"), "body",
+ "Wrong attribute on document!");
+
+ // Document URL.
+ var rootDir = getRootDirectory(window.location.href);
+ is(docAcc.URL, rootDir + "test_nsIAccessibleDocument.html",
+ "Wrong URL for document!");
+
+ // Document title and mime type.
+ is(docAcc.title, "nsIAccessibleDocument chrome tests",
+ "Wrong title for document!");
+ is(docAcc.mimeType, "text/html",
+ "Wrong mime type for document!");
+
+ // DocAccessible::getDocType currently returns NS_ERROR_FAILURE.
+ // See bug 442005. After fixing, please remove this comment and
+ // uncomment the below two lines to enable the test.
+// is(docAcc.docType, "HTML",
+// "Wrong type of document!");
+
+ // Test for correct Document retrieval.
+ var domDoc = null;
+ try {
+ domDoc = docAcc.DOMDocument;
+ } catch (e) {}
+ ok(domDoc, "no Document for this doc accessible!");
+ is(domDoc, document, "Document nodes do not match!");
+
+ // Test for correct nsIDOMWindow retrieval.
+ var domWindow = null;
+ try {
+ domWindow = docAcc.window;
+ } catch (e) {}
+ ok(domWindow, "no nsIDOMWindow for this doc accessible!");
+ is(domWindow, window, "Window nodes do not match!");
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=441737"
+ title="nsAccessibleDocument chrome tests">
+ Mozilla Bug 441737
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/test_nsIAccessibleImage.html b/accessible/tests/mochitest/test_nsIAccessibleImage.html
new file mode 100644
index 0000000000..1d8ee2022d
--- /dev/null
+++ b/accessible/tests/mochitest/test_nsIAccessibleImage.html
@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=429659
+-->
+<head>
+ <title>nsIAccessibleImage chrome tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="common.js"></script>
+ <script type="application/javascript"
+ src="role.js"></script>
+ <script type="application/javascript"
+ src="attributes.js"></script>
+ <script type="application/javascript"
+ src="layout.js"></script>
+
+ <script type="application/javascript">
+ function testCoordinates(aID, aAcc, aWidth, aHeight) {
+ var screenX = {}, screenY = {}, windowX = {}, windowY = {}, parentX = {},
+ parentY = {};
+
+ // get screen coordinates.
+ aAcc.getImagePosition(
+ nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE,
+ screenX, screenY);
+ // get window coordinates.
+ aAcc.getImagePosition(
+ nsIAccessibleCoordinateType.COORDTYPE_WINDOW_RELATIVE,
+ windowX, windowY);
+ // get parent related coordinates.
+ aAcc.getImagePosition(
+ nsIAccessibleCoordinateType.COORDTYPE_PARENT_RELATIVE,
+ parentX, parentY);
+ // XXX For linked images, a negative parentY value is returned, and the
+ // screenY coordinate is the link's screenY coordinate minus 1.
+ // Until this is fixed, set parentY to -1 if it's negative.
+ if (parentY.value < 0)
+ parentY.value = -1;
+
+ // See if asking image for child at image's screen coordinates gives
+ // correct accessible. getChildAtPoint operates on screen coordinates.
+ var tempAcc = null;
+ try {
+ tempAcc = aAcc.getChildAtPoint(screenX.value, screenY.value);
+ } catch (e) {}
+ is(tempAcc, aAcc,
+ "Wrong accessible returned for position of " + aID + "!");
+
+ // get image's parent.
+ var imageParentAcc = null;
+ try {
+ imageParentAcc = aAcc.parent;
+ } catch (e) {}
+ ok(imageParentAcc, "no parent accessible for " + aID + "!");
+
+ if (imageParentAcc) {
+ // See if parent's screen coordinates plus image's parent relative
+ // coordinates equal to image's screen coordinates.
+ var parentAccX = {}, parentAccY = {}, parentAccWidth = {},
+ parentAccHeight = {};
+ imageParentAcc.getBounds(parentAccX, parentAccY, parentAccWidth,
+ parentAccHeight);
+ is(parentAccX.value + parentX.value, screenX.value,
+ "Wrong screen x coordinate for " + aID + "!");
+// XXX see bug 456344 is(parentAccY.value + parentY.value, screenY.value,
+// "Wrong screen y coordinate for " + aID + "!");
+ }
+
+ var [expected_w, expected_h] = CSSToDevicePixels(window, aWidth, aHeight);
+ var width = {}, height = {};
+ aAcc.getImageSize(width, height);
+ is(width.value, expected_w, "Wrong width for " + aID + "!");
+ is(height.value, expected_h, "wrong height for " + aID + "!");
+ }
+
+ function testThis(aID, aSRC, aWidth, aHeight,
+ aActionCount, aActionNames) {
+ var acc = getAccessible(aID, [nsIAccessibleImage]);
+ if (!acc)
+ return;
+
+ // Test role
+ testRole(aID, ROLE_GRAPHIC);
+
+ // test coordinates and size
+ testCoordinates(aID, acc, aWidth, aHeight);
+
+ // bug 429659: Make sure the SRC attribute is set for any image
+ var attributes = {"src": aSRC};
+ testAttrs(acc, attributes, true);
+
+ var actionCount = aActionCount || 0;
+ is(acc.actionCount, actionCount,
+ "Wrong number of actions for " + aID + "!");
+ if (actionCount) {
+ for (let index = 0; index < aActionNames.length; index++) {
+ is(acc.getActionName(index), aActionNames[index],
+ "Wrong action name for " + aID + ", index " + index + "!");
+ }
+ }
+ }
+
+ function doTest() {
+ // Test non-linked image
+ testThis("nonLinkedImage", "moz.png", 89, 38);
+
+ // Test linked image
+ var actionNamesArray = ["click ancestor"];
+ testThis("linkedImage", "moz.png", 89, 38, 1,
+ actionNamesArray);
+
+ // Image with long desc
+ actionNamesArray = ["showlongdesc"];
+ testThis("longdesc", "moz.png", 89, 38, 1,
+ actionNamesArray);
+
+ // Image with invalid url in long desc
+ testThis("invalidLongdesc", "moz.png", 89, 38, 0);
+
+ // Image with click and long desc
+ actionNamesArray = null;
+ actionNamesArray = ["click", "showlongdesc"];
+ testThis("clickAndLongdesc", "moz.png",
+ 89, 38, 2, actionNamesArray);
+
+ // Image with click
+ actionNamesArray = null;
+ actionNamesArray = ["click"];
+ testThis("click", "moz.png",
+ 89, 38, 1, actionNamesArray);
+
+ // Image with long desc
+ actionNamesArray = null;
+ actionNamesArray = ["showlongdesc"];
+ testThis("longdesc2", "moz.png",
+ 89, 38, 1, actionNamesArray);
+
+ // Image described by HTML:a@href with whitespaces
+ actionNamesArray = null;
+ actionNamesArray = ["showlongdesc"];
+ testThis("longdesc3", "moz.png",
+ 89, 38, 1, actionNamesArray);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=429659">Mozilla Bug 429659</a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=652635"
+ title="fall back missing @longdesc to aria-describedby pointing to a href">
+ Mozilla Bug 652635
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <br>Simple image:<br>
+ <img id="nonLinkedImage" src="moz.png"/>
+ <br>Linked image:<br>
+ <a href="http://www.mozilla.org"><img id="linkedImage" src="moz.png"></a>
+ <br>Image with longdesc:<br>
+ <img id="longdesc" src="moz.png" longdesc="longdesc_src.html"
+ alt="Image of Mozilla logo"/>
+ <br>Image with invalid url in longdesc:<br>
+ <img id="invalidLongdesc" src="moz.png" longdesc="longdesc src.html"
+ alt="Image of Mozilla logo"/>
+ <br>Image with click and longdesc:<br>
+ <img id="clickAndLongdesc" src="moz.png" longdesc="longdesc_src.html"
+ alt="Another image of Mozilla logo" onclick="alert('Clicked!');"/>
+
+ <br>image described by a link to be treated as longdesc<br>
+ <img id="longdesc2" src="moz.png" aria-describedby="describing_link"
+ alt="Second Image of Mozilla logo"/>
+ <a id="describing_link" href="longdesc_src.html">link to description of image</a>
+
+ <br>Image described by a link to be treated as longdesc with whitespaces<br>
+ <img id="longdesc3" src="moz.png" aria-describedby="describing_link2"
+ alt="Second Image of Mozilla logo"/>
+ <a id="describing_link2" href="longdesc src.html">link to description of image</a>
+
+ <br>Image with click:<br>
+ <img id="click" src="moz.png"
+ alt="A third image of Mozilla logo" onclick="alert('Clicked, too!');"/>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text.js b/accessible/tests/mochitest/text.js
new file mode 100644
index 0000000000..21392336a1
--- /dev/null
+++ b/accessible/tests/mochitest/text.js
@@ -0,0 +1,814 @@
+/* import-globals-from common.js */
+
+// //////////////////////////////////////////////////////////////////////////////
+// Public
+
+const BOUNDARY_CHAR = nsIAccessibleText.BOUNDARY_CHAR;
+const BOUNDARY_WORD_START = nsIAccessibleText.BOUNDARY_WORD_START;
+const BOUNDARY_WORD_END = nsIAccessibleText.BOUNDARY_WORD_END;
+const BOUNDARY_LINE_START = nsIAccessibleText.BOUNDARY_LINE_START;
+const BOUNDARY_LINE_END = nsIAccessibleText.BOUNDARY_LINE_END;
+const BOUNDARY_PARAGRAPH = nsIAccessibleText.BOUNDARY_PARAGRAPH;
+
+const kTextEndOffset = nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT;
+const kCaretOffset = nsIAccessibleText.TEXT_OFFSET_CARET;
+
+const EndPoint_Start = nsIAccessibleTextRange.EndPoint_Start;
+const EndPoint_End = nsIAccessibleTextRange.EndPoint_End;
+
+const kTodo = 1; // a test is expected to fail
+const kOk = 2; // a test doesn't fail
+
+/**
+ * Test characterCount for the given array of accessibles.
+ *
+ * @param aCount [in] the expected character count
+ * @param aIDs [in] array of accessible identifiers to test
+ * @param aTodoFlag [in, optional] either kOk or kTodo
+ */
+function testCharacterCount(aIDs, aCount, aTodoFlag) {
+ var ids = aIDs instanceof Array ? aIDs : [aIDs];
+ var isFunc = aTodoFlag == kTodo ? todo_is : is;
+ for (var i = 0; i < ids.length; i++) {
+ var textacc = getAccessible(ids[i], [nsIAccessibleText]);
+ isFunc(
+ textacc.characterCount,
+ aCount,
+ "Wrong character count for " + prettyName(ids[i])
+ );
+ }
+}
+
+/**
+ * Test text between two given offsets.
+ *
+ * @param aIDs [in] an array of accessible IDs to test
+ * @param aStartOffset [in] the start offset within the text to test
+ * @param aEndOffset [in] the end offset up to which the text is tested
+ * @param aText [in] the expected result from the test
+ * @param aTodoFlag [in, optional] either kOk or kTodo
+ */
+function testText(aIDs, aStartOffset, aEndOffset, aText, aTodoFlag) {
+ var ids = aIDs instanceof Array ? aIDs : [aIDs];
+ var isFunc = aTodoFlag == kTodo ? todo_is : is;
+ for (var i = 0; i < ids.length; i++) {
+ var acc = getAccessible(ids[i], nsIAccessibleText);
+ try {
+ isFunc(
+ acc.getText(aStartOffset, aEndOffset),
+ aText,
+ "getText: wrong text between start and end offsets '" +
+ aStartOffset +
+ "', '" +
+ aEndOffset +
+ " for '" +
+ prettyName(ids[i]) +
+ "'"
+ );
+ } catch (e) {
+ ok(
+ false,
+ "getText fails between start and end offsets '" +
+ aStartOffset +
+ "', '" +
+ aEndOffset +
+ " for '" +
+ prettyName(ids[i]) +
+ "'"
+ );
+ }
+ }
+}
+
+/**
+ * Test getTextAtOffset for BOUNDARY_CHAR over different elements.
+ *
+ * @param aIDs [in] the accessible identifier or array of accessible
+ * identifiers
+ * @param aOffset [in] the offset to get a character at it
+ * @param aChar [in] the expected character
+ * @param aStartOffset [in] expected start offset of the character
+ * @param aEndOffset [in] expected end offset of the character
+ */
+function testCharAtOffset(aIDs, aOffset, aChar, aStartOffset, aEndOffset) {
+ var IDs = aIDs instanceof Array ? aIDs : [aIDs];
+ for (var i = 0; i < IDs.length; i++) {
+ var acc = getAccessible(IDs[i], nsIAccessibleText);
+ testTextHelper(
+ IDs[i],
+ aOffset,
+ BOUNDARY_CHAR,
+ aChar,
+ aStartOffset,
+ aEndOffset,
+ kOk,
+ kOk,
+ kOk,
+ acc.getTextAtOffset,
+ "getTextAtOffset "
+ );
+ }
+}
+
+/**
+ * Test getTextAtOffset function over different elements.
+ *
+ * @param aIDs [in] ID or array of IDs
+ * @param aBoundaryType [in] boundary type for text to be retrieved
+ * @param aTestList [in] array of sets:
+ * offset1 and offset2 defining the offset range
+ * the text in the range
+ * start offset of the text in the range
+ * end offset of the text in the range
+ *
+ * or
+ *
+ * @param aOffset [in] the offset to get the text at
+ * @param aBoundaryType [in] Boundary type for text to be retrieved
+ * @param aText [in] expected return text for getTextAtOffset
+ * @param aStartOffset [in] expected return start offset for getTextAtOffset
+ * @param aEndOffset [in] expected return end offset for getTextAtOffset
+ * @param ... [in] list of ids or list of tuples made of:
+ * element identifier
+ * kTodo or kOk for returned text
+ * kTodo or kOk for returned start offset
+ * kTodo or kOk for returned offset result
+ */
+function testTextAtOffset() {
+ testTextSuperHelper("getTextAtOffset", arguments);
+}
+
+/**
+ * Test getTextAfterOffset for BOUNDARY_CHAR over different elements.
+ *
+ * @param aIDs [in] the accessible identifier or array of accessible
+ * identifiers
+ * @param aOffset [in] the offset to get a character after it
+ * @param aChar [in] the expected character
+ * @param aStartOffset [in] expected start offset of the character
+ * @param aEndOffset [in] expected end offset of the character
+ */
+function testCharAfterOffset(aIDs, aOffset, aChar, aStartOffset, aEndOffset) {
+ var IDs = aIDs instanceof Array ? aIDs : [aIDs];
+ for (var i = 0; i < IDs.length; i++) {
+ var acc = getAccessible(IDs[i], nsIAccessibleText);
+ testTextHelper(
+ IDs[i],
+ aOffset,
+ BOUNDARY_CHAR,
+ aChar,
+ aStartOffset,
+ aEndOffset,
+ kOk,
+ kOk,
+ kOk,
+ acc.getTextAfterOffset,
+ "getTextAfterOffset "
+ );
+ }
+}
+
+/**
+ * Test getTextAfterOffset function over different elements
+ *
+ * @param aIDs [in] ID or array of IDs
+ * @param aBoundaryType [in] boundary type for text to be retrieved
+ * @param aTestList [in] array of sets:
+ * offset1 and offset2 defining the offset range
+ * the text in the range
+ * start offset of the text in the range
+ * end offset of the text in the range
+ *
+ * or
+ *
+ * @param aOffset [in] the offset to get the text after
+ * @param aBoundaryType [in] Boundary type for text to be retrieved
+ * @param aText [in] expected return text for getTextAfterOffset
+ * @param aStartOffset [in] expected return start offset for getTextAfterOffset
+ * @param aEndOffset [in] expected return end offset for getTextAfterOffset
+ * @param ... [in] list of ids or list of tuples made of:
+ * element identifier
+ * kTodo or kOk for returned text
+ * kTodo or kOk for returned start offset
+ * kTodo or kOk for returned offset result
+ */
+function testTextAfterOffset(
+ aOffset,
+ aBoundaryType,
+ aText,
+ aStartOffset,
+ aEndOffset
+) {
+ testTextSuperHelper("getTextAfterOffset", arguments);
+}
+
+/**
+ * Test getTextBeforeOffset for BOUNDARY_CHAR over different elements.
+ *
+ * @param aIDs [in] the accessible identifier or array of accessible
+ * identifiers
+ * @param aOffset [in] the offset to get a character before it
+ * @param aChar [in] the expected character
+ * @param aStartOffset [in] expected start offset of the character
+ * @param aEndOffset [in] expected end offset of the character
+ */
+function testCharBeforeOffset(aIDs, aOffset, aChar, aStartOffset, aEndOffset) {
+ var IDs = aIDs instanceof Array ? aIDs : [aIDs];
+ for (var i = 0; i < IDs.length; i++) {
+ var acc = getAccessible(IDs[i], nsIAccessibleText);
+ testTextHelper(
+ IDs[i],
+ aOffset,
+ BOUNDARY_CHAR,
+ aChar,
+ aStartOffset,
+ aEndOffset,
+ kOk,
+ kOk,
+ kOk,
+ acc.getTextBeforeOffset,
+ "getTextBeforeOffset "
+ );
+ }
+}
+
+/**
+ * Test getTextBeforeOffset function over different elements
+ *
+ * @param aIDs [in] ID or array of IDs
+ * @param aBoundaryType [in] boundary type for text to be retrieved
+ * @param aTestList [in] array of sets:
+ * offset1 and offset2 defining the offset range
+ * the text in the range
+ * start offset of the text in the range
+ * end offset of the text in the range
+ *
+ * or
+ *
+ * @param aOffset [in] the offset to get the text before
+ * @param aBoundaryType [in] Boundary type for text to be retrieved
+ * @param aText [in] expected return text for getTextBeforeOffset
+ * @param aStartOffset [in] expected return start offset for getTextBeforeOffset
+ * @param aEndOffset [in] expected return end offset for getTextBeforeOffset
+ * @param ... [in] list of ids or list of tuples made of:
+ * element identifier
+ * kTodo or kOk for returned text
+ * kTodo or kOk for returned start offset
+ * kTodo or kOk for returned offset result
+ */
+function testTextBeforeOffset(
+ aOffset,
+ aBoundaryType,
+ aText,
+ aStartOffset,
+ aEndOffset
+) {
+ testTextSuperHelper("getTextBeforeOffset", arguments);
+}
+
+/**
+ * Test word count for an element.
+ *
+ * @param aElement [in] element identifier
+ * @param aCount [in] Expected word count
+ * @param aToDoFlag [in] kTodo or kOk for returned text
+ */
+function testWordCount(aElement, aCount, aToDoFlag) {
+ var isFunc = aToDoFlag == kTodo ? todo_is : is;
+ var acc = getAccessible(aElement, nsIAccessibleText);
+ var startOffsetObj = {},
+ endOffsetObj = {};
+ var length = acc.characterCount;
+ var offset = 0;
+ var wordCount = 0;
+ while (true) {
+ acc.getTextAtOffset(
+ offset,
+ BOUNDARY_WORD_START,
+ startOffsetObj,
+ endOffsetObj
+ );
+ if (offset >= length) {
+ break;
+ }
+
+ wordCount++;
+ offset = endOffsetObj.value;
+ }
+ isFunc(
+ wordCount,
+ aCount,
+ "wrong words count for '" +
+ acc.getText(0, -1) +
+ "': " +
+ wordCount +
+ " in " +
+ prettyName(aElement)
+ );
+}
+
+/**
+ * Test word at a position for an element.
+ *
+ * @param aElement [in] element identifier
+ * @param aWordIndex [in] index of the word to test
+ * @param aText [in] expected text for that word
+ * @param aToDoFlag [in] kTodo or kOk for returned text
+ */
+function testWordAt(aElement, aWordIndex, aText, aToDoFlag) {
+ var isFunc = aToDoFlag == kTodo ? todo_is : is;
+ var acc = getAccessible(aElement, nsIAccessibleText);
+
+ var textLength = acc.characterCount;
+ var wordIdx = aWordIndex;
+ var startOffsetObj = { value: 0 },
+ endOffsetObj = { value: 0 };
+ for (let offset = 0; offset < textLength; offset = endOffsetObj.value) {
+ acc.getTextAtOffset(
+ offset,
+ BOUNDARY_WORD_START,
+ startOffsetObj,
+ endOffsetObj
+ );
+
+ wordIdx--;
+ if (wordIdx < 0) {
+ break;
+ }
+ }
+
+ if (wordIdx >= 0) {
+ ok(
+ false,
+ "the given word index '" +
+ aWordIndex +
+ "' exceeds words amount in " +
+ prettyName(aElement)
+ );
+
+ return;
+ }
+
+ var startWordOffset = startOffsetObj.value;
+ var endWordOffset = endOffsetObj.value;
+
+ // Calculate the end word offset.
+ acc.getTextAtOffset(
+ endOffsetObj.value,
+ BOUNDARY_WORD_END,
+ startOffsetObj,
+ endOffsetObj
+ );
+ if (startOffsetObj.value != textLength) {
+ endWordOffset = startOffsetObj.value;
+ }
+
+ if (endWordOffset <= startWordOffset) {
+ todo(
+ false,
+ "wrong start and end offset for word at index '" +
+ aWordIndex +
+ "': " +
+ " of text '" +
+ acc.getText(0, -1) +
+ "' in " +
+ prettyName(aElement)
+ );
+
+ return;
+ }
+
+ let text = acc.getText(startWordOffset, endWordOffset);
+ isFunc(
+ text,
+ aText,
+ "wrong text for word at index '" +
+ aWordIndex +
+ "': " +
+ " of text '" +
+ acc.getText(0, -1) +
+ "' in " +
+ prettyName(aElement)
+ );
+}
+
+/**
+ * Test words in a element.
+ *
+ * @param aElement [in] element identifier
+ * @param aWords [in] array of expected words
+ * @param aToDoFlag [in, optional] kTodo or kOk for returned text
+ */
+function testWords(aElement, aWords, aToDoFlag) {
+ if (aToDoFlag == null) {
+ aToDoFlag = kOk;
+ }
+
+ testWordCount(aElement, aWords.length, aToDoFlag);
+
+ for (var i = 0; i < aWords.length; i++) {
+ testWordAt(aElement, i, aWords[i], aToDoFlag);
+ }
+}
+
+/**
+ * Remove all selections.
+ *
+ * @param aID [in] Id, DOM node, or acc obj
+ */
+function cleanTextSelections(aID) {
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+
+ while (acc.selectionCount > 0) {
+ acc.removeSelection(0);
+ }
+}
+
+/**
+ * Test addSelection method.
+ *
+ * @param aID [in] Id, DOM node, or acc obj
+ * @param aStartOffset [in] start offset for the new selection
+ * @param aEndOffset [in] end offset for the new selection
+ * @param aSelectionsCount [in] expected number of selections after addSelection
+ */
+function testTextAddSelection(aID, aStartOffset, aEndOffset, aSelectionsCount) {
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+ var text = acc.getText(0, -1);
+
+ acc.addSelection(aStartOffset, aEndOffset);
+
+ is(
+ acc.selectionCount,
+ aSelectionsCount,
+ text +
+ ": failed to add selection from offset '" +
+ aStartOffset +
+ "' to offset '" +
+ aEndOffset +
+ "': selectionCount after"
+ );
+}
+
+/**
+ * Test removeSelection method.
+ *
+ * @param aID [in] Id, DOM node, or acc obj
+ * @param aSelectionIndex [in] index of the selection to be removed
+ * @param aSelectionsCount [in] expected number of selections after
+ * removeSelection
+ */
+function testTextRemoveSelection(aID, aSelectionIndex, aSelectionsCount) {
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+ var text = acc.getText(0, -1);
+
+ acc.removeSelection(aSelectionIndex);
+
+ is(
+ acc.selectionCount,
+ aSelectionsCount,
+ text +
+ ": failed to remove selection at index '" +
+ aSelectionIndex +
+ "': selectionCount after"
+ );
+}
+
+/**
+ * Test setSelectionBounds method.
+ *
+ * @param aID [in] Id, DOM node, or acc obj
+ * @param aStartOffset [in] new start offset for the selection
+ * @param aEndOffset [in] new end offset for the selection
+ * @param aSelectionIndex [in] index of the selection to set
+ * @param aSelectionsCount [in] expected number of selections after
+ * setSelectionBounds
+ */
+function testTextSetSelection(
+ aID,
+ aStartOffset,
+ aEndOffset,
+ aSelectionIndex,
+ aSelectionsCount
+) {
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+ var text = acc.getText(0, -1);
+
+ acc.setSelectionBounds(aSelectionIndex, aStartOffset, aEndOffset);
+
+ is(
+ acc.selectionCount,
+ aSelectionsCount,
+ text +
+ ": failed to set selection at index '" +
+ aSelectionIndex +
+ "': selectionCount after"
+ );
+}
+
+/**
+ * Test selectionCount method.
+ *
+ * @param aID [in] Id, DOM node, or acc obj
+ * @param aCount [in] expected selection count
+ */
+function testTextSelectionCount(aID, aCount) {
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+ var text = acc.getText(0, -1);
+
+ is(acc.selectionCount, aCount, text + ": wrong selectionCount: ");
+}
+
+/**
+ * Test getSelectionBounds method.
+ *
+ * @param aID [in] Id, DOM node, or acc obj
+ * @param aStartOffset [in] expected start offset for the selection
+ * @param aEndOffset [in] expected end offset for the selection
+ * @param aSelectionIndex [in] index of the selection to get
+ */
+function testTextGetSelection(aID, aStartOffset, aEndOffset, aSelectionIndex) {
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+ var text = acc.getText(0, -1);
+
+ var startObj = {},
+ endObj = {};
+ acc.getSelectionBounds(aSelectionIndex, startObj, endObj);
+
+ is(
+ startObj.value,
+ aStartOffset,
+ text + ": wrong start offset for index '" + aSelectionIndex + "'"
+ );
+ is(
+ endObj.value,
+ aEndOffset,
+ text + ": wrong end offset for index '" + aSelectionIndex + "'"
+ );
+}
+
+function testTextRange(
+ aRange,
+ aRangeDescr,
+ aStartContainer,
+ aStartOffset,
+ aEndContainer,
+ aEndOffset,
+ aText,
+ aCommonContainer,
+ aChildren
+) {
+ isObject(
+ aRange.startContainer,
+ getAccessible(aStartContainer),
+ "Wrong start container of " + aRangeDescr
+ );
+ is(aRange.startOffset, aStartOffset, "Wrong start offset of " + aRangeDescr);
+ isObject(
+ aRange.endContainer,
+ getAccessible(aEndContainer),
+ "Wrong end container of " + aRangeDescr
+ );
+ is(aRange.endOffset, aEndOffset, "Wrong end offset of " + aRangeDescr);
+
+ if (aText === undefined) {
+ return;
+ }
+
+ is(aRange.text, aText, "Wrong text of " + aRangeDescr);
+
+ var children = aRange.embeddedChildren;
+ is(
+ children ? children.length : 0,
+ aChildren ? aChildren.length : 0,
+ "Wrong embedded children count of " + aRangeDescr
+ );
+
+ isObject(
+ aRange.container,
+ getAccessible(aCommonContainer),
+ "Wrong container of " + aRangeDescr
+ );
+
+ if (aChildren) {
+ for (var i = 0; i < aChildren.length; i++) {
+ var expectedChild = getAccessible(aChildren[i]);
+ var actualChild = children.queryElementAt(i, nsIAccessible);
+ isObject(
+ actualChild,
+ expectedChild,
+ "Wrong child at index '" + i + "' of " + aRangeDescr
+ );
+ }
+ }
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Private
+
+function testTextSuperHelper(aFuncName, aArgs) {
+ // List of tests.
+ if (aArgs[2] instanceof Array) {
+ let ids = aArgs[0] instanceof Array ? aArgs[0] : [aArgs[0]];
+ let boundaryType = aArgs[1];
+ let list = aArgs[2];
+ for (let i = 0; i < list.length; i++) {
+ let offset1 = list[i][0],
+ offset2 = list[i][1];
+ let text = list[i][2],
+ startOffset = list[i][3],
+ endOffset = list[i][4];
+ let failureList = list[i][5];
+ for (let offset = offset1; offset <= offset2; offset++) {
+ for (let idIdx = 0; idIdx < ids.length; idIdx++) {
+ let id = ids[idIdx];
+
+ let flagOk1 = kOk,
+ flagOk2 = kOk,
+ flagOk3 = kOk;
+ if (failureList) {
+ for (let fIdx = 0; fIdx < failureList.length; fIdx++) {
+ if (
+ offset == failureList[fIdx][0] &&
+ id == failureList[fIdx][1]
+ ) {
+ flagOk1 = failureList[fIdx][2];
+ flagOk2 = failureList[fIdx][3];
+ flagOk3 = failureList[fIdx][4];
+ break;
+ }
+ }
+ }
+
+ let acc = getAccessible(id, nsIAccessibleText);
+ testTextHelper(
+ id,
+ offset,
+ boundaryType,
+ text,
+ startOffset,
+ endOffset,
+ flagOk1,
+ flagOk2,
+ flagOk3,
+ acc[aFuncName],
+ aFuncName + " "
+ );
+ }
+ }
+ }
+ return;
+ }
+
+ // Test at single offset. List of IDs.
+ var offset = aArgs[0];
+ var boundaryType = aArgs[1];
+ var text = aArgs[2];
+ var startOffset = aArgs[3];
+ var endOffset = aArgs[4];
+ if (aArgs[5] instanceof Array) {
+ let ids = aArgs[5];
+ for (let i = 0; i < ids.length; i++) {
+ let acc = getAccessible(ids[i], nsIAccessibleText);
+ testTextHelper(
+ ids[i],
+ offset,
+ boundaryType,
+ text,
+ startOffset,
+ endOffset,
+ kOk,
+ kOk,
+ kOk,
+ acc[aFuncName],
+ aFuncName + " "
+ );
+ }
+
+ return;
+ }
+
+ // Each ID is tested separately.
+ for (let i = 5; i < aArgs.length; i = i + 4) {
+ let ID = aArgs[i];
+ let acc = getAccessible(ID, nsIAccessibleText);
+ let toDoFlag1 = aArgs[i + 1];
+ let toDoFlag2 = aArgs[i + 2];
+ let toDoFlag3 = aArgs[i + 3];
+
+ testTextHelper(
+ ID,
+ offset,
+ boundaryType,
+ text,
+ startOffset,
+ endOffset,
+ toDoFlag1,
+ toDoFlag2,
+ toDoFlag3,
+ acc[aFuncName],
+ aFuncName + " "
+ );
+ }
+}
+
+function testTextHelper(
+ aID,
+ aOffset,
+ aBoundaryType,
+ aText,
+ aStartOffset,
+ aEndOffset,
+ aToDoFlag1,
+ aToDoFlag2,
+ aToDoFlag3,
+ aTextFunc,
+ aTextFuncName
+) {
+ var exceptionFlag =
+ aToDoFlag1 == undefined ||
+ aToDoFlag2 == undefined ||
+ aToDoFlag3 == undefined;
+
+ var startMsg = aTextFuncName + "(" + boundaryToString(aBoundaryType) + "): ";
+ var endMsg = ", id: " + prettyName(aID) + ";";
+
+ try {
+ var startOffsetObj = {},
+ endOffsetObj = {};
+ var text = aTextFunc(aOffset, aBoundaryType, startOffsetObj, endOffsetObj);
+
+ if (exceptionFlag) {
+ ok(false, startMsg + "no expected failure at offset " + aOffset + endMsg);
+ return;
+ }
+
+ var isFunc1 = aToDoFlag1 == kTodo ? todo : ok;
+ var isFunc2 = aToDoFlag2 == kTodo ? todo : ok;
+ var isFunc3 = aToDoFlag3 == kTodo ? todo : ok;
+
+ isFunc1(
+ text == aText,
+ startMsg +
+ "wrong text " +
+ "(got '" +
+ text +
+ "', expected: '" +
+ aText +
+ "')" +
+ ", offset: " +
+ aOffset +
+ endMsg
+ );
+ isFunc2(
+ startOffsetObj.value == aStartOffset,
+ startMsg +
+ "wrong start offset" +
+ "(got '" +
+ startOffsetObj.value +
+ "', expected: '" +
+ aStartOffset +
+ "')" +
+ ", offset: " +
+ aOffset +
+ endMsg
+ );
+ isFunc3(
+ endOffsetObj.value == aEndOffset,
+ startMsg +
+ "wrong end offset" +
+ "(got '" +
+ endOffsetObj.value +
+ "', expected: '" +
+ aEndOffset +
+ "')" +
+ ", offset: " +
+ aOffset +
+ endMsg
+ );
+ } catch (e) {
+ var okFunc = exceptionFlag ? todo : ok;
+ okFunc(
+ false,
+ startMsg + "failed at offset " + aOffset + endMsg + ", exception: " + e
+ );
+ }
+}
+
+function boundaryToString(aBoundaryType) {
+ switch (aBoundaryType) {
+ case BOUNDARY_CHAR:
+ return "char";
+ case BOUNDARY_WORD_START:
+ return "word start";
+ case BOUNDARY_WORD_END:
+ return "word end";
+ case BOUNDARY_LINE_START:
+ return "line start";
+ case BOUNDARY_LINE_END:
+ return "line end";
+ case BOUNDARY_PARAGRAPH:
+ return "paragraph";
+ }
+ return "unknown";
+}
diff --git a/accessible/tests/mochitest/text/a11y.ini b/accessible/tests/mochitest/text/a11y.ini
new file mode 100644
index 0000000000..18c4982aa6
--- /dev/null
+++ b/accessible/tests/mochitest/text/a11y.ini
@@ -0,0 +1,19 @@
+[DEFAULT]
+support-files = doc.html
+ !/accessible/tests/mochitest/*.js
+
+[test_atcaretoffset.html]
+[test_charboundary.html]
+[test_doc.html]
+[test_dynamic.html]
+[test_general.xhtml]
+[test_gettext.html]
+[test_hypertext.html]
+[test_lineboundary.html]
+[test_paragraphboundary.html]
+[test_passwords.html]
+[test_selection.html]
+[test_settext_input_event.html]
+[test_textBounds.html]
+[test_wordboundary.html]
+[test_words.html]
diff --git a/accessible/tests/mochitest/text/doc.html b/accessible/tests/mochitest/text/doc.html
new file mode 100644
index 0000000000..d57406c226
--- /dev/null
+++ b/accessible/tests/mochitest/text/doc.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="application/javascript">
+ document.documentElement.appendChild(document.createTextNode("outbody"));
+ </script>
+</head>
+<body>inbody</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_atcaretoffset.html b/accessible/tests/mochitest/text/test_atcaretoffset.html
new file mode 100644
index 0000000000..33fe7cd793
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_atcaretoffset.html
@@ -0,0 +1,425 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test: nsIAccessibleText getText* functions at caret offset</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ // gA11yEventDumpToConsole = true; // debugging
+
+ function traverseTextByLines(aQueue, aID, aLines) {
+ var wholeText = "";
+ for (var i = 0; i < aLines.length ; i++)
+ wholeText += aLines[i][0] + aLines[i][1];
+
+ var baseInvokerFunc = synthClick;
+ var charIter = new charIterator(wholeText, aLines);
+ // charIter.debugOffset = 10; // enable to run tests at given offset only
+
+ while (charIter.next()) {
+ aQueue.push(new tmpl_moveTo(aID, baseInvokerFunc, wholeText, charIter));
+ baseInvokerFunc = synthRightKey;
+ }
+ }
+
+ /**
+ * Used to get test list for each traversed character.
+ */
+ function charIterator(aWholeText, aLines) {
+ this.next = function charIterator_next() {
+ // Don't increment offset if we are at end of the wrapped line
+ // (offset is shared between end of this line and start of next line).
+ if (this.mAtWrappedLineEnd) {
+ this.mAtWrappedLineEnd = false;
+ this.mLine = this.mLine.nextLine;
+ return true;
+ }
+
+ this.mOffset++;
+ if (this.mOffset > aWholeText.length)
+ return false;
+
+ var nextLine = this.mLine.nextLine;
+ if (!nextLine.isFakeLine() && this.mOffset == nextLine.start) {
+ if (nextLine.start == this.mLine.end)
+ this.mAtWrappedLineEnd = true;
+ else
+ this.mLine = nextLine;
+ }
+
+ return true;
+ };
+
+ Object.defineProperty(this, "offset", { get() { return this.mOffset; },
+ });
+
+ Object.defineProperty(this, "offsetDescr", { get() {
+ return this.mOffset + " offset (" + this.mLine.number + " line, " +
+ (this.mOffset - this.mLine.start) + " offset on the line)";
+ },
+ });
+
+ Object.defineProperty(this, "tests", { get() {
+ // Line boundary tests.
+ var cLine = this.mLine;
+ var pLine = cLine.prevLine;
+ var ppLine = pLine.prevLine;
+ var nLine = cLine.nextLine;
+ var nnLine = nLine.nextLine;
+
+ var lineTests = [
+ [ testTextBeforeOffset, BOUNDARY_LINE_START, pLine.start, cLine.start],
+ [ testTextBeforeOffset, BOUNDARY_LINE_END, ppLine.end, pLine.end],
+ [ testTextAtOffset, BOUNDARY_LINE_START, cLine.start, nLine.start],
+ [ testTextAtOffset, BOUNDARY_LINE_END, pLine.end, cLine.end],
+ [ testTextAfterOffset, BOUNDARY_LINE_START, nLine.start, nnLine.start],
+ [ testTextAfterOffset, BOUNDARY_LINE_END, cLine.end, nLine.end],
+ ];
+
+ // Word boundary tests.
+ var cWord = this.mLine.firstWord;
+ var nWord = cWord.nextWord, pWord = cWord.prevWord;
+
+ // The current word is a farthest word starting at or after the offset.
+ if (this.mOffset >= nWord.start) {
+ while (this.mOffset >= nWord.start && !this.mLine.isLastWord(cWord)) {
+ cWord = nWord;
+ nWord = nWord.nextWord;
+ }
+ pWord = cWord.prevWord;
+ } else if (this.mOffset < cWord.start) {
+ while (this.mOffset < cWord.start) {
+ cWord = pWord;
+ pWord = pWord.prevWord;
+ }
+ nWord = cWord.nextWord;
+ }
+
+ var nnWord = nWord.nextWord, ppWord = pWord.prevWord;
+
+ var isAfterWordEnd =
+ this.mOffset > cWord.end || cWord.line != this.mLine;
+ var isAtOrAfterWordEnd = (this.mOffset >= cWord.end);
+ var useNextWordForAtWordEnd =
+ isAtOrAfterWordEnd && this.mOffset != aWholeText.length;
+
+ var wordTests = [
+ [ testTextBeforeOffset, BOUNDARY_WORD_START,
+ pWord.start, cWord.start ],
+ [ testTextBeforeOffset, BOUNDARY_WORD_END,
+ (isAfterWordEnd ? pWord : ppWord).end,
+ (isAfterWordEnd ? cWord : pWord).end ],
+ [ testTextAtOffset, BOUNDARY_WORD_START,
+ cWord.start, nWord.start ],
+ [ testTextAtOffset, BOUNDARY_WORD_END,
+ (useNextWordForAtWordEnd ? cWord : pWord).end,
+ (useNextWordForAtWordEnd ? nWord : cWord).end ],
+ [ testTextAfterOffset, BOUNDARY_WORD_START,
+ nWord.start, nnWord.start ],
+ [ testTextAfterOffset, BOUNDARY_WORD_END,
+ (isAfterWordEnd ? nWord : cWord).end,
+ (isAfterWordEnd ? nnWord : nWord).end ],
+ ];
+
+ // Character boundary tests.
+ var prevOffset = this.offset > 1 ? this.offset - 1 : 0;
+ var nextOffset = this.offset >= aWholeText.length ?
+ this.offset : this.offset + 1;
+ var nextAfterNextOffset = nextOffset >= aWholeText.length ?
+ nextOffset : nextOffset + 1;
+
+ var charTests = [
+ [ testTextBeforeOffset, BOUNDARY_CHAR,
+ prevOffset, this.offset ],
+ [ testTextAtOffset, BOUNDARY_CHAR,
+ this.offset,
+ this.mAtWrappedLineEnd ? this.offset : nextOffset ],
+ [ testTextAfterOffset, BOUNDARY_CHAR,
+ this.mAtWrappedLineEnd ? this.offset : nextOffset,
+ this.mAtWrappedLineEnd ? nextOffset : nextAfterNextOffset ],
+ ];
+
+ return lineTests.concat(wordTests.concat(charTests));
+ },
+ });
+
+ Object.defineProperty(this, "failures", { get() {
+ if (this.mOffset == this.mLine.start)
+ return this.mLine.lineStartFailures;
+ if (this.mOffset == this.mLine.end)
+ return this.mLine.lineEndFailures;
+ return [];
+ },
+ });
+
+ this.mOffset = -1;
+ this.mLine = new line(aWholeText, aLines, 0);
+ this.mAtWrappedLineEnd = false;
+ this.mWord = this.mLine.firstWord;
+ }
+
+ /**
+ * A line object. Allows to navigate by lines and by words.
+ */
+ function line(aWholeText, aLines, aIndex) {
+ Object.defineProperty(this, "prevLine", { get() {
+ return new line(aWholeText, aLines, aIndex - 1);
+ },
+ });
+ Object.defineProperty(this, "nextLine", { get() {
+ return new line(aWholeText, aLines, aIndex + 1);
+ },
+ });
+
+ Object.defineProperty(this, "start", { get() {
+ if (aIndex < 0)
+ return 0;
+
+ if (aIndex >= aLines.length)
+ return aWholeText.length;
+
+ return aLines[aIndex][2];
+ },
+ });
+ Object.defineProperty(this, "end", { get() {
+ if (aIndex < 0)
+ return 0;
+
+ if (aIndex >= aLines.length)
+ return aWholeText.length;
+
+ return aLines[aIndex][3];
+ },
+ });
+
+ Object.defineProperty(this, "number", { get() { return aIndex; },
+ });
+ Object.defineProperty(this, "wholeText", { get() { return aWholeText; },
+ });
+ this.isFakeLine = function line_isFakeLine() {
+ return aIndex < 0 || aIndex >= aLines.length;
+ };
+
+ Object.defineProperty(this, "lastWord", { get() {
+ if (aIndex < 0)
+ return new word(this, [], -1);
+ if (aIndex >= aLines.length)
+ return new word(this, [], 0);
+
+ var words = aLines[aIndex][4].words;
+ return new word(this, words, words.length - 2);
+ },
+ });
+ Object.defineProperty(this, "firstWord", { get() {
+ if (aIndex < 0)
+ return new word(this, [], -1);
+ if (aIndex >= aLines.length)
+ return new word(this, [], 0);
+
+ var words = aLines[aIndex][4].words;
+ return new word(this, words, 0);
+ },
+ });
+
+ this.isLastWord = function line_isLastWord(aWord) {
+ var lastWord = this.lastWord;
+ return lastWord.start == aWord.start && lastWord.end == aWord.end;
+ };
+
+ Object.defineProperty(this, "lineStartFailures", { get() {
+ if (aIndex < 0 || aIndex >= aLines.length)
+ return [];
+
+ return aLines[aIndex][4].lsf || [];
+ },
+ });
+ Object.defineProperty(this, "lineEndFailures", { get() {
+ if (aIndex < 0 || aIndex >= aLines.length)
+ return [];
+
+ return aLines[aIndex][4].lef || [];
+ },
+ });
+ }
+
+ /**
+ * A word object. Allows to navigate by words.
+ */
+ function word(aLine, aWords, aIndex) {
+ Object.defineProperty(this, "prevWord", { get() {
+ if (aIndex >= 2)
+ return new word(aLine, aWords, aIndex - 2);
+
+ var prevLineLastWord = aLine.prevLine.lastWord;
+ if (this.start == prevLineLastWord.start && !this.isFakeStartWord())
+ return prevLineLastWord.prevWord;
+ return prevLineLastWord;
+ },
+ });
+ Object.defineProperty(this, "nextWord", { get() {
+ if (aIndex + 2 < aWords.length)
+ return new word(aLine, aWords, aIndex + 2);
+
+ var nextLineFirstWord = aLine.nextLine.firstWord;
+ if (this.end == nextLineFirstWord.end && !this.isFakeEndWord())
+ return nextLineFirstWord.nextWord;
+ return nextLineFirstWord;
+ },
+ });
+
+ Object.defineProperty(this, "line", { get() { return aLine; } });
+
+ Object.defineProperty(this, "start", { get() {
+ if (this.isFakeStartWord())
+ return 0;
+
+ if (this.isFakeEndWord())
+ return aLine.end;
+ return aWords[aIndex];
+ },
+ });
+ Object.defineProperty(this, "end", { get() {
+ if (this.isFakeStartWord())
+ return 0;
+
+ return this.isFakeEndWord() ? aLine.end : aWords[aIndex + 1];
+ },
+ });
+
+ this.toString = function word_toString() {
+ var start = this.start, end = this.end;
+ return "'" + aLine.wholeText.substring(start, end) +
+ "' at [" + start + ", " + end + "]";
+ };
+
+ this.isFakeStartWord = function() { return aIndex < 0; };
+ this.isFakeEndWord = function() { return aIndex >= aWords.length; };
+ }
+
+ /**
+ * A template invoker to move through the text.
+ */
+ function tmpl_moveTo(aID, aInvokerFunc, aWholeText, aCharIter) {
+ this.offset = aCharIter.offset;
+
+ var checker = new caretMoveChecker(this.offset, true, aID);
+ this.__proto__ = new (aInvokerFunc)(aID, checker);
+
+ this.finalCheck = function genericMoveTo_finalCheck() {
+ if (this.noTests())
+ return;
+
+ for (var i = 0; i < this.tests.length; i++) {
+ var func = this.tests[i][0];
+ var boundary = this.tests[i][1];
+ var startOffset = this.tests[i][2];
+ var endOffset = this.tests[i][3];
+ var text = aWholeText.substring(startOffset, endOffset);
+
+ var isOk1 = kOk, isOk2 = kOk, isOk3 = kOk;
+ for (var fIdx = 0; fIdx < this.failures.length; fIdx++) {
+ var failure = this.failures[fIdx];
+ if (func.name.includes(failure[0]) && boundary == failure[1]) {
+ isOk1 = failure[2];
+ isOk2 = failure[3];
+ isOk3 = failure[4];
+ }
+ }
+
+ func(kCaretOffset, boundary, text, startOffset, endOffset,
+ aID, isOk1, isOk2, isOk3);
+ }
+ };
+
+ this.getID = function genericMoveTo_getID() {
+ return "move to " + this.offsetDescr;
+ };
+
+ this.noTests = function tmpl_moveTo_noTests() {
+ return ("debugOffset" in aCharIter) &&
+ (aCharIter.debugOffset != this.offset);
+ };
+
+ this.offsetDescr = aCharIter.offsetDescr;
+ this.tests = this.noTests() ? null : aCharIter.tests;
+ this.failures = aCharIter.failures;
+ }
+
+ var gQueue = null;
+ function doTest() {
+ gQueue = new eventQueue();
+
+ // __a__w__o__r__d__\n
+ // 0 1 2 3 4 5
+ // __t__w__o__ (soft line break)
+ // 6 7 8 9
+ // __w__o__r__d__s
+ // 10 11 12 13 14 15
+
+ traverseTextByLines(gQueue, "textarea",
+ [ [ "aword", "\n", 0, 5, { words: [ 0, 5 ] } ],
+ [ "two ", "", 6, 10, { words: [ 6, 9 ] } ],
+ [ "words", "", 10, 15, { words: [ 10, 15 ] } ],
+ ] );
+
+ var line4 = [ // "riend "
+ [ "TextBeforeOffset", BOUNDARY_WORD_END,
+ kOk, kOk, kOk],
+ [ "TextAfterOffset", BOUNDARY_WORD_END,
+ kOk, kOk, kOk ],
+ ];
+ traverseTextByLines(gQueue, "ta_wrapped",
+ [ [ "hi ", "", 0, 3, { words: [ 0, 2 ] } ],
+ [ "hello ", "", 3, 9, { words: [ 3, 8 ] } ],
+ [ "my ", "", 9, 12, { words: [ 9, 11 ] } ],
+ [ "longf", "", 12, 17, { words: [ 12, 17 ] } ],
+ [ "riend ", "", 17, 23, { words: [ 17, 22 ], lsf: line4 } ],
+ [ "t sq ", "", 23, 28, { words: [ 23, 24, 25, 27 ] } ],
+ [ "t", "", 28, 29, { words: [ 28, 29 ] } ],
+ ] );
+
+ gQueue.invoke(); // will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="nsIAccessibleText getText related functions tests at caret offset"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=852021">
+ Bug 852021
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+
+ <textarea id="textarea" cols="5">aword
+two words</textarea>
+
+ <!-- scrollbar-width: none is needed so that the width of the scrollbar
+ doesn't incorrectly affect the width of the textarea on some systems.
+ See bug 1600170 and bug 33654.
+ -->
+ <textarea id="ta_wrapped" cols="5" style="scrollbar-width: none;">hi hello my longfriend t sq t</textarea>
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_charboundary.html b/accessible/tests/mochitest/text/test_charboundary.html
new file mode 100644
index 0000000000..5ca4120a47
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_charboundary.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Char boundary text tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ //
+ // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+
+ var IDs = [ "i1", "d1", "e1", "t1" ];
+
+ testCharBeforeOffset(IDs, 0, "", 0, 0);
+ testCharBeforeOffset(IDs, 1, "h", 0, 1);
+ testCharBeforeOffset(IDs, 14, "n", 13, 14);
+ testCharBeforeOffset(IDs, 15, "d", 14, 15);
+
+ testCharAtOffset(IDs, 0, "h", 0, 1);
+ testCharAtOffset(IDs, 1, "e", 1, 2);
+ testCharAtOffset(IDs, 14, "d", 14, 15);
+ testCharAtOffset(IDs, 15, "", 15, 15);
+
+ testCharAfterOffset(IDs, 0, "e", 1, 2);
+ testCharAfterOffset(IDs, 1, "l", 2, 3);
+ testCharAfterOffset(IDs, 14, "", 15, 15);
+ testCharAfterOffset(IDs, 15, "", 15, 15);
+
+ // ////////////////////////////////////////////////////////////////////////
+ //
+ // __B__r__a__v__e__ __S__i__r__ __ __R__o__b__i__n__ __ __ __r__a__n
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
+
+ IDs = [ "i2", "d2", "e2", "t2" ];
+
+ testCharBeforeOffset(IDs, 0, "", 0, 0);
+ testCharBeforeOffset(IDs, 1, "B", 0, 1);
+ testCharBeforeOffset(IDs, 6, " ", 5, 6);
+ testCharBeforeOffset(IDs, 10, " ", 9, 10);
+ testCharBeforeOffset(IDs, 11, " ", 10, 11);
+ testCharBeforeOffset(IDs, 17, " ", 16, 17);
+ testCharBeforeOffset(IDs, 19, " ", 18, 19);
+
+ testCharAtOffset(IDs, 0, "B", 0, 1);
+ testCharAtOffset(IDs, 1, "r", 1, 2);
+ testCharAtOffset(IDs, 5, " ", 5, 6);
+ testCharAtOffset(IDs, 9, " ", 9, 10);
+ testCharAtOffset(IDs, 10, " ", 10, 11);
+ testCharAtOffset(IDs, 17, " ", 17, 18);
+
+ testCharAfterOffset(IDs, 0, "r", 1, 2);
+ testCharAfterOffset(IDs, 1, "a", 2, 3);
+ testCharAfterOffset(IDs, 4, " ", 5, 6);
+ testCharAfterOffset(IDs, 5, "S", 6, 7);
+ testCharAfterOffset(IDs, 8, " ", 9, 10);
+ testCharAfterOffset(IDs, 9, " ", 10, 11);
+ testCharAfterOffset(IDs, 10, "R", 11, 12);
+ testCharAfterOffset(IDs, 15, " ", 16, 17);
+ testCharAfterOffset(IDs, 16, " ", 17, 18);
+ testCharAfterOffset(IDs, 17, " ", 18, 19);
+ testCharAfterOffset(IDs, 18, "r", 19, 20);
+
+ // ////////////////////////////////////////////////////////////////////////
+ //
+ // __o__n__e__w__o__r__d__\n
+ // 0 1 2 3 4 5 6 7
+ // __\n
+ // 8
+ // __t__w__o__ __w__o__r__d__s__\n
+ // 9 10 11 12 13 14 15 16 17 18
+
+ IDs = ["d3", "dbr3", "e3", "ebr3", "t3"];
+
+ testCharBeforeOffset(IDs, 8, "\n", 7, 8);
+ testCharBeforeOffset(IDs, 9, "\n", 8, 9);
+ testCharBeforeOffset(IDs, 10, "t", 9, 10);
+
+ testCharAtOffset(IDs, 7, "\n", 7, 8);
+ testCharAtOffset(IDs, 8, "\n", 8, 9);
+ testCharAtOffset(IDs, 9, "t", 9, 10);
+
+ testCharAfterOffset(IDs, 6, "\n", 7, 8);
+ testCharAfterOffset(IDs, 7, "\n", 8, 9);
+ testCharAfterOffset(IDs, 8, "t", 9, 10);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="i1" value="hello my friend"/>
+ <div id="d1">hello my friend</div>
+ <div id="e1" contenteditable="true">hello my friend</div>
+ <textarea id="t1" contenteditable="true">hello my friend</textarea>
+
+ <input id="i2" value="Brave Sir Robin ran"/>
+ <pre>
+ <div id="d2">Brave Sir Robin ran</div>
+ <div id="e2" contenteditable="true">Brave Sir Robin ran</div>
+ </pre>
+ <textarea id="t2" cols="300">Brave Sir Robin ran</textarea>
+
+ <pre>
+ <div id="d3">oneword
+
+two words
+</div>
+ <div id="dbr3">oneword<br/><br/>two words<br/></div>
+ <div id="e3" contenteditable="true">oneword
+
+two words
+</div>
+ <div id="ebr3" contenteditable="true">oneword<br/><br/>two words<br/></div>
+ <textarea id="t3" cols="300">oneword
+
+two words</textarea>
+ </pre>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_doc.html b/accessible/tests/mochitest/text/test_doc.html
new file mode 100644
index 0000000000..88b75b98c4
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_doc.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessibleText getText related function tests for document accessible</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript">
+
+ function doTest() {
+ var iframeDoc = [ getNode("iframe").contentDocument ];
+ testCharacterCount(iframeDoc, 15);
+ testText(iframeDoc, 0, 15, "outbody inbody ");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Elements appended outside the body aren't accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887">Mozilla Bug 608887</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <iframe id="iframe" src="doc.html"></iframe>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_dynamic.html b/accessible/tests/mochitest/text/test_dynamic.html
new file mode 100644
index 0000000000..63889fc664
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_dynamic.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessibleText getText related function tests for tree mutations</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function insertBefore(aId, aEl, aTextBefore, aTextAfter, aStartIdx, aEndIdx) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aId),
+ ];
+
+ this.invoke = function insertBefore_invoke() {
+ testText(aId, 0, -1, aTextBefore);
+ getNode(aId).insertBefore(aEl, getNode(aId).firstChild);
+ };
+
+ this.finalCheck = function insertBefore_finalCheck() {
+ testText(aId, aStartIdx, aEndIdx, aTextAfter);
+ };
+
+ this.getID = function insertTextBefore_getID() {
+ return "insert " + prettyName(aEl) + " before";
+ };
+ }
+
+ function insertTextBefore(aId, aTextBefore, aText) {
+ var el = document.createTextNode(aText);
+ this.__proto__ = new insertBefore(aId, el, aTextBefore,
+ aText + aTextBefore, 0, -1);
+ }
+
+ function insertImgBefore(aId, aTextBefore) {
+ var el = document.createElement("img");
+ el.setAttribute("src", "../moz.png");
+ el.setAttribute("alt", "mozilla");
+
+ this.__proto__ = new insertBefore(aId, el, aTextBefore,
+ kEmbedChar + aTextBefore, 0, -1);
+ }
+
+ function insertTextBefore2(aId) {
+ var el = document.createTextNode("hehe");
+ this.__proto__ = new insertBefore(aId, el, "ho", "ho", 4, -1);
+ }
+
+ var gQueue = null;
+ function doTest() {
+ gQueue = new eventQueue();
+ gQueue.push(new insertTextBefore("c1", "ho", "ha"));
+ gQueue.push(new insertImgBefore("c1", "haho"));
+ gQueue.push(new insertImgBefore("c2", kEmbedChar));
+ gQueue.push(new insertTextBefore2("c3"));
+ gQueue.invoke(); // will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="c1">ho</div>
+ <div id="c2"><img src="../moz.png" alt="mozilla"></div>
+ <div id="c3">ho</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_general.xhtml b/accessible/tests/mochitest/text/test_general.xhtml
new file mode 100644
index 0000000000..df0ffcc0c6
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_general.xhtml
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Tests: XUL label text interface">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Testing
+
+ var gQueue = null;
+ function doTests()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // XUL label
+
+ var ids = ["label1", "label2"];
+
+ testCharacterCount(ids, 5);
+
+ testText(ids, 0, -1, "Hello");
+ testText(ids, 0, 1, "H");
+
+ testCharAfterOffset(ids, 0, "e", 1, 2);
+ testCharBeforeOffset(ids, 1, "H", 0, 1);
+ testCharAtOffset(ids, 1, "e", 1, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML input
+
+ testTextAtOffset([ getNode("tbox1") ], BOUNDARY_LINE_START,
+ [ [ 0, 4, "test", 0, 4 ] ]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=396166"
+ title="xul:label@value accessible should implement nsIAccessibleText">
+ Bug 396166
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=899433"
+ title="Accessibility returns empty line for last line in certain cases">
+ Bug 899433
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+ <label id="label1" value="Hello"/>
+ <label id="label2">Hello</label>
+
+ <html:input id="tbox1" value="test"/>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/text/test_gettext.html b/accessible/tests/mochitest/text/test_gettext.html
new file mode 100644
index 0000000000..2f221a416b
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_gettext.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Get text between offsets tests</title>
+ <meta charset="utf-8">
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ //
+ // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+
+ var IDs = [ "i1", "d1", "d1wrap", "e1", "t1" ];
+
+ testCharacterCount(IDs, 15);
+
+ testText(IDs, 0, 1, "h");
+ testText(IDs, 1, 3, "el");
+ testText(IDs, 14, 15, "d");
+ testText(IDs, 0, 15, "hello my friend");
+ testText(IDs, 0, -1, "hello my friend");
+
+ // ////////////////////////////////////////////////////////////////////////
+ //
+ // __B__r__a__v__e__ __S__i__r__ __ __R__o__b__i__n__ __ __ __r__a__n
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
+
+ IDs = [ "i2", "dpre2", "epre2", "t2" ];
+
+ testCharacterCount(IDs, 22);
+
+ testText(IDs, 0, 1, "B");
+ testText(IDs, 5, 6, " ");
+ testText(IDs, 9, 11, " ");
+ testText(IDs, 16, 19, " ");
+ testText(IDs, 0, 22, "Brave Sir Robin ran");
+ testText(IDs, 0, -1, "Brave Sir Robin ran");
+
+ testCharacterCount(["d2", "e2"], 19);
+ testText(["d2", "e2"], 0, 19, "Brave Sir Robin ran");
+
+ // ////////////////////////////////////////////////////////////////////////
+ //
+ // __o__n__e__w__o__r__d__\n
+ // 0 1 2 3 4 5 6 7
+ // __\n
+ // 8
+ // __t__w__o__ __w__o__r__d__s__\n
+ // 9 10 11 12 13 14 15 16 17 18
+
+ IDs = ["d3", "dbr3", "e3", "ebr3", "t3"];
+
+ testCharacterCount(IDs, 19);
+
+ testText(IDs, 0, 19, "oneword\n\ntwo words\n");
+ testText(IDs, 0, -1, "oneword\n\ntwo words\n");
+
+ // ////////////////////////////////////////////////////////////////////////
+ //
+ // CSS text-transform
+ //
+ // Content with `text-transform:uppercase | lowercase | capitalize` returns
+ // the transformed content.
+ //
+ testText(["d4a"], 0, -1, "HELLO MY FRIEND");
+ testText(["d4b"], 0, -1, "hello my friend");
+ testText(["d4c"], 0, -1, "Hello My Friend");
+
+ // `text-transform: full-width | full-size-kana` should not be reflected in
+ // a11y.
+ testText(["d5a"], 0, -1, "hello my friend");
+ testText(["d5b"], 0, -1, "ゕゖヵヶ");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="i1" value="hello my friend"/>
+ <div id="d1">hello my friend</div>
+ <div id="d1wrap" style="word-wrap:break-word; width:1px">hello my friend</div>
+ <div id="e1" contenteditable="true">hello my friend</div>
+ <textarea id="t1">hello my friend</textarea>
+
+ <input id="i2" value="Brave Sir Robin ran"/>
+ <pre><div id="dpre2">Brave Sir Robin ran</div></pre>
+ <pre><div id="epre2" contenteditable="true">Brave Sir Robin ran</div></pre>
+ <textarea id="t2" cols="300">Brave Sir Robin ran</textarea>
+ <div id="d2">Brave Sir Robin ran</div>
+ <div id="e2" contenteditable="true">Brave Sir Robin ran</div>
+
+ <pre>
+ <div id="d3">oneword
+
+two words
+</div>
+ <div id="dbr3">oneword<br/><br/>two words<br/></div>
+ <div id="e3" contenteditable="true">oneword
+
+two words
+</div>
+ <div id="ebr3" contenteditable="true">oneword<br/><br/>two words<br/></div>
+ <textarea id="t3" cols="300">oneword
+
+two words
+</textarea>
+ </pre>
+
+ <div id="d4a" style="text-transform:uppercase">Hello My Friend</div>
+ <div id="d4b" style="text-transform:lowercase">Hello My Friend</div>
+ <div id="d4c" style="text-transform:capitalize">hello my friend</div>
+
+ <div id="d5a" style="text-transform:full-width">hello my friend</div>
+ <div id="d5b" style="text-transform:full-size-kana">ゕゖヵヶ</div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_hypertext.html b/accessible/tests/mochitest/text/test_hypertext.html
new file mode 100644
index 0000000000..b8a289ea52
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_hypertext.html
@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessibleText getText related function tests for rich text</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ #listitemnone {
+ list-style-type: none;
+ }
+ h6.gencontent:before {
+ content: "aga"
+ }
+ </style>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // null getText
+ // ////////////////////////////////////////////////////////////////////////
+
+ var emptyTextAcc = getAccessible("nulltext", [nsIAccessibleText]);
+ is(emptyTextAcc.getText(0, -1), "", "getText() END_OF_TEXT with null string");
+ is(emptyTextAcc.getText(0, 0), "", "getText() Len==0 with null string");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // hypertext
+ // ////////////////////////////////////////////////////////////////////////
+
+ // ! - embedded object char
+ // __h__e__l__l__o__ __!__ __s__e__e__ __!__
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+
+ var IDs = [ "hypertext", "hypertext2", "ht_displaycontents" ];
+
+ // //////////////////////////////////////////////////////////////////////
+ // characterCount
+
+ testCharacterCount(IDs, 13);
+
+ // //////////////////////////////////////////////////////////////////////
+ // getText
+
+ testText(IDs, 0, 1, "h");
+ testText(IDs, 5, 7, " " + kEmbedChar);
+ testText(IDs, 10, 13, "e " + kEmbedChar);
+ testText(IDs, 0, 13, "hello " + kEmbedChar + " see " + kEmbedChar);
+
+ // //////////////////////////////////////////////////////////////////////
+ // getTextAtOffset line boundary
+
+ testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5,
+ "hypertext3", kOk, kOk, kOk);
+
+ // XXX: see bug 634202.
+ testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5,
+ "hypertext4", kOk, kOk, kOk);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // list
+ // ////////////////////////////////////////////////////////////////////////
+
+ IDs = [ "list" ];
+ testCharacterCount(IDs, 2);
+ testText(IDs, 0, 2, kEmbedChar + kEmbedChar);
+
+ IDs = [ "listitem" ];
+ testCharacterCount(IDs, 6);
+ testText(IDs, 0, 6, "1. foo");
+
+ IDs = [ "listitemnone" ];
+ testCharacterCount(IDs, 3);
+ testText(IDs, 0, 3, "bar");
+
+ testText(["testbr"], 0, 3, "foo");
+
+ testTextAtOffset(2, nsIAccessibleText.BOUNDARY_CHAR, "o", 2, 3, "testbr",
+ kOk, kOk, kOk);
+ testTextAtOffset(2, nsIAccessibleText.BOUNDARY_WORD_START, "foo\n", 0, 4,
+ "testbr", kOk, kOk, kOk);
+ testTextBeforeOffset(2, nsIAccessibleText.BOUNDARY_LINE_START, "foo\n",
+ 0, 4, "testbr", kTodo, kOk, kTodo);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Fix getText"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630001">
+ Bug 630001, part3
+ </a>
+ <a target="_blank"
+ title="getTextAtOffset line boundary may return more than one line"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=638326">
+ Bug 638326
+ </a>
+ <a target="_blank"
+ title="getText(0, -1) fails with empty text"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=749810">
+ Bug 749810
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="nulltext"></div>
+
+ <div id="hypertext">hello <a>friend</a> see <img src="about:blank"></div>
+ <div id="hypertext2">hello <a>friend</a> see <input></div>
+ <div id="ht_displaycontents">hello <a>friend</a> see <ul id="ul" style="display: contents;">
+ <li>Supermarket 1</li>
+ <li>Supermarket 2</li>
+ </ul></div>
+ <ol id="list">
+ <li id="listitem">foo</li>
+ <li id="listitemnone">bar</li>
+ </ol>
+
+ <div id="hypertext3">line
+<!-- haha -->
+<!-- hahaha -->
+<h6>heading</h6>
+ </div>
+
+ <div id="hypertext4">line
+<!-- haha -->
+<!-- hahaha -->
+<h6 role="presentation" class="gencontent">heading</h6>
+ </div>
+
+ <div id="testbr">foo<br/></div>
+
+ <div></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_lineboundary.html b/accessible/tests/mochitest/text/test_lineboundary.html
new file mode 100644
index 0000000000..77b35ece5d
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_lineboundary.html
@@ -0,0 +1,422 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Line boundary getText* functions tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript">
+ function doTest() {
+ testTextAtOffset("line_test_1", BOUNDARY_LINE_START,
+ [[0, 6, "Line 1 ", 0, 7],
+ // See the kOk test below.
+ // [7, 7, kEmbedChar, 7, 8],
+ [8, 15, "Line 3 ", 8, 15]]);
+ testTextAtOffset(/* aOffset */ 7, BOUNDARY_LINE_START,
+ kEmbedChar, /* aStartOffset */ 7, /* aEndOffset */ 8,
+ "line_test_1",
+ /* returned text */ kOk,
+ /* returned start offset */ kOk, /* returned end offset */ kOk);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+
+ var IDs = [ "input", "div", "editable", "textarea",
+ getNode("ta", getNode("ta_cntr").contentDocument) ];
+
+ testTextBeforeOffset(IDs, BOUNDARY_LINE_START,
+ [ [ 0, 15, "", 0, 0 ] ]);
+ testTextBeforeOffset(IDs, BOUNDARY_LINE_END,
+ [ [ 0, 15, "", 0, 0 ] ]);
+
+ testTextAtOffset(IDs, BOUNDARY_LINE_START,
+ [ [ 0, 15, "hello my friend", 0, 15 ] ]);
+ testTextAtOffset(IDs, BOUNDARY_LINE_END,
+ [ [ 0, 15, "hello my friend", 0, 15 ] ]);
+
+ testTextAfterOffset(IDs, BOUNDARY_LINE_START,
+ [ [ 0, 15, "", 15, 15 ] ]);
+ testTextAfterOffset(IDs, BOUNDARY_LINE_END,
+ [ [ 0, 15, "", 15, 15 ] ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // __o__n__e__w__o__r__d__\n
+ // 0 1 2 3 4 5 6 7
+ // __\n
+ // 8
+ // __t__w__o__ __w__o__r__d__s__\n
+ // 9 10 11 12 13 14 15 16 17 18
+
+ IDs = [ "ml_div", "ml_divbr", "ml_editable", "ml_editablebr", "ml_textarea"];
+
+ testTextBeforeOffset(IDs, BOUNDARY_LINE_START,
+ [ [ 0, 7, "", 0, 0 ],
+ [ 8, 8, "oneword\n", 0, 8 ],
+ [ 9, 18, "\n", 8, 9 ],
+ [ 19, 19, "two words\n", 9, 19 ]]);
+
+ testTextBeforeOffset(IDs, BOUNDARY_LINE_END,
+ [ [ 0, 7, "", 0, 0 ],
+ [ 8, 8, "oneword", 0, 7 ],
+ [ 9, 18, "\n", 7, 8 ],
+ [ 19, 19, "\ntwo words", 8, 18 ]]);
+
+ testTextAtOffset(IDs, BOUNDARY_LINE_START,
+ [ [ 0, 7, "oneword\n", 0, 8 ],
+ [ 8, 8, "\n", 8, 9 ],
+ [ 9, 18, "two words\n", 9, 19 ],
+ [ 19, 19, "", 19, 19 ]]);
+ testTextAtOffset(IDs, BOUNDARY_LINE_END,
+ [ [ 0, 7, "oneword", 0, 7 ],
+ [ 8, 8, "\n", 7, 8 ],
+ [ 9, 18, "\ntwo words", 8, 18 ],
+ [ 19, 19, "\n", 18, 19 ]]);
+
+ testTextAfterOffset(IDs, BOUNDARY_LINE_START,
+ [ [ 0, 7, "\n", 8, 9 ],
+ [ 8, 8, "two words\n", 9, 19 ],
+ [ 9, 19, "", 19, 19 ]]);
+ testTextAfterOffset(IDs, BOUNDARY_LINE_END,
+ [ [ 0, 7, "\n", 7, 8 ],
+ [ 8, 8, "\ntwo words", 8, 18 ],
+ [ 9, 18, "\n", 18, 19 ],
+ [ 19, 19, "", 19, 19 ]]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // a * b (* is embedded char for link)
+ testTextBeforeOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_START,
+ [ [ 0, 5, "", 0, 0 ] ]);
+
+ testTextBeforeOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_END,
+ [ [ 0, 5, "", 0, 0 ] ]);
+
+ testTextAtOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_START,
+ [ [ 0, 5, "a " + kEmbedChar + " c", 0, 5 ] ]);
+
+ testTextAtOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_END,
+ [ [ 0, 5, "a " + kEmbedChar + " c", 0, 5 ] ]);
+
+ testTextAfterOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_START,
+ [ [ 0, 5, "", 5, 5 ] ]);
+
+ testTextAfterOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_END,
+ [ [ 0, 5, "", 5, 5 ] ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // foo<br> and foo<br><br>
+
+ testTextAtOffset([ getAccessible("ht_2").firstChild.firstChild ],
+ BOUNDARY_LINE_START,
+ [ [ 0, 3, "foo\n", 0, 4 ] ]);
+ testTextAtOffset([ getAccessible("ht_3").firstChild.firstChild ],
+ BOUNDARY_LINE_START,
+ [ [ 0, 3, "foo\n", 0, 4 ], [ 4, 4, "\n", 4, 5 ] ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // 'Hello world ' (\n is rendered as space)
+
+ testTextAtOffset([ "ht_4" ], BOUNDARY_LINE_START,
+ [ [ 0, 12, "Hello world ", 0, 12 ] ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // list items
+
+ testTextAtOffset([ "li1" ], BOUNDARY_LINE_START,
+ [ [ 0, 6, kDiscBulletText + "Item", 0, 6 ] ]);
+ testTextAtOffset([ "li2" ], BOUNDARY_LINE_START,
+ [ [ 0, 2, kDiscBulletText, 0, 2 ] ]);
+ testTextAtOffset([ "li3" ], BOUNDARY_LINE_START,
+ [ [ 0, 8, kDiscBulletText + "a long ", 0, 9 ],
+ [ 9, 12, "and ", 9, 13 ] ]);
+ testTextAtOffset([ "li4" ], BOUNDARY_LINE_START,
+ [ [ 0, 7, kDiscBulletText + "a " + kEmbedChar + " c", 0, 7 ] ]);
+ testTextAtOffset([ "li5" ], BOUNDARY_LINE_START,
+ [ [ 0, 2, kDiscBulletText + "\n", 0, 3 ],
+ [ 3, 7, "hello", 3, 8 ] ]);
+ testTextAtOffset([ "ul1" ], BOUNDARY_LINE_START,
+ [ [ 0, 0, kEmbedChar, 0, 1 ],
+ [ 1, 1, kEmbedChar, 1, 2 ],
+ [ 2, 2, kEmbedChar, 2, 3 ],
+ [ 3, 3, kEmbedChar, 3, 4 ],
+ [ 4, 5, kEmbedChar, 4, 5 ] ]);
+
+ testTextAtOffset([ "li6" ], BOUNDARY_LINE_START,
+ [ [ 0, 7, "1. Item", 0, 7 ] ]);
+ testTextAtOffset([ "li7" ], BOUNDARY_LINE_START,
+ [ [ 0, 3, "2. ", 0, 3 ] ]);
+ testTextAtOffset([ "li8" ], BOUNDARY_LINE_START,
+ [ [ 0, 9, "3. a long ", 0, 10 ],
+ [ 10, 13, "and ", 10, 14 ] ]);
+ testTextAtOffset([ "li9" ], BOUNDARY_LINE_START,
+ [ [ 0, 8, "4. a " + kEmbedChar + " c", 0, 8 ] ]);
+ testTextAtOffset([ "li10" ], BOUNDARY_LINE_START,
+ [ [ 0, 3, "5. \n", 0, 4 ],
+ [ 4, 8, "hello", 4, 9 ] ]);
+ testTextAtOffset([ "ol1" ], BOUNDARY_LINE_START,
+ [ [ 0, 0, kEmbedChar, 0, 1 ],
+ [ 1, 1, kEmbedChar, 1, 2 ],
+ [ 2, 2, kEmbedChar, 2, 3 ],
+ [ 3, 3, kEmbedChar, 3, 4 ],
+ [ 4, 5, kEmbedChar, 4, 5 ] ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Nested hypertexts
+
+ testTextAtOffset(["ht_5" ], BOUNDARY_LINE_START,
+ [ [ 0, 0, kEmbedChar, 0, 1 ] ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Block followed by list
+
+ testTextAtOffset([ "block_then_ul" ], BOUNDARY_LINE_START,
+ [ [ 0, 0, kEmbedChar, 0, 1 ],
+ [ 1, 1, kEmbedChar, 1, 2 ] ]);
+
+ // Embedded char containing a line break breaks line offsets in parent.
+ testTextAtOffset([ "brInEmbed" ], BOUNDARY_LINE_START,
+ [ [0, 1, "a " + kEmbedChar, 0, 3],
+ [2, 2, "a " + kEmbedChar + " d", 0, 5],
+ [3, 5, kEmbedChar + " d", 2, 5] ]);
+ testTextAtOffset([ "brInEmbedAndBefore" ], BOUNDARY_LINE_START,
+ [ [0, 1, "a\n", 0, 2],
+ [2, 3, "b " + kEmbedChar, 2, 5],
+ [4, 4, "b " + kEmbedChar + " e", 2, 7],
+ [5, 7, kEmbedChar + " e", 4, 7] ]);
+ testTextAtOffset([ "brInEmbedAndAfter" ], BOUNDARY_LINE_START,
+ [ [0, 1, "a " + kEmbedChar, 0, 3],
+ [2, 2, "a " + kEmbedChar + " d\n", 0, 6],
+ [3, 5, kEmbedChar + " d\n", 2, 6],
+ [6, 7, "e", 6, 7] ]);
+ testTextAtOffset([ "brInEmbedAndBlockElementAfter" ], BOUNDARY_LINE_START,
+ [ [0, 2, "a " + kEmbedChar, 0, 3],
+ [3, 4, kEmbedChar, 3, 4] ]);
+ testTextAtOffset([ "brInEmbedThenTextThenBlockElement" ], BOUNDARY_LINE_START,
+ [ [0, 1, "a " + kEmbedChar, 0, 3],
+ [2, 2, "a " + kEmbedChar + " d", 0, 5],
+ [3, 4, kEmbedChar + " d", 2, 5],
+ [5, 6, kEmbedChar, 5, 6] ]);
+ testTextAtOffset([ "noBrInEmbedButOneBefore" ], BOUNDARY_LINE_START,
+ [ [0, 1, "a\n", 0, 2],
+ [2, 7, "b " + kEmbedChar + " d", 2, 7] ]);
+ testTextAtOffset([ "noBrInEmbedButOneAfter" ], BOUNDARY_LINE_START,
+ [ [0, 3, "a " + kEmbedChar + "\n", 0, 4],
+ [4, 5, "c", 4, 5] ]);
+ testTextAtOffset([ "twoEmbedsWithBRs" ], BOUNDARY_LINE_START,
+ [ [0, 1, "a " + kEmbedChar, 0, 3],
+ [2, 2, "a " + kEmbedChar + kEmbedChar, 0, 4],
+ [3, 3, kEmbedChar + kEmbedChar + " f", 2, 6],
+ [4, 6, kEmbedChar + " f", 3, 6] ]);
+
+ // Inline block span with nested spans and BRs
+ testTextAtOffset([ "inlineBlockWithSpansAndBrs" ], BOUNDARY_LINE_START,
+ [ [0, 1, "a\n", 0, 2],
+ [2, 3, "b\n", 2, 4],
+ [4, 5, "c", 4, 5] ]);
+
+ // Spans with BRs and whitespaces.
+ testTextAtOffset([ "spansWithWhitespaces" ], BOUNDARY_LINE_START,
+ [ [0, 6, "Line 1\n", 0, 7],
+ [7, 13, "Line 2\n", 7, 14],
+ [14, 20, "Line 3\n", 14, 21],
+ [21, 27, "Line 4\n", 21, 28],
+ [28, 28, "", 28, 28] ]);
+
+ // A line with an empty display: contents leaf in the middle.
+ testTextAtOffset([ "displayContents" ], BOUNDARY_LINE_START,
+ // See the kOk test below.
+ // [ [0, 3, `a${kEmbedChar}b`, 0, 3] ]);
+ [ [0, 0, `a${kEmbedChar}b`, 0, 3],
+ [2, 3, `a${kEmbedChar}b`, 0, 3] ]);
+ testTextAtOffset(/* aOffset */ 1, BOUNDARY_LINE_START,
+ `a${kEmbedChar}b`, /* aStartOffset */ 0, /* aEndOffset */ 3,
+ "displayContents",
+ /* returned text */ kOk,
+ /* returned start offset */ kOk,
+ /* returned end offset */ kOk);
+
+ // A line which wraps, followed by a br, followed by another line.
+ testTextAtOffset([ "brAfterWrapped" ], BOUNDARY_LINE_START,
+ [ [0, 1, "a ", 0, 2],
+ [2, 3, "b\n", 2, 4],
+ [4, 5, "c", 4, 5] ]);
+
+ testTextAtOffset([ "inlineInput" ], BOUNDARY_LINE_END,
+ [ [0, 1, "a", 0, 1],
+ [2, 7, `\nb ${kEmbedChar} d`, 1, 7,
+ [ [ 4, "inlineInput", kOk, kOk, kOk] ] ] ]);
+
+ testTextAtOffset([ "inlineInput2" ], BOUNDARY_LINE_END,
+ [ [0, 1, "a", 0, 1],
+ [2, 7, `\n${kEmbedChar} c d`, 1, 7,
+ [ [ 2, "inlineInput2", kOk, kOk, kOk] ] ] ]);
+
+ testTextAtOffset([ "inlineInput3" ], BOUNDARY_LINE_END,
+ [ [0, 1, "a", 0, 1],
+ [2, 8, `\nb${kEmbedChar} c d`, 1, 8,
+ [ [ 3, "inlineInput3", kOk, kOk, kOk] ] ] ]);
+
+ testTextAtOffset([ "inlineInput4" ], BOUNDARY_LINE_END,
+ [ [0, 1, "a", 0, 1],
+ [2, 7, `\n${kEmbedChar}b c d`, 1, 8,
+ [ [ 2, "inlineInput4", kOk, kOk, kOk ] ] ] ]);
+
+ testTextAtOffset(/* aOffset */ 0, BOUNDARY_LINE_START,
+ kEmbedChar, 0, 1, "contentEditableTable",
+ /* returned text */ kOk,
+ /* returned start offset */ kOk,
+ /* returned end offset */ kOk);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="getTextAtOffset for word boundaries: beginning of a new life"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=853340">
+ Bug 853340
+ </a>
+ <a target="_blank"
+ title="getTextBeforeOffset for word boundaries: evolving"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=855732">
+ Bug 855732
+ </a>
+ <a target="_blank"
+ title=" getTextAfterOffset for line boundary on new rails"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=882292">
+ Bug 882292
+ </a>
+ <a target="_blank"
+ title="getTextAtOffset broken for last object when closing tag is preceded by newline char"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=947170">
+ Bug 947170
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input" value="hello my friend"/>
+ <div id="div">hello my friend</div>
+ <div id="editable" contenteditable="true">hello my friend</div>
+ <textarea id="textarea">hello my friend</textarea>
+ <iframe id="ta_cntr"
+ src="data:text/html,<html><body><textarea id='ta'>hello my friend</textarea></body></html>"></iframe>
+
+ <pre>
+ <div id="ml_div" style="border-style:outset;">oneword
+
+two words
+</div>
+ <div id="ml_divbr" style="border-style:outset;">oneword<br/><br/>two words<br/></div>
+ <div id="ml_editable" style="border-style:outset;" contenteditable="true">oneword
+
+two words
+</div>
+ <div id="ml_editablebr" contenteditable="true" style="border-style:outset;">oneword<br/><br/>two words<br/></div>
+ <textarea id="ml_textarea" cols="300">oneword
+
+two words
+</textarea>
+ </pre>
+
+ <iframe id="ht_1" src="data:text/html,<html><body>a <a href=''>b</a> c</body></html>"></iframe>
+
+ <iframe id="ht_2" src="data:text/html,<div contentEditable='true'>foo<br/></div>"></iframe>
+ <iframe id="ht_3" src="data:text/html,<div contentEditable='true'>foo<br/><br/></div>"></iframe>
+
+ <p id="ht_4">Hello world
+</p>
+
+ <ul id="ul1">
+ <li id="li1">Item</li>
+ <li id="li2"></li>
+ <li id="li3" style="font-family:monospace; font-size:10pt; width:8ch;">a long and winding road that lead me to your door</li>
+ <li id="li4">a <a href=''>b</a> c</li>
+ <li id="li5"><br>hello</li>
+ </ul>
+
+ <ol id="ol1">
+ <li id="li6">Item</li>
+ <li id="li7"></li>
+ <li id="li8" style="font-family:monospace; font-size:10pt; width:8ch;">a long and winding road that lead me to your door</li>
+ <li id="li9">a <a href=''>b</a> c</li>
+ <li id="li10"><br>hello</li>
+ </ol>
+
+ <div id="ht_5">
+ <div>
+ <p>sectiounus</p>
+ <p>seciofarus</p>
+ </div>
+ </div>
+ <div id="line_test_1">
+ Line 1
+ <center><input type="TEXT"><input value="Button" type="SUBMIT"></center>
+ Line 3
+ </div>
+
+ <div id="block_then_ul">
+ <p>Block</p>
+ <ul><li>Li</li></ul>
+ </div>
+ <div id="brInEmbed" contenteditable>a <a href="https://mozilla.org/">b<br>c</a> d</div>
+ <div id="brInEmbedAndBefore">a<br>b <a href="https://mozilla.org/">c<br>d</a> e</div>
+ <div id="brInEmbedAndAfter">a <a href="https://mozilla.org/">b<br>c</a> d<br>e</div>
+ <div id="brInEmbedAndBlockElementAfter">a <a href="https://mozilla.org/">b<br>c</a><p>d</p></div>
+ <div id="brInEmbedThenTextThenBlockElement">a <a href="https://mozilla.org/">b<br>c</a> d<p>e</p></div>
+ <div id="noBrInEmbedButOneBefore">a<br>b <a href="https://mozilla.org/">c</a> d</div>
+ <div id="noBrInEmbedButOneAfter">a <a href="https://mozilla.org/">b</a><br>c</div>
+ <div id="twoEmbedsWithBRs">a <a href="https://mozilla.org">b<br>c</a><a href="https://mozilla.org">d<br>e</a> f</div>
+ <span id="inlineBlockWithSpansAndBrs" style="display: inline-block;"><span>a<br>b<br><span></span></span>c</span>
+ <div id="spansWithWhitespaces"> <!-- Don't indent the following block -->
+<span>Line 1<br/>
+</span>
+<span>Line 2<br/>
+</span>
+<span>Line 3<br/>
+</span>
+<span>Line 4<br/>
+</span></div><!-- OK to indent again -->
+ <div id="displayContents">a<ul style="display: contents;"><li style="display: contents;"></li></ul>b</div>
+ <div id="brAfterWrapped" style="width: 10px;">a b<br>c</div>
+ <div id="inlineInput">a<br>b <input value="c"> d</div>
+ <div id="inlineInput2">a<br><input value="b"> c d</div>
+ <div id="inlineInput3">a<br> b<input value=""> c d</div>
+ <div id="inlineInput4">a<br><input value="">b c d</div>
+ <div id="contentEditableTable" contenteditable>
+ <table style="display: inline-table">
+ <thead>
+ <th>Foo</th>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Bar</td>
+ </tr>
+ </tbody>
+ </table>
+ <br>
+ <table style="display: inline-table">
+ <thead>
+ <th>Foo</th>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Bar</td>
+ </tr>
+ </tbody>
+ </table>
+ <br>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_paragraphboundary.html b/accessible/tests/mochitest/text/test_paragraphboundary.html
new file mode 100644
index 0000000000..8b270b661c
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_paragraphboundary.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Paragraph boundary getText* functions tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript">
+ function doTest() {
+ // First, test the contentEditable.
+ testTextAtOffset("ce", BOUNDARY_PARAGRAPH,
+ [[0, 0, kEmbedChar, 0, 1],
+ [1, 2, kEmbedChar, 1, 2]]);
+
+ // Now, test each paragraph.
+ var ID = getNode("ce").firstElementChild;
+ testTextAtOffset(ID, BOUNDARY_PARAGRAPH,
+ [[0, 15, "hello my friend", 0, 15]]);
+ ID = getNode("ce").lastElementChild;
+ testTextAtOffset(ID, BOUNDARY_PARAGRAPH,
+ [[0, 11, "hello again", 0, 11]]);
+
+ // Test a paragraph whose line forcefully wraps.
+ testTextAtOffset("forced_wrap", BOUNDARY_PARAGRAPH,
+ [[0, 2, "ab", 0, 2]]);
+
+ // Test paragraphs with a few line breaks.
+ testTextAtOffset("forced_br", BOUNDARY_PARAGRAPH,
+ [[0, 1, "a\n", 0, 2], // a and br treated as a paragraph
+ [2, 3, "b\n", 2, 4], // b treated as a paragraph, excl 2nd line break
+ [4, 4, "\n", 4, 5], // second br treated as a separate paragraph
+ [5, 6, "c", 5, 6]]); // Last paragraph treated as usual
+ testTextAtOffset("br_at_beginning", BOUNDARY_PARAGRAPH,
+ [[0, 0, "\n", 0, 1], // br treated as a separate paragraph
+ [1, 2, "a\n", 1, 3], // a and br treated as a paragraph
+ [3, 4, "b", 3, 4]]); // b treated as last paragraph
+
+ // Test a paragraph with an embedded link.
+ testTextAtOffset("pWithLink", BOUNDARY_PARAGRAPH,
+ [[0, 3, "a" + kEmbedChar + "d", 0, 3]]);
+ testTextAtOffset("link", BOUNDARY_PARAGRAPH,
+ [[0, 2, "bc", 0, 2]]);
+
+ // Paragraph with link that contains a line break.
+ testTextAtOffset("pWithLinkWithBr", BOUNDARY_PARAGRAPH,
+ [[0, 0, "a" + kEmbedChar, 0, 2],
+ [1, 1, "a" + kEmbedChar + "d", 0, 3],
+ [2, 3, kEmbedChar + "d", 1, 3]]);
+
+ // Test a list and list item
+ testTextAtOffset("ul", BOUNDARY_PARAGRAPH,
+ [[0, 0, kEmbedChar, 0, 1],
+ [1, 2, kEmbedChar, 1, 2]]);
+ testTextAtOffset("li1", BOUNDARY_PARAGRAPH,
+ [[0, 3, "• a", 0, 3]]);
+ testTextAtOffset("li2", BOUNDARY_PARAGRAPH,
+ [[0, 3, "• a", 0, 3]]);
+ // Test a list item containing multiple text leaf nodes.
+ testTextAtOffset("liMultiLeaf", BOUNDARY_PARAGRAPH,
+ [[0, 4, "• ab", 0, 4]]);
+
+ // Test line breaks in a textarea.
+ testTextAtOffset("textarea", BOUNDARY_PARAGRAPH,
+ [[0, 1, "a\n", 0, 2],
+ [2, 3, "b\n", 2, 4],
+ [4, 4, "\n", 4, 5],
+ [5, 6, "c", 5, 6]]);
+
+ // Test that a textarea has a blank paragraph at the end if it contains
+ // a line break as its last character.
+ testTextAtOffset("textarea_with_trailing_br", BOUNDARY_PARAGRAPH,
+ [[0, 15, "This is a test.\n", 0, 16],
+ [16, 16, "", 16, 16]]);
+
+ // Paragraph with a presentational line break.
+ testTextAtOffset("presentational_br", BOUNDARY_PARAGRAPH,
+ [[0, 3, "a b", 0, 3]]);
+
+ // Two paragraphs in a div, non-editable case.
+ testTextAtOffset("two_paragraphs", BOUNDARY_PARAGRAPH,
+ [[0, 0, kEmbedChar, 0, 1],
+ [1, 2, kEmbedChar, 1, 2]]);
+
+ // Div containing a paragraph containing a link
+ testTextAtOffset("divWithParaWithLink", BOUNDARY_PARAGRAPH,
+ [[0, 0, kEmbedChar, 0, 1],
+ [1, 2, "b", 1, 2]]);
+
+ // Two text nodes and a br
+ testTextAtOffset("twoTextNodesAndBr", BOUNDARY_PARAGRAPH,
+ [[0, 2, "ab\n", 0, 3],
+ [3, 3, "", 3, 3]]);
+
+ // Link followed by a paragraph.
+ testTextAtOffset("linkThenPara", BOUNDARY_PARAGRAPH,
+ [[0, 0, kEmbedChar, 0, 1],
+ [1, 2, kEmbedChar, 1, 2]]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="getTextAtOffset for paragraph boundaries"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1520779">
+ Bug 1520779
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="ce" contenteditable="true">
+ <p>hello my friend</p>
+ <p>hello again</p>
+ </div>
+ <p id="forced_wrap" style="width: 1px; word-break: break-all;">ab</p>
+ <p id="forced_br">a<br>b<br><br>c</p>
+ <p id="br_at_beginning"><br>a<br>b</p>
+ <p id="pWithLink">a<a id="link" href="https://example.com/">bc</a>d</p>
+ <p id="pWithLinkWithBr">a<a href="#">b<br>c</a>d</p>
+ <ul id="ul"><li id="li1">a</li><li>b</li></ul>
+ <style>#li2::marker { content:'\2022\0020'; }</style>
+ <ul id="ul"><li id="li2">a</li><li>b</li></ul>
+ <ul><li id="liMultiLeaf">a<span>b</span></li></ul>
+ <textarea id="textarea">a
+b
+
+c</textarea> <!-- This must be outdented for a correct test case -->
+ <textarea id="textarea_with_trailing_br">This is a test.
+</textarea> <!-- This must be outdented for a correct test case -->
+ <p id="presentational_br" style="white-space: pre-wrap;">a<span> <br role="presentation"></span>b</p>
+ <div id="two_paragraphs"><p>a</p><p>b</p></div>
+ <div id ="divWithParaWithLink"><p><a href="#">a</a></p>b</div>
+ <p id="twoTextNodesAndBr">a<span>b</span><br></p>
+ <div id="linkThenPara"><a href="#">a</a><p>b</p></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_passwords.html b/accessible/tests/mochitest/text/test_passwords.html
new file mode 100644
index 0000000000..fc184f2d45
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_passwords.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessibleText getText related function tests for text and password inputs</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // regular text and password inputs
+ // ////////////////////////////////////////////////////////////////////////
+
+ // //////////////////////////////////////////////////////////////////////
+ // characterCount and getText for regular text field
+
+ var IDs = [ "username" ];
+ testCharacterCount(IDs, 4);
+ testText(IDs, 0, 4, "test");
+
+ // //////////////////////////////////////////////////////////////////////
+ // characterCount and getText for password field
+
+ IDs = [ "password" ];
+ testCharacterCount(IDs, 4);
+ let password = document.getElementById("password");
+ let editor = SpecialPowers.wrap(password).editor;
+ let passwordMask = editor.passwordMask;
+ testText(IDs, 0, 4, `${passwordMask}${passwordMask}${passwordMask}${passwordMask}`);
+ // a11y data is updated at next tick so that we need to refresh here.
+ editor.unmask(0, 2);
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0);
+ testText(IDs, 0, 4, `te${passwordMask}${passwordMask}`);
+ editor.unmask(2, 4);
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0);
+ testText(IDs, 0, 4, `${passwordMask}${passwordMask}st`);
+ editor.unmask(0, 4);
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0);
+ testText(IDs, 0, 4, `test`);
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="mochitest for getText for password fields"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=415943">Mozilla Bug 415943</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <form action="post.php" method="post">
+ <label for="username">User name:</label>
+ <input id="username" value="test"><br />
+ <label for="password">Password:</label>
+ <input type="password" id="password" value="test"/>
+ </form>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_selection.html b/accessible/tests/mochitest/text/test_selection.html
new file mode 100644
index 0000000000..ce83e57086
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_selection.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test text selection functions</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // Test selection count: clean selection / check count.
+ testTextAddSelection("div0", 0, 2, 1); // |Test selection...
+ cleanTextSelections("div0");
+ testTextSelectionCount("div0", 0);
+
+ // Test addition: adding two equal selections, the second one should
+ // not be added.
+ testTextAddSelection("div1", 7, 9, 1); // Test ad|di|ng two...
+ testTextAddSelection("div1", 7, 9, 1); // Test ad|di|ng two...
+ testTextGetSelection("div1", 7, 9, 0);
+
+ // Test overlapping selections: adding three selections, one adjacent.
+ testTextAddSelection("div2", 0, 3, 1); // |Tes|t adding 3...
+ testTextAddSelection("div2", 7, 9, 2); // |Tes|t ad|di|ng 3...
+ testTextAddSelection("div2", 3, 4, 3); // |Tes||t| ad|di|ng 3...
+ testTextGetSelection("div2", 0, 3, 0);
+ testTextGetSelection("div2", 3, 4, 1);
+ testTextGetSelection("div2", 7, 9, 2);
+
+ // Test selection re-ordering: adding two selections.
+ // NOTE: removeSelections aSelectionIndex is from start of document.
+ testTextAddSelection("div3", 0, 3, 1); // |Tes|t adding 2...
+ testTextAddSelection("div3", 7, 9, 2); // |Tes|t ad|di|ng 2...
+ testTextRemoveSelection("div3", 4, 1); // Test ad|di|ng 2...
+
+ // Test extending existing selection.
+ // NOTE: setSelectionBounds aSelectionIndex is from start of document.
+ testTextAddSelection("div4", 4, 5, 1); // Test| |extending...
+ testTextSetSelection("div4", 4, 9, 6, 1); // Test| exte|nding...
+
+ // Test moving an existing selection.
+ // NOTE: setSelectionBounds aSelectionIndex is from start of document.
+ testTextAddSelection("div5", 1, 3, 1); // T|es|t moving...
+ testTextSetSelection("div5", 5, 9, 6, 1); // Test |movi|ng...
+
+ // Test adding selections to multiple inner elements.
+ testTextAddSelection("div71", 0, 3, 1); // |Tes|t adding...
+ testTextAddSelection("div71", 7, 8, 2); // |Tes|t ad|d|ing...
+ testTextAddSelection("div72", 4, 6, 1); // Test| a|dding...
+ testTextAddSelection("div72", 7, 8, 2); // Test| a|d|d|ing...
+
+ // Test adding selection to parent element.
+ // NOTE: If inner elements are represented as embedded chars
+ // we count their internal selections.
+ testTextAddSelection("div7", 7, 8, 5); // Test ad|d|ing...
+
+ // Test attempt to selected generated content.
+ // range's start is clipped to end of generated content.
+ testTextAddSelection("div8", 1, 8, 1);
+ testTextGetSelection("div8", 6, 8, 0);
+ // range's end is expanded to end of container hypertext.
+ testTextAddSelection("div8", 10, 15, 2);
+ testTextGetSelection("div8", 10, 23, 1);
+
+ testTextAddSelection("li", 0, 8, 1);
+ testTextGetSelection("li", 3, 8, 0);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+
+</script>
+</head>
+
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="div0">Test selection count</div>
+ </br>
+ <div id="div1">Test adding two equal selections </div>
+ <div id="div2">Test adding 3 selections one adjacent </div>
+ <div id="div3">Test adding 2 selections, remove first one </div>
+ <div id="div4">Test extending a selection </div>
+ <div id="div5">Test moving a selection </div>
+ </br>
+ <div id="div7">Test adding selections to parent element
+ <div id="div71">Test adding selections to inner element1 </div>
+ <div id="div72">Test adding selections to inner element2 </div>
+ </div>
+ <style>
+ #div8:before {
+ content: 'hello ';
+ }
+ #div8:after {
+ content: ', i love you';
+ }
+ </style>
+ <div id="div8">world</div>
+ <ol>
+ <li id="li">Number one</li>
+ </ol>
+
+</body>
+
+</html>
diff --git a/accessible/tests/mochitest/text/test_settext_input_event.html b/accessible/tests/mochitest/text/test_settext_input_event.html
new file mode 100644
index 0000000000..2f0ecacf30
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_settext_input_event.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test that setTextContents only sends one DOM input event</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript">
+ async function doTest() {
+ let input = getAccessible("input", [nsIAccessibleEditableText]);
+ let eventPromise = new Promise(resolve =>
+ document.getElementById("input").addEventListener(
+ "input", resolve, { once: true }));
+
+ input.setTextContents("goodbye");
+ let inputEvent = await eventPromise;
+ is(inputEvent.target.value, "goodbye", "input set to new value.");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="HyperTextAccessible::ReplaceText causes two distinct DOM 'input' events"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1490840">Mozilla Bug 1490840</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <input id="input" value="hello">
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_textBounds.html b/accessible/tests/mochitest/text/test_textBounds.html
new file mode 100644
index 0000000000..66e7f1a93f
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_textBounds.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>TextBounds tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // Returned rect should be all 0 if no frame; e.g. display: contents.
+ testTextBounds(
+ "displayContents", 0, 0, [0, 0, 0, 0], COORDTYPE_SCREEN_RELATIVE
+ );
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <section id="displayContents" style="display: contents;"></section>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_wordboundary.html b/accessible/tests/mochitest/text/test_wordboundary.html
new file mode 100644
index 0000000000..49d4d95561
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_wordboundary.html
@@ -0,0 +1,361 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Word boundary text tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // "hello"
+ // __h__e__l__l__o__
+ // 0 1 2 3 4 5
+ var ids = [ "i1", "d1", "e1", "t1" ];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "", 0, 0 ] ]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 0, 0 ] ]);
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "hello", 0, 5 ] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "hello", 0, 5 ] ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "", 5, 5 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 5, 5 ] ]);
+
+ // "hello "
+ // __h__e__l__l__o__ __
+ // 0 1 2 3 4 5 6
+ ids = [ "i2", "d2", "p2", "e2", "t2" ];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 6, "", 0, 0 ] ]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 6, "hello", 0, 5 ],
+ ]);
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 6, "hello ", 0, 6 ] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 4, "hello", 0, 5 ],
+ [ 5, 6, " ", 5, 6 ],
+ ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 6, "", 6, 6 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, " ", 5, 6 ],
+ [ 6, 6, "", 6, 6 ],
+ ]);
+
+ // "hello all"
+ // __h__e__l__l__o__ __a__l__l__
+ // 0 1 2 3 4 5 6 7 8 9
+ ids = [ "i6", "d6", "e6", "t6" ];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 9, "hello ", 0, 6 ]]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 9, "hello", 0, 5 ] ]);
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "hello ", 0, 6 ],
+ [ 6, 9, "all", 6, 9 ] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 4, "hello", 0, 5 ],
+ [ 5, 9, " all", 5, 9 ] ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "all", 6, 9 ],
+ [ 6, 9, "", 9, 9 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, " all", 5, 9 ],
+ [ 6, 9, "", 9, 9 ] ]);
+
+ // " hello all " (with whitespace collapsing)
+ // __h__e__l__l__o__ __a__l__l__ __
+ // 0 1 2 3 4 5 6 7 8 9 10
+ ids = [ "d6a", "e6a" ];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 10, "hello ", 0, 6 ] ]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 9, "hello", 0, 5 ],
+ [ 10, 10, " all", 5, 9 ] ]);
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "hello ", 0, 6 ],
+ [ 6, 10, "all ", 6, 10 ] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 4, "hello", 0, 5 ],
+ [ 5, 8, " all", 5, 9 ],
+ [ 9, 10, " ", 9, 10 ] ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "all ", 6, 10 ],
+ [ 6, 10, "", 10, 10 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, " all", 5, 9 ],
+ [ 6, 9, " ", 9, 10 ],
+ [ 10, 10, "", 10, 10 ] ]);
+
+ // "hello my friend"
+ // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ids = [ "i7", "d7", "e7", "t7", "w7" ];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 8, "hello ", 0, 6 ],
+ [ 9, 15, "my ", 6, 9 ] ]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 8, "hello", 0, 5 ],
+ [ 9, 15, " my", 5, 8 ] ]);
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "hello ", 0, 6 ],
+ [ 6, 8, "my ", 6, 9 ],
+ [ 9, 15, "friend", 9, 15] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 4, "hello", 0, 5 ],
+ [ 5, 7, " my", 5, 8 ],
+ [ 8, 15, " friend", 8, 15] ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "my ", 6, 9 ],
+ [ 6, 8, "friend", 9, 15 ],
+ [ 9, 15, "", 15, 15 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, " my", 5, 8 ],
+ [ 6, 8, " friend", 8, 15 ],
+ [ 9, 15, "", 15, 15 ] ]);
+
+ // "Brave Sir Robin ran"
+ // __B__r__a__v__e__ __S__i__r__ __ __R__o__b__i__n__ __ __ __r__a__n__
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
+ ids = [ "i8", "d8", "e8", "t8" ];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 10, "Brave ", 0, 6 ],
+ [ 11, 18, "Sir ", 6, 11 ],
+ [ 19, 22, "Robin ", 11, 19 ] ]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 9, "Brave", 0, 5 ],
+ [ 10, 16, " Sir", 5, 9 ],
+ [ 17, 22, " Robin", 9, 16 ] ]);
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "Brave ", 0, 6 ],
+ [ 6, 10, "Sir ", 6, 11 ],
+ [ 11, 18, "Robin ", 11, 19 ],
+ [ 19, 22, "ran", 19, 22 ] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 4, "Brave", 0, 5 ],
+ [ 5, 8, " Sir", 5, 9 ],
+ [ 9, 15, " Robin", 9, 16 ],
+ [ 16, 22, " ran", 16, 22 ] ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "Sir ", 6, 11 ],
+ [ 6, 10, "Robin ", 11, 19 ],
+ [ 11, 18, "ran", 19, 22 ],
+ [ 19, 22, "", 22, 22 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, " Sir", 5, 9 ],
+ [ 6, 9, " Robin", 9, 16 ],
+ [ 10, 16, " ran", 16, 22 ],
+ [ 17, 22, "", 22, 22 ] ]);
+
+ // 'oneword
+ // '
+ // 'two words
+ // '
+ // __o__n__e__w__o__r__d__\n
+ // 0 1 2 3 4 5 6 7
+ // __\n
+ // 8
+ // __t__w__o__ __w__o__r__d__s__\n__
+ // 9 10 11 12 13 14 15 16 17 18 19
+
+ ids = ["ml_div1", "ml_divbr1", "ml_ediv1", "ml_edivbr1", "ml_t1"];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 8, "", 0, 0 ],
+ [ 9, 12, "oneword\n\n", 0, 9 ],
+ [ 13, 19, "two ", 9, 13 ] ]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 7, "", 0, 0 ],
+ [ 8, 12, "oneword", 0, 7,
+ [ [ 8, "ml_divbr1", kOk, kOk, kOk ],
+ [ 8, "ml_edivbr1", kOk, kOk, kOk ],
+ [ 9, "ml_divbr1", kOk, kOk, kOk ],
+ [ 9, "ml_edivbr1", kOk, kOk, kOk ] ] ],
+ [ 13, 18, "\n\ntwo", 7, 12 ],
+ [ 19, 19, " words", 12, 18,
+ [ [ 19, "ml_divbr1", kOk, kOk, kOk ],
+ [ 19, "ml_edivbr1", kOk, kOk, kOk ] ] ],
+ ] );
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 8, "oneword\n\n", 0, 9 ],
+ [ 9, 12, "two ", 9, 13 ],
+ [ 13, 19, "words\n", 13, 19 ] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 6, "oneword", 0, 7 ],
+ [ 7, 11, "\n\ntwo", 7, 12 ],
+ [ 12, 17, " words", 12, 18 ],
+ [ 18, 19, "\n", 18, 19,
+ [ [ 18, "ml_divbr1", kOk, kOk, kOk ],
+ [ 18, "ml_edivbr1", kOk, kOk, kOk ],
+ [ 19, "ml_divbr1", kOk, kOk, kOk ],
+ [ 19, "ml_edivbr1", kOk, kOk, kOk ] ] ] ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 8, "two ", 9, 13 ],
+ [ 9, 12, "words\n", 13, 19 ],
+ [ 13, 19, "", 19, 19 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 7, "\n\ntwo", 7, 12 ],
+ [ 8, 12, " words", 12, 18 ],
+ [ 13, 18, "\n", 18, 19,
+ [ [ 18, "ml_divbr1", kOk, kOk, kOk ],
+ [ 18, "ml_edivbr1", kOk, kOk, kOk ] ] ],
+ [ 19, 19, "", 19, 19 ] ]);
+
+ // a <a href="#">b</a>
+ // a *
+ testTextBeforeOffset("cntr_1", BOUNDARY_WORD_START,
+ [ [ 0, 1, "", 0, 0 ],
+ [ 2, 3, "a ", 0, 2 ] ]);
+
+ testTextAtOffset("cntr_1", BOUNDARY_WORD_START,
+ [ [ 0, 1, "a ", 0, 2 ],
+ [ 2, 3, kEmbedChar, 2, 3 ] ]);
+ testTextAfterOffset("cntr_1", BOUNDARY_WORD_START,
+ [ [ 0, 1, kEmbedChar, 2, 3 ],
+ [ 2, 3, "", 3, 3 ] ]);
+
+ // Punctuation tests.
+ testTextAtOffset("punc_alone", BOUNDARY_WORD_START, [
+ [ 0, 1, "a ", 0, 2 ],
+ [ 2, 4, "@@ ", 2, 5 ],
+ [ 5, 6, "b", 5, 6 ]
+ ]);
+ testTextAtOffset("punc_begin", BOUNDARY_WORD_START, [
+ [ 0, 1, "a ", 0, 2 ],
+ [ 2, 5, "@@b ", 2, 6 ],
+ [ 6, 7, "c", 6, 7 ]
+ ]);
+ testTextAtOffset("punc_end", BOUNDARY_WORD_START, [
+ [ 0, 1, "a ", 0, 2 ],
+ [ 2, 5, "b@@ ", 2, 6 ],
+ [ 6, 7, "c", 6, 7 ]
+ ]);
+ testTextAtOffset("punc_middle", BOUNDARY_WORD_START, [
+ [ 0, 1, "a ", 0, 2 ],
+ [ 2, 4, "b@@", 2, 5 ],
+ [ 5, 6, "c ", 5, 7 ],
+ [ 7, 8, "d", 7, 8 ]
+ ]);
+ testTextAtOffset("punc_everywhere", BOUNDARY_WORD_START, [
+ [ 0, 1, "a ", 0, 2 ],
+ [ 2, 6, "@@b@@", 2, 7 ],
+ [ 7, 10, "c@@ ", 7, 11 ],
+ [ 11, 12, "d", 11, 12 ]
+ ]);
+
+ // Multi-word embedded object test.
+ testTextAtOffset("multiword_embed", BOUNDARY_WORD_START, [
+ [ 0, 1, "a ", 0, 2 ],
+ [ 2, 3, `${kEmbedChar} `, 2, 4, [
+ // Word at offset 2 returns end offset 3, should be 4.
+ [ 2, "multiword_embed", kOk, kOk, kOk ]
+ ] ],
+ [ 4, 5, "b", 4, 5 ]
+ ]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="i1" value="hello"/>
+ <div id="d1">hello</div>
+ <div id="e1" contenteditable="true">hello</div>
+ <textarea id="t1">hello</textarea>
+
+ <input id="i2" value="hello "/>
+ <div id="d2"> hello </div>
+ <pre><div id="p2">hello </div></pre>
+ <div id="e2" contenteditable="true" style='white-space:pre'>hello </div>
+ <textarea id="t2">hello </textarea>
+
+ <input id="i6" value="hello all"/>
+ <div id="d6"> hello all</div>
+ <div id="e6" contenteditable="true">hello all</div>
+ <textarea id="t6">hello all</textarea>
+
+ <div id="d6a"> hello all </div>
+ <div id="e6a" contenteditable="true"> hello all </div>
+
+ <input id="i7" value="hello my friend"/>
+ <div id="d7"> hello my friend</div>
+ <div id="e7" contenteditable="true">hello my friend</div>
+ <textarea id="t7">hello my friend</textarea>
+ <div id="w7" style="width:1em"> hello my friend</div>
+
+ <input id="i8" value="Brave Sir Robin ran"/>
+ <pre>
+ <div id="d8">Brave Sir Robin ran</div>
+ <div id="e8" contenteditable="true">Brave Sir Robin ran</div>
+ </pre>
+ <textarea id="t8" cols="300">Brave Sir Robin ran</textarea>
+
+ <pre>
+<div id="ml_div1">oneword
+
+two words
+</div>
+<div id="ml_divbr1">oneword<br/><br/>two words<br/></div>
+<div id="ml_ediv1" contenteditable="true">oneword
+
+two words
+</div>
+<div id="ml_edivbr1" contenteditable="true">oneword<br/><br/>two words<br/></div>
+<textarea id="ml_t1" cols="300">oneword
+
+two words
+</textarea>
+ </pre>
+
+ <div id="cntr_1">a <a href="#">b</a></div>
+
+ <p id="punc_alone">a @@ b</p>
+ <p id="punc_begin">a @@b c</p>
+ <p id="punc_end">a b@@ c</p>
+ <p id="punc_middle">a b@@c d</p>
+ <p id="punc_everywhere">a @@b@@c@@ d</p>
+
+ <p id="multiword_embed">a <a href="#">x y</a> b</p>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_words.html b/accessible/tests/mochitest/text/test_words.html
new file mode 100644
index 0000000000..7e4fba9154
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_words.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessibleText getText related function tests for html:input,html:div and html:textarea</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript">
+ if (navigator.platform.startsWith("Mac")) {
+ SimpleTest.expectAssertions(0, 1);
+ } else {
+ SimpleTest.expectAssertions(0, 1);
+ }
+
+ function doTest() {
+ // "one two"
+ testWords("div1", ["one", "two"]);
+
+ // "one two"
+ testWords("div2", ["one", "two"]);
+
+ // "one,two"
+ testWordCount("div3", 2, kOk);
+ testWordAt("div3", 0, "one", kTodo);
+ testWordAt("div3", 1, "two", kOk);
+
+ // "one, two"
+ testWordCount("div4", 2, kOk);
+ testWordAt("div4", 0, "one", kTodo);
+ testWordAt("div4", 1, "two", kOk);
+
+ // "one+two"
+ testWordCount("div5", 2, kOk);
+ testWordAt("div5", 0, "one", kTodo);
+ testWordAt("div5", 1, "two", kOk);
+
+ // "one+two "
+ testWordCount("div6", 2, kOk);
+ testWordAt("div6", 0, "one", kTodo);
+ testWordAt("div6", 1, "two", kOk);
+
+ // "one\ntwo"
+ testWordCount("div7", 2, kOk);
+ testWordAt("div7", 0, "one", kOk);
+ testWordAt("div7", 1, "two", kTodo);
+
+ // "one.two"
+ testWordCount("div8", 2, kOk);
+ testWordAt("div8", 0, "one", kTodo);
+ testWordAt("div8", 1, "two", kOk);
+
+ // "345"
+ testWords("div9", ["345"]);
+
+ // "3a A4"
+ testWords("div10", ["3a", "A4"]);
+
+ // "3.1416"
+ testWords("div11", ["3.1416"], kTodo);
+
+ // "4,261.01"
+ testWords("div12", ["4,261.01"], kTodo);
+
+ // "カタカナ"
+ testWords("div13", ["カタカナ"], kOk);
+
+ // "Peter's car"
+ testWords("div14", ["Peter's", "car"], kTodo);
+
+ // "N.A.T.O."
+ testWords("div15", ["N.A.T.O."], kTodo);
+
+ // "3+4*5=23"
+ testWordCount("div16", 4, kOk);
+ testWordAt("div15", 0, "3", kTodo);
+ testWordAt("div15", 1, "4", kTodo);
+ testWordAt("div15", 2, "5", kTodo);
+ testWordAt("div15", 3, "23", kTodo);
+
+ // "Hello. Friend, are you here?!"
+ testWordCount("div17", 5, kOk);
+ testWordAt("div17", 0, "Hello", kTodo);
+ testWordAt("div17", 1, "Friend", kTodo);
+ testWordAt("div17", 2, "are", kOk);
+ testWordAt("div17", 3, "you", kOk);
+ testWordAt("div17", 4, "here", kTodo);
+
+ testWords("input_1", ["foo", "bar"]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="nsIAccessibleText test word boundaries"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=452769">Mozilla Bug 452769</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ <div id="div1">one two</div>
+ <div id="div2">one two</div>
+ <div id="div3">one,two</div>
+ <div id="div4">one, two</div>
+ <div id="div5">one+two</div>
+ <div id="div6">one+two </div>
+ <div id="div7">one<br/>two</div>
+ <div id="div8">one.two</div>
+ <div id="div9">345</div>
+ <div id="div10">3a A4</div>
+ <div id="div11">3.1416</div>
+ <div id="div12">4,261.01</div>
+ <div id="div13">カタカナ</div>
+ <div id="div14">Peter's car</div>
+ <div id="div15">N.A.T.O.</div>
+ <div id="div16">3+4*5=23</div>
+ <div id="div17">Hello. Friend, are you here?!</div>
+ </pre>
+ <input id="input_1" type="text" value="foo bar" placeholder="something or other">
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textattrs/a11y.ini b/accessible/tests/mochitest/textattrs/a11y.ini
new file mode 100644
index 0000000000..6250e663b4
--- /dev/null
+++ b/accessible/tests/mochitest/textattrs/a11y.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/moz.png
+
+[test_general.html]
+[test_general.xhtml]
+[test_invalid.html]
+[test_mathml.html]
+[test_spelling.html]
+[test_svg.html]
diff --git a/accessible/tests/mochitest/textattrs/test_general.html b/accessible/tests/mochitest/textattrs/test_general.html
new file mode 100644
index 0000000000..02adcf675a
--- /dev/null
+++ b/accessible/tests/mochitest/textattrs/test_general.html
@@ -0,0 +1,823 @@
+<html>
+
+<head>
+ <title>Text attributes tests</title>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ .gencontent:before { content: "*"; }
+ .gencontent:after { content: "*"; }
+ </style>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script src="../common.js"></script>
+ <script src="../attributes.js"></script>
+ <script src="../events.js"></script>
+
+ <script>
+ var gComputedStyle = null;
+
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // area1
+ var ID = "area1";
+ var defAttrs = buildDefaultTextAttrs(ID, "10pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ var attrs = {};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 7);
+
+ attrs = { "font-weight": kBoldFontWeight };
+ testTextAttrs(ID, 7, attrs, defAttrs, 7, 11);
+
+ attrs = {};
+ testTextAttrs(ID, 12, attrs, defAttrs, 11, 18);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area2
+ ID = "area2";
+ defAttrs = buildDefaultTextAttrs(ID, "14pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 7);
+
+ attrs = { "font-weight": kBoldFontWeight };
+ testTextAttrs(ID, 7, attrs, defAttrs, 7, 12);
+
+ var tempElem = getNode(ID).firstChild.nextSibling.firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"font-style": gComputedStyle.fontStyle,
+ "font-weight": kBoldFontWeight };
+ testTextAttrs(ID, 13, attrs, defAttrs, 12, 19);
+
+ attrs = { "font-weight": kBoldFontWeight };
+ testTextAttrs(ID, 20, attrs, defAttrs, 19, 23);
+
+ attrs = {};
+ testTextAttrs(ID, 24, attrs, defAttrs, 23, 30);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area3
+ ID = "area3";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ tempElem = getNode(ID).firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 6);
+
+ tempElem = tempElem.firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 6, attrs, defAttrs, 6, 26);
+
+ tempElem = tempElem.parentNode;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 26, attrs, defAttrs, 26, 27);
+
+ tempElem = tempElem.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"color": gComputedStyle.color,
+ "background-color": gComputedStyle.backgroundColor};
+ testTextAttrs(ID, 27, attrs, defAttrs, 27, 50);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area4
+ ID = "area4";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ tempElem = getNode(ID).firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 16);
+
+ tempElem = tempElem.nextSibling.firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 16, attrs, defAttrs, 16, 33);
+
+ tempElem = tempElem.parentNode;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 34, attrs, defAttrs, 33, 46);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area5: "Green!*!RedNormal"
+ ID = "area5";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ // Green
+ tempElem = getNode(ID).firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 5);
+
+ // br
+ attrs = {};
+ testTextAttrs(ID, 5, attrs, defAttrs, 5, 6);
+
+ // img, embedded accessible, no attributes
+ attrs = {};
+ testTextAttrs(ID, 6, attrs, {}, 6, 7);
+
+ // br
+ attrs = {};
+ testTextAttrs(ID, 7, attrs, defAttrs, 7, 8);
+
+ // Red
+ tempElem = tempElem.nextSibling.nextSibling.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 9, attrs, defAttrs, 8, 11);
+
+ // Normal
+ attrs = {};
+ testTextAttrs(ID, 11, attrs, defAttrs, 11, 18);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area6 (CSS vertical-align property, refer to bug 445938 for details
+ // and sup and sub elements, refer to bug 735645 for details)
+ ID = "area6";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 5);
+
+ // Embedded object (sup) has no attributes but takes up one character.
+ testTextAttrs(ID, 5, {}, {}, 5, 6);
+
+ attrs = {};
+ testTextAttrs(ID, 6, attrs, defAttrs, 6, 20);
+
+ attrs = { "text-position": "super" };
+ testTextAttrs(ID, 20, attrs, defAttrs, 20, 28);
+
+ attrs = {};
+ testTextAttrs(ID, 28, attrs, defAttrs, 28, 32);
+
+ // Embedded object (sub) has no attributes but takes up one character.
+ testTextAttrs(ID, 32, {}, {}, 32, 33);
+
+ attrs = {};
+ testTextAttrs(ID, 33, attrs, defAttrs, 33, 38);
+
+ attrs = { "text-position": "sub" };
+ testTextAttrs(ID, 38, attrs, defAttrs, 38, 47);
+
+ attrs = {};
+ testTextAttrs(ID, 47, attrs, defAttrs, 47, 52);
+
+ attrs = { "text-position": "super" };
+ testTextAttrs(ID, 52, attrs, defAttrs, 52, 67);
+
+ attrs = {};
+ testTextAttrs(ID, 67, attrs, defAttrs, 67, 72);
+
+ attrs = { "text-position": "sub" };
+ testTextAttrs(ID, 72, attrs, defAttrs, 72, 85);
+
+ attrs = {};
+ testTextAttrs(ID, 85, attrs, defAttrs, 85, 90);
+
+ attrs = { "text-position": "super" };
+ testTextAttrs(ID, 90, attrs, defAttrs, 90, 106);
+
+ attrs = {};
+ testTextAttrs(ID, 106, attrs, defAttrs, 106, 111);
+
+ attrs = { "text-position": "sub" };
+ testTextAttrs(ID, 111, attrs, defAttrs, 111, 125);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area7
+ ID = "area7";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ defAttrs.language = "en";
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {"language": "ru"};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 6);
+
+ attrs = {};
+ testTextAttrs(ID, 6, attrs, defAttrs, 6, 7);
+
+ tempElem = getNode(ID).firstChild.nextSibling.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = { "background-color": gComputedStyle.backgroundColor};
+ testTextAttrs(ID, 13, attrs, defAttrs, 7, 20);
+
+ attrs = {};
+ testTextAttrs(ID, 20, attrs, defAttrs, 20, 21);
+
+ attrs = {"language": "de"};
+ testTextAttrs(ID, 21, attrs, defAttrs, 21, 36);
+
+ attrs = {};
+ testTextAttrs(ID, 36, attrs, defAttrs, 36, 44);
+
+ attrs = {};
+ testTextAttrs(ID, 37, attrs, defAttrs, 36, 44);
+
+ tempElem = tempElem.nextSibling.nextSibling.nextSibling.nextSibling.firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 44, attrs, defAttrs, 44, 51);
+
+ tempElem = tempElem.firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"font-weight": kBoldFontWeight,
+ "color": gComputedStyle.color};
+ testTextAttrs(ID, 51, attrs, defAttrs, 51, 55);
+
+ tempElem = tempElem.parentNode;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 55, attrs, defAttrs, 55, 62);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area9, different single style spans in styled paragraph
+ ID = "area9";
+ defAttrs = buildDefaultTextAttrs(ID, "10pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 6);
+
+ attrs = { "font-size": "12pt" };
+ testTextAttrs(ID, 7, attrs, defAttrs, 6, 12);
+
+ attrs = {};
+ testTextAttrs(ID, 13, attrs, defAttrs, 12, 21);
+
+ // Walk to the span with the different background color
+ tempElem = getNode(ID).firstChild.nextSibling.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = { "background-color": gComputedStyle.backgroundColor };
+ testTextAttrs(ID, 22, attrs, defAttrs, 21, 36);
+
+ attrs = {};
+ testTextAttrs(ID, 37, attrs, defAttrs, 36, 44);
+
+ // Walk from the background color span to the one with font-style
+ tempElem = tempElem.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = { "font-style": gComputedStyle.fontStyle };
+ testTextAttrs(ID, 45, attrs, defAttrs, 44, 61);
+
+ attrs = {};
+ testTextAttrs(ID, 62, attrs, defAttrs, 61, 69);
+
+ // Walk from span with font-style to the one with font-family.
+ tempElem = tempElem.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = { "font-family": kMonospaceFontFamily };
+ testTextAttrs(ID, 70, attrs, defAttrs, 69, 83);
+
+ attrs = {};
+ testTextAttrs(ID, 84, attrs, defAttrs, 83, 91);
+
+ attrs = {
+ "text-underline-style": "solid",
+ "text-underline-color": gComputedStyle.color,
+ };
+ testTextAttrs(ID, 92, attrs, defAttrs, 91, 101);
+
+ attrs = {};
+ testTextAttrs(ID, 102, attrs, defAttrs, 101, 109);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": gComputedStyle.color,
+ };
+ testTextAttrs(ID, 110, attrs, defAttrs, 109, 122);
+
+ attrs = {};
+ testTextAttrs(ID, 123, attrs, defAttrs, 122, 130);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": gComputedStyle.color,
+ };
+ testTextAttrs(ID, 131, attrs, defAttrs, 130, 143);
+
+ attrs = {};
+ testTextAttrs(ID, 144, attrs, defAttrs, 143, 151);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": gComputedStyle.color,
+ };
+ testTextAttrs(ID, 152, attrs, defAttrs, 151, 164);
+
+ attrs = {};
+ testTextAttrs(ID, 165, attrs, defAttrs, 164, 172);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area10, different single style spans in non-styled paragraph
+ ID = "area10";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 7);
+
+ attrs = { "font-size": "14pt" };
+ testTextAttrs(ID, 7, attrs, defAttrs, 7, 13);
+
+ attrs = {};
+ testTextAttrs(ID, 13, attrs, defAttrs, 13, 22);
+
+ // Walk to the span with the different background color
+ tempElem = getNode(ID).firstChild.nextSibling.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = { "background-color": gComputedStyle.backgroundColor };
+ testTextAttrs(ID, 23, attrs, defAttrs, 22, 37);
+
+ attrs = {};
+ testTextAttrs(ID, 38, attrs, defAttrs, 37, 45);
+
+ // Walk from the background color span to the one with font-style
+ tempElem = tempElem.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = {"font-style": gComputedStyle.fontStyle};
+ testTextAttrs(ID, 46, attrs, defAttrs, 45, 62);
+
+ attrs = {};
+ testTextAttrs(ID, 63, attrs, defAttrs, 62, 70);
+
+ // Walk from span with font-style to the one with font-family.
+ tempElem = tempElem.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem);
+ attrs = { "font-family": kMonospaceFontFamily };
+ testTextAttrs(ID, 71, attrs, defAttrs, 70, 84);
+
+ attrs = {};
+ testTextAttrs(ID, 85, attrs, defAttrs, 84, 92);
+
+ attrs = {
+ "text-underline-style": "solid",
+ "text-underline-color": gComputedStyle.color,
+ };
+ testTextAttrs(ID, 93, attrs, defAttrs, 92, 102);
+
+ attrs = {};
+ testTextAttrs(ID, 103, attrs, defAttrs, 102, 110);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": gComputedStyle.color,
+ };
+ testTextAttrs(ID, 111, attrs, defAttrs, 110, 123);
+
+ attrs = {};
+ testTextAttrs(ID, 124, attrs, defAttrs, 123, 131);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area11, "font-weight" tests
+ ID = "area11";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt", kBoldFontWeight);
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = { };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 13);
+
+ attrs = { "font-weight": kNormalFontWeight };
+ testTextAttrs(ID, 13, attrs, defAttrs, 13, 20);
+
+ attrs = { };
+ testTextAttrs(ID, 20, attrs, defAttrs, 20, 27);
+
+ attrs = { "font-weight": kNormalFontWeight };
+ testTextAttrs(ID, 27, attrs, defAttrs, 27, 33);
+
+ attrs = { };
+ testTextAttrs(ID, 33, attrs, defAttrs, 33, 51);
+
+ attrs = { "font-weight": kNormalFontWeight };
+ testTextAttrs(ID, 51, attrs, defAttrs, 51, 57);
+
+ attrs = { };
+ testTextAttrs(ID, 57, attrs, defAttrs, 57, 97);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // test out of range offset
+ testTextAttrsWrongOffset("area12", -1);
+ testTextAttrsWrongOffset("area12", 500);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // test zero offset on empty hypertext accessibles
+ ID = "area13";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ attrs = { };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 0);
+
+ ID = "area14";
+ defAttrs = buildDefaultTextAttrs(ID, kInputFontSize,
+ kNormalFontWeight, kInputFontFamily);
+
+ attrs = { };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 0);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area15, embed char tests, "*plain*plain**bold*bold*"
+ ID = "area15";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+
+ // p
+ testTextAttrs(ID, 0, { }, { }, 0, 1);
+ // plain
+ testTextAttrs(ID, 1, { }, defAttrs, 1, 6);
+ // p
+ testTextAttrs(ID, 6, { }, { }, 6, 7);
+ // plain
+ testTextAttrs(ID, 7, { }, defAttrs, 7, 12);
+ // p and img
+ testTextAttrs(ID, 12, { }, { }, 12, 14);
+ // bold
+ attrs = { "font-weight": kBoldFontWeight };
+ testTextAttrs(ID, 14, attrs, defAttrs, 14, 18);
+ // p
+ testTextAttrs(ID, 18, { }, { }, 18, 19);
+ // bold
+ attrs = { "font-weight": kBoldFontWeight };
+ testTextAttrs(ID, 19, attrs, defAttrs, 19, 23);
+ // p
+ testTextAttrs(ID, 23, { }, { }, 23, 24);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area16, "font-family" tests
+ ID = "area16";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = { "font-family": kMonospaceFontFamily };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 4);
+
+ attrs = { };
+ testTextAttrs(ID, 4, attrs, defAttrs, 4, 9);
+
+ attrs = { "font-family": kSerifFontFamily };
+ testTextAttrs(ID, 9, attrs, defAttrs, 9, 13);
+
+ attrs = { };
+ testTextAttrs(ID, 13, attrs, defAttrs, 13, 18);
+
+ attrs = { "font-family": kAbsentFontFamily };
+ testTextAttrs(ID, 18, attrs, defAttrs, 18, 22);
+
+ // bug 1224498 - this fails with 'cursive' fontconfig lookup
+ if (!LINUX) {
+ attrs = { };
+ testTextAttrs(ID, 22, attrs, defAttrs, 22, 27);
+
+ attrs = { "font-family": kCursiveFontFamily };
+ testTextAttrs(ID, 27, attrs, defAttrs, 27, 31);
+
+ attrs = { };
+ testTextAttrs(ID, 31, attrs, defAttrs, 31, 45);
+ }
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area17, "text-decoration" tests
+ ID = "area17";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {
+ "text-underline-style": "solid",
+ "text-underline-color": getSystemColor("CanvasText"),
+ };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 10);
+
+ attrs = {
+ "text-underline-style": "solid",
+ "text-underline-color": "rgb(0, 0, 255)",
+ };
+ testTextAttrs(ID, 10, attrs, defAttrs, 10, 15);
+
+ attrs = {
+ "text-underline-style": "dotted",
+ "text-underline-color": getSystemColor("CanvasText"),
+ };
+ testTextAttrs(ID, 15, attrs, defAttrs, 15, 22);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": getSystemColor("CanvasText"),
+ };
+ testTextAttrs(ID, 22, attrs, defAttrs, 22, 34);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": "rgb(0, 0, 255)",
+ };
+ testTextAttrs(ID, 34, attrs, defAttrs, 34, 39);
+
+ attrs = {
+ "text-line-through-style": "wavy",
+ "text-line-through-color": getSystemColor("CanvasText"),
+ };
+ testTextAttrs(ID, 39, attrs, defAttrs, 39, 44);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area18, "auto-generation text" tests
+ ID = "area18";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {
+ "auto-generated": "true",
+ "font-family": "-moz-bullet-font",
+ };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 2);
+ testTextAttrs(ID, 3, { }, defAttrs, 3, 7);
+ attrs = {
+ "auto-generated": "true",
+ };
+ testTextAttrs(ID, 7, attrs, defAttrs, 7, 8);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area19, "HTML5 mark tag" test
+ // text enclosed in mark tag will have a different background color
+ // However, since bug 982125, it is its own accessible.
+ // Therefore, anything other than the default background color is
+ // unexpected.
+ ID = "area19";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+
+ attrs = {};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 10);
+
+ ID = "area19mark";
+ let defMarkAttrs = buildDefaultTextAttrs(ID, "12pt");
+ attrs = {};
+ testTextAttrs(ID, 0, attrs, defMarkAttrs, 0, 7);
+
+ ID = "area19";
+ attrs = {};
+ testTextAttrs(ID, 11, attrs, defAttrs, 11, 22);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // area20, "aOffset as -1 (Mozilla Bug 789621)" test
+
+ ID = "area20";
+ defAttrs = buildDefaultTextAttrs(ID, "15pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ testTextAttrs(ID, -1, {}, defAttrs, 0, 11);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML sub tag offset test - verify attributes
+ ID = "sub_tag";
+ defAttrs = buildDefaultTextAttrs(ID, "10pt");
+ defAttrs["text-position"] = "sub";
+ testDefaultTextAttrs(ID, defAttrs);
+ testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML sup tag offset test - verify attributes
+ ID = "sup_tag";
+ defAttrs = buildDefaultTextAttrs(ID, "10pt");
+ defAttrs["text-position"] = "super";
+ testDefaultTextAttrs(ID, defAttrs);
+ testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA subscript role - verify text-position attribute
+ ID = "subscript_role";
+ defAttrs = { "text-position": "sub" };
+ testDefaultTextAttrs(ID, defAttrs, true);
+ testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA superscript role - verify text-position attribute
+ ID = "superscript_role";
+ defAttrs = { "text-position": "super" };
+ testDefaultTextAttrs(ID, defAttrs, true);
+ testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // test text-position attributes in various situations
+ ID = "superscript_role_in_div";
+ defAttrs = { "text-position": "super" };
+ testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true);
+
+ ID = "sub_within_superscript_role";
+ defAttrs = { "text-position": "sub" };
+ testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true);
+
+ ID = "sup_within_subscript_role";
+ defAttrs = { "text-position": "super" };
+ testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true);
+
+ ID = "sub_within_sup";
+ defAttrs = { "text-position": "sub" };
+ testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true);
+
+ ID = "sup_within_sub";
+ defAttrs = { "text-position": "super" };
+ testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true);
+
+ ID = "css_sub_within_superscript_role";
+ attrs = { "text-position": "sub" };
+ testTextAttrs(ID, 0, attrs, {}, 0, 11, true);
+
+ ID = "css_super_within_subscript_role";
+ attrs = { "text-position": "super" };
+ testTextAttrs(ID, 0, attrs, {}, 0, 11, true);
+
+ ID = "sub_with_superscript_role";
+ defAttrs = { "text-position": "super" };
+ testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true);
+
+ ID = "sup_with_subscript_role";
+ defAttrs = { "text-position": "sub" };
+ testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body style="font-size: 12pt">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=345759"
+ title="Implement text attributes">
+ Mozilla Bug 345759
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=473569"
+ title="Restrict text-position to allowed values">
+ Mozilla Bug 473569
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=473576"
+ title="font-family text attribute should expose actual font used">
+ Mozilla Bug 473576
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=523304"
+ title="expose text-underline-color and text-line-through-color text attributes">
+ Mozilla Bug 523304
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=735645"
+ title="expose sub and sup elements in text attributes">
+ Mozilla Bug 735645
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=445516"
+ title="Support auto-generated text attribute on bullet lists">
+ Mozilla Bug 445516
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=789621"
+ title="getTextAttributes doesn't work with magic offsets">
+ Mozilla Bug 789621
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="area1" style="font-size: smaller">Normal <b>Bold</b> Normal</p>
+ <p id="area2" style="font-size: 120%">Normal <b>Bold <i>Italic </i>Bold</b> Normal</p>
+ <p id="area3" style="background-color: blue;">
+ <span style="color: green; background-color: rgb(0, 0, 255)">
+ Green
+ <span style="color: red">but children are red</span>
+ </span><span style="color: green; background-color: rgb(255, 255, 0);">
+ Another green section.
+ </span>
+ </p>
+ <p id="area4">
+ <span style="color: green">
+ Green
+ </span><span style="color: green">
+ Green too
+ <span style="color: red">with red children</span>
+ Green again
+ </span>
+ </p>
+ <!-- Green!*!RedNormal-->
+ <p id="area5">
+ <span style="color: green">Green</span>
+ <img src="../moz.png" alt="image"/>
+ <span style="color: red">Red</span>Normal
+ </p>
+ <p id="area6">
+ This <sup>sentence</sup> has the word
+ <span style="vertical-align:super;">sentence</span> in
+ <sub>superscript</sub> and
+ <span style="vertical-align:sub;">subscript</span> and
+ <span style="vertical-align:20%;">superscript 20%</span> and
+ <span style="vertical-align:-20%;">subscript 20%</span> and
+ <span style="vertical-align:20px;">superscript 20px</span> and
+ <span style="vertical-align:-20px;">subscript 20px</span>
+ </p>
+
+ <p lang="en" id="area7">
+ <span lang="ru">Привет</span>
+ <span style="background-color: blue">Blue BG color</span>
+ <span lang="de">Ich bin/Du bist</span>
+ <span lang="en">
+ Normal
+ <span style="color: magenta">Magenta<b>Bold</b>Magenta</span>
+ </span>
+ </p>
+
+ <p id="area9" style="font-size: smaller">Small
+ <span style="font-size: 120%">bigger</span> smaller
+ <span style="background-color: blue;">background blue</span> normal
+ <span style="font-style: italic;">Different styling</span> normal
+ <span style="font-family: monospace;">Different font</span> normal
+ <span style="text-decoration: underline;">underlined</span> normal
+ <span style="text-decoration: line-through;">strikethrough</span> normal
+ <s>strikethrough</s> normal
+ <strike>strikethrough</strike> normal
+ </p>
+
+ <p id="area10">Normal
+ <span style="font-size: 120%">bigger</span> smaller
+ <span style="background-color: blue;">background blue</span> normal
+ <span style="font-style: italic;">Different styling</span> normal
+ <span style="font-family: monospace;">Different font</span> normal
+ <span style="text-decoration: underline;">underlined</span> normal
+ <span style="text-decoration: line-through;">strikethrough</span> normal
+ </p>
+
+ <p id="area11" style="font-weight: bolder;">
+ <span style="font-weight: bolder;">bolder</span>bolder
+ <span style="font-weight: lighter;">lighter</span>bolder
+ <span style="font-weight: normal;">normal</span>bolder
+ <b>bold</b>bolder
+ <span style="font-weight: 400;">normal</span>bolder
+ <span style="font-weight: 700;">bold</span>bolder
+ <span style="font-weight: bold;">bold</span>bolder
+ <span style="font-weight: 900;">bold</span>bolder
+ </p>
+
+ <p id="area12">hello</p>
+ <p id="area13"></p>
+ <input id="area14">
+
+ <!-- *plain*plain**bold*bold*-->
+ <div id="area15"><p>embed</p>plain<p>embed</p>plain<p>embed</p><img src="../moz.png" alt="image"/><b>bold</b><p>embed</p><b>bold</b><p>embed</p></div>
+
+ <p id="area16" style="font-family: sans-serif;">
+ <span style="font-family: monospace;">text</span>text
+ <span style="font-family: serif;">text</span>text
+ <span style="font-family: BodoniThatDoesntExist;">text</span>text
+ <span style="font-family: Comic Sans MS, cursive;">text</span>text
+ <span style="font-family: sans-serif, fantasy;">text</span>text
+ </p>
+
+ <p id="area17">
+ <span style="text-decoration-line: underline;">underline
+ </span><span style="text-decoration: underline; text-decoration-color: blue;">blue
+ </span><span style="text-decoration: underline; text-decoration-style: dotted;">dotted
+ </span><span style="text-decoration-line: line-through;">linethrough
+ </span><span style="text-decoration: line-through; text-decoration-color: blue;">blue
+ </span><span style="text-decoration: line-through; text-decoration-style: wavy;">wavy
+ </span>
+ </p>
+
+ <ul>
+ <li id="area18" class="gencontent">item</li>
+ </ul>
+
+ <p id="area19">uncolored
+ <mark id="area19mark">colored</mark> uncolored
+ </p>
+
+ <p id="area20" style="font-size: 15pt;">offset test</p>
+
+ <!-- subscript, superscript tests -->
+ <sub id="sub_tag">offset test</sub>
+ <sup id="sup_tag">offset test</sup>
+ <p id="subscript_role" role="subscript">offset test</p>
+ <p id="superscript_role" role="superscript">offset test</p>
+
+ <div><span id="superscript_role_in_div" role="superscript">offset test</span></div>
+ <p role="superscript"><sub id="sub_within_superscript_role">offset test</sub></p>
+ <p role="subscript"><sup id="sup_within_subscript_role">offset test</sup></p>
+ <sup><sub id="sub_within_sup">offset test</sub></sup>
+ <sub><sup id="sup_within_sub">offset test</sup></sub>
+ <p id="css_sub_within_superscript_role" role="superscript"><span style="vertical-align: sub">offset test</span></p>
+ <p id="css_super_within_subscript_role" role="subscript"><span style="vertical-align: super">offset test</span></p>
+ <sub id="sub_with_superscript_role" role="superscript">offset test</sub>
+ <sup id="sup_with_subscript_role" role="subscript">offset test</sup>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textattrs/test_general.xhtml b/accessible/tests/mochitest/textattrs/test_general.xhtml
new file mode 100644
index 0000000000..de0556f10c
--- /dev/null
+++ b/accessible/tests/mochitest/textattrs/test_general.xhtml
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Tests: XUL label text interface">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Testing
+
+ function doTests()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // XUL label
+
+ testTextAttrs("label1", 0, {}, {}, 0, 5, true);
+ testTextAttrs("label2", 0, {}, {}, 0, 5, true);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+ <label id="label1" value="Hello"/>
+ <label id="label2">Hello</label>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/textattrs/test_invalid.html b/accessible/tests/mochitest/textattrs/test_invalid.html
new file mode 100644
index 0000000000..7028b32622
--- /dev/null
+++ b/accessible/tests/mochitest/textattrs/test_invalid.html
@@ -0,0 +1,59 @@
+<html>
+
+<head>
+ <title>Invalid text attribute</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+ function doTests() {
+ testDefaultTextAttrs("aria_invalid_empty", {}, true);
+ testDefaultTextAttrs("aria_invalid_true", { "invalid": "true" }, true);
+ testDefaultTextAttrs("aria_invalid_false", { "invalid": "false" }, true);
+ testDefaultTextAttrs("aria_invalid_grammar", { "invalid": "grammar" }, true);
+ testDefaultTextAttrs("aria_invalid_spelling", { "invalid": "spelling" }, true);
+ testDefaultTextAttrs("aria_invalid_erroneous", { "invalid": "true" }, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=445510"
+ title="Support ARIA-based text attributes">
+ Mozilla Bug 445510
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="aria_invalid_empty" aria-invalid="">no invalid</div>
+ <div id="aria_invalid_true" aria-invalid="true">invalid:true</div>
+ <div id="aria_invalid_false" aria-invalid="false">invalid:false</div>
+ <div id="aria_invalid_grammar" aria-invalid="grammar">invalid:grammar</div>
+ <div id="aria_invalid_spelling" aria-invalid="spelling">invalid:spelling</div>
+ <div id="aria_invalid_erroneous" aria-invalid="erroneous">invalid:true</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textattrs/test_mathml.html b/accessible/tests/mochitest/textattrs/test_mathml.html
new file mode 100644
index 0000000000..e6936ebc7f
--- /dev/null
+++ b/accessible/tests/mochitest/textattrs/test_mathml.html
@@ -0,0 +1,47 @@
+<html>
+
+<head>
+ <title>MathML Text attributes tests</title>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script src="../common.js"></script>
+ <script src="../attributes.js"></script>
+ <script src="../events.js"></script>
+
+ <script>
+ function doTest() {
+ const math = getNode("math");
+ const defAttrs = buildDefaultTextAttrs(math, "10pt");
+ testDefaultTextAttrs(math, defAttrs);
+
+ for (const id of ["mn", "mi", "annotation", "annotationXml"]) {
+ testTextAttrs(id, 0, {}, defAttrs, 0, 1);
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body style="font-size: 12pt">
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <math id="math" style="font-size: smaller">
+ <mn id="mn">1</mn>
+ <mi id="mi">x</mi>
+ <!-- tabindex forces creation of an Accessible -->
+ <annotation id="annotation" tabindex="0">a</annotation>
+ <annotation-xml id="annotationXml" tabindex="0">a</annotation-xml>
+ </math>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textattrs/test_spelling.html b/accessible/tests/mochitest/textattrs/test_spelling.html
new file mode 100644
index 0000000000..b8f3858353
--- /dev/null
+++ b/accessible/tests/mochitest/textattrs/test_spelling.html
@@ -0,0 +1,52 @@
+<html>
+
+<head>
+ <title>Spell check text attribute tests</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+ async function doTest() {
+ const misspelledAttrs = {"invalid": "spelling"};
+
+ let editable = document.getElementById("div_after_misspelling");
+ // The attr change event gets fired on the last accessible containing a
+ // spelling error.
+ let spellDone = waitForEvent(EVENT_TEXT_ATTRIBUTE_CHANGED, "div_after_misspelling_div2");
+ editable.focus();
+ await spellDone;
+ testTextAttrs("div_after_misspelling_div1", 0, {}, {}, 0, 5, true);
+ testTextAttrs("div_after_misspelling_div1", 5, misspelledAttrs, {}, 5, 9, true);
+ testTextAttrs("div_after_misspelling_div2", 0, {}, {}, 0, 5, true);
+ testTextAttrs("div_after_misspelling_div2", 5, misspelledAttrs, {}, 5, 9, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Text attribute offsets for accessibles after first spelling error (bug 1479678) -->
+ <div id="div_after_misspelling" contenteditable="true" spellcheck="true" lang="en-US">
+ <div id="div_after_misspelling_div1">Test tset</div>
+ <div id="div_after_misspelling_div2">test tset</div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textattrs/test_svg.html b/accessible/tests/mochitest/textattrs/test_svg.html
new file mode 100644
index 0000000000..c8d5d5f893
--- /dev/null
+++ b/accessible/tests/mochitest/textattrs/test_svg.html
@@ -0,0 +1,52 @@
+<html>
+
+<head>
+ <title>SVG Text attributes tests</title>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script src="../common.js"></script>
+ <script src="../attributes.js"></script>
+ <script src="../events.js"></script>
+
+ <script>
+ function doTest() {
+ const svg = getNode("svg");
+ const defAttrs = buildDefaultTextAttrs(svg, "10pt");
+ testDefaultTextAttrs(svg, defAttrs);
+ testTextAttrs(svg, 0, {}, defAttrs, 0, 2);
+
+ const g = getNode("g");
+ testTextAttrs(g, 0, {}, defAttrs, 0, 2);
+
+ const a = getNode("a");
+ const aDefAttrs = buildDefaultTextAttrs(a, "10pt");
+ testTextAttrs(a, 0, {}, aDefAttrs, 0, 1);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body style="font-size: 12pt">
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <svg id="svg" style="font-size: smaller">
+ <foreignobject>f1</foreignobject>
+ <g id="g">
+ <title>g</title>
+ <foreignobject>f2</foreignobject>
+ </g>
+ <text><a href="#" id="a">a</a></text>
+ </svg>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textcaret/a11y.ini b/accessible/tests/mochitest/textcaret/a11y.ini
new file mode 100644
index 0000000000..6e2e4ef8d7
--- /dev/null
+++ b/accessible/tests/mochitest/textcaret/a11y.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_general.html]
diff --git a/accessible/tests/mochitest/textcaret/test_general.html b/accessible/tests/mochitest/textcaret/test_general.html
new file mode 100644
index 0000000000..bd949116fd
--- /dev/null
+++ b/accessible/tests/mochitest/textcaret/test_general.html
@@ -0,0 +1,174 @@
+<html>
+
+<head>
+ <title>Text accessible caret testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Turn on/off the caret browsing mode.
+ */
+ function turnCaretBrowsing(aIsOn) {
+ Services.prefs.setBoolPref("accessibility.browsewithcaret", aIsOn);
+ }
+
+ /**
+ * Test caret offset for the given accessible.
+ */
+ function testCaretOffset(aID, aCaretOffset) {
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+ is(acc.caretOffset, aCaretOffset,
+ "Wrong caret offset for " + aID);
+ }
+
+ function testCaretOffsets(aList) {
+ for (var i = 0; i < aList.length; i++)
+ testCaretOffset(aList[0][0], aList[0][1]);
+ }
+
+ function queueTraversalList(aList, aFocusNode) {
+ for (var i = 0 ; i < aList.length; i++) {
+ var node = aList[i].DOMPoint[0];
+ var nodeOffset = aList[i].DOMPoint[1];
+
+ var textAcc = aList[i].point[0];
+ var textOffset = aList[i].point[1];
+ var textList = aList[i].pointList;
+ var invoker =
+ new moveCaretToDOMPoint(textAcc, node, nodeOffset, textOffset,
+ ((i == 0) ? aFocusNode : null),
+ testCaretOffsets.bind(null, textList));
+ gQueue.push(invoker);
+ }
+ }
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ function doTests() {
+ turnCaretBrowsing(true);
+
+ // test caret offsets
+ testCaretOffset(document, 0); // because of no selection ranges
+ testCaretOffset("textbox", -1);
+ testCaretOffset("textarea", -1);
+ testCaretOffset("p", -1);
+
+ // test caret move events and caret offsets
+ gQueue = new eventQueue();
+
+ gQueue.push(new setCaretOffset("textbox", 1, "textbox"));
+ gQueue.push(new setCaretOffset("link", 1, "link"));
+ gQueue.push(new setCaretOffset("heading", 1, document));
+
+ // a*b*c
+ var p2Doc = getNode("p2_container").contentDocument;
+ var traversalList = [
+ { // before 'a'
+ DOMPoint: [ getNode("p2", p2Doc).firstChild, 0 ],
+ point: [ getNode("p2", p2Doc), 0 ],
+ pointList: [ [ p2Doc, 0 ] ],
+ },
+ { // after 'a' (before anchor)
+ DOMPoint: [ getNode("p2", p2Doc).firstChild, 1 ],
+ point: [ getNode("p2", p2Doc), 1 ],
+ pointList: [ [ p2Doc, 0 ] ],
+ },
+ { // before 'b' (inside anchor)
+ DOMPoint: [ getNode("p2_a", p2Doc).firstChild, 0 ],
+ point: [ getNode("p2_a", p2Doc), 0 ],
+ pointList: [
+ [ getNode("p2", p2Doc), 1 ],
+ [ p2Doc, 0 ],
+ ],
+ },
+ { // after 'b' (inside anchor)
+ DOMPoint: [ getNode("p2_a", p2Doc).firstChild, 1 ],
+ point: [ getNode("p2_a", p2Doc), 1 ],
+ pointList: [
+ [ getNode("p2", p2Doc), 1 ],
+ [ p2Doc, 0 ],
+ ],
+ },
+ { // before 'c' (after anchor)
+ DOMPoint: [ getNode("p2", p2Doc).lastChild, 0 ],
+ point: [ getNode("p2", p2Doc), 2 ],
+ pointList: [ [ p2Doc, 0 ] ],
+ },
+ { // after 'c'
+ DOMPoint: [ getNode("p2", p2Doc).lastChild, 1 ],
+ point: [ getNode("p2", p2Doc), 3 ],
+ pointList: [ [ p2Doc, 0 ] ],
+ },
+ ];
+ queueTraversalList(traversalList, getNode("p2", p2Doc));
+
+ gQueue.onFinish = function() {
+ turnCaretBrowsing(false);
+ };
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=448744"
+ title="caretOffset should return -1 if the system caret is not currently with in that particular object">
+ Bug 448744
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=524115"
+ title="HyperText accessible should get focus when the caret is positioned inside of it, text is changed or copied into clipboard by ATs">
+ Bug 524115
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=546068"
+ title="Position is not being updated when atk_text_set_caret_offset is used">
+ Bug 546068
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=672717"
+ title="Broken caret when moving into/out of embedded objects with right arrow">
+ Bug 672717
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=725581"
+ title="caretOffset for textarea should be -1 when textarea doesn't have a focus">
+ Bug 725581
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="textbox" value="hello"/>
+ <textarea id="textarea">text<br>text</textarea>
+ <p id="p" contentEditable="true"><span>text</span><br/>text</p>
+ <a id="link" href="about:mozilla">about mozilla</a>
+ <h5 id="heading">heading</h5>
+ <iframe id="p2_container"
+ src="data:text/html,<p id='p2' contentEditable='true'>a<a id='p2_a' href='mozilla.org'>b</a>c</p>"></iframe>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textrange/a11y.ini b/accessible/tests/mochitest/textrange/a11y.ini
new file mode 100644
index 0000000000..4e610c61f3
--- /dev/null
+++ b/accessible/tests/mochitest/textrange/a11y.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/moz.png
+
+[test_general.html]
+[test_selection.html]
diff --git a/accessible/tests/mochitest/textrange/test_general.html b/accessible/tests/mochitest/textrange/test_general.html
new file mode 100644
index 0000000000..b7da4ee8aa
--- /dev/null
+++ b/accessible/tests/mochitest/textrange/test_general.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Text Range tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+ <script type="application/javascript">
+
+ function doTest() {
+ // enclosingRange
+ var input = getAccessible("input", [ nsIAccessibleText ]);
+ testTextRange(input.enclosingRange, "enclosing range for 'input'",
+ input, 0, input, 5, "hello", input);
+
+ var ta = getAccessible("textarea", [ nsIAccessibleText ]);
+ testTextRange(ta.enclosingRange, "enclosing range for 'textarea'",
+ ta, 0, ta, 5, "hello", ta);
+
+ var iframeDocNode = getNode("iframe").contentDocument;
+ var iframeDoc = getAccessible(iframeDocNode, [ nsIAccessibleText ]);
+ testTextRange(iframeDoc.enclosingRange, "enclosing range for iframe doc",
+ iframeDoc, 0, iframeDoc, 1, "hello",
+ iframeDoc, [ getNode("p", iframeDocNode) ]);
+
+ // getRangeByChild
+ var docacc = getAccessible(document, [ nsIAccessibleText ]);
+ var p1 = getAccessible("p1");
+ var p1Range = docacc.getRangeByChild(p1);
+ testTextRange(p1Range, "range by 'p1' child",
+ p1, 0, "p1", 11, "text text",
+ p1, ["p1_img"]);
+
+ testTextRange(docacc.getRangeByChild(getAccessible("p1_img")),
+ "range by 'p1_img' child",
+ "p1", 5, "p1", 5, "",
+ "p1", ["p1_img"]);
+
+ var p2 = getAccessible("p2");
+ var p2Range = docacc.getRangeByChild(p2);
+ testTextRange(p2Range, "range by 'p2' child",
+ p2, 0, "p2", 11, "text link text",
+ p2, ["p2_a"]);
+
+ testTextRange(docacc.getRangeByChild(getAccessible("p2_a")),
+ "range by 'p2_a' child",
+ "p2_a", 0, "p2_a", 5, "link",
+ "p2_a", ["p2_img"]);
+
+ // getRangeAtPoint
+ getNode("p2_a").scrollIntoView(true);
+ var [x, y] = getPos("p2_a");
+ testTextRange(docacc.getRangeAtPoint(x + 1, y + 1),
+ "range at 'p2_a' top-left edge",
+ "p2_a", 0, "p2_a", 0, "",
+ "p2_a");
+
+ // TextRange::compare
+ ok(input.enclosingRange.compare(input.enclosingRange),
+ "input enclosing ranges should be equal");
+
+ ok(!input.enclosingRange.compare(ta.enclosingRange),
+ "input and textarea enclosing ranges can't be equal");
+
+ // TextRange::compareEndPoints
+ var res = p1Range.compareEndPoints(EndPoint_End, p2Range, EndPoint_Start);
+ is(res, -1, "p1 range must be lesser with p2 range");
+
+ res = p2Range.compareEndPoints(EndPoint_Start, p1Range, EndPoint_End);
+ is(res, 1, "p2 range must be greater with p1 range");
+
+ res = p1Range.compareEndPoints(EndPoint_Start, p1Range, EndPoint_Start);
+ is(res, 0, "p1 range must be equal with p1 range");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Implement Text accessible text range methods"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=975065">Bug 975065</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input" value="hello">
+ <textarea id="textarea">hello</textarea>
+ <iframe id="iframe" src="data:text/html,<html><body><p id='p'>hello</p></body></html>"></iframe>
+ <p id="p1">text <img id="p1_img", src="../moz.png"> text</p>
+ <p id="p2">text <a id="p2_a" href="www">link<img id="p2_img", src="../moz.png"></a> text</p>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textrange/test_selection.html b/accessible/tests/mochitest/textrange/test_selection.html
new file mode 100644
index 0000000000..2a5d4da5c2
--- /dev/null
+++ b/accessible/tests/mochitest/textrange/test_selection.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Text Range selection tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+ <script type="application/javascript">
+
+ function doTest() {
+ var sel = window.getSelection();
+ var p = getNode("p1");
+ var a = getNode("p2_a");
+
+ var range = document.createRange();
+ sel.addRange(range);
+
+ // the accessible is contained by the range
+ range.selectNode(p);
+
+ var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #1", document, 3, document, 4);
+
+ ok(a11yrange.crop(getAccessible(a)), "Range failed to crop #1.");
+ testTextRange(a11yrange, "cropped range #1", a, 0, a, 5);
+
+ // the range is contained by the accessible
+ range.selectNode(a);
+ a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #2", p, 5, p, 6);
+
+ ok(a11yrange.crop(getAccessible(p)), "Range failed to crop #2.");
+ testTextRange(a11yrange, "cropped range #2", p, 5, p, 6);
+
+ // the range starts before the accessible and ends inside it
+ range.setStart(p, 0);
+ range.setEndAfter(a.firstChild, 4);
+ a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #3", p, 0, a, 4);
+
+ ok(a11yrange.crop(getAccessible(a)), "Range failed to crop #3.");
+ testTextRange(a11yrange, "cropped range #3", a, 0, a, 4);
+
+ // the range starts inside the accessible and ends after it
+ range.setStart(a.firstChild, 1);
+ range.setEndAfter(p);
+ a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #4", a, 1, document, 4);
+
+ ok(a11yrange.crop(getAccessible(a)), "Range failed to crop #4.");
+ testTextRange(a11yrange, "cropped range #4", a, 1, a, 5);
+
+ // the range ends before the accessible
+ range.setStart(p.firstChild, 0);
+ range.setEnd(p.firstChild, 4);
+ a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #5", p, 0, p, 4);
+ ok(!a11yrange.crop(getAccessible(a)), "Crop #5 succeeded while it shouldn't");
+
+ // the range starts after the accessible
+ range.setStart(p.lastChild, 0);
+ range.setEnd(p.lastChild, 4);
+ a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #6", p, 6, p, 10);
+
+ ok(!a11yrange.crop(getAccessible(a)), "Crop #6 succeeded while it shouldn't");
+
+ // crop a range by a table
+ range.selectNode(getNode("c2"));
+ a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #7", document, 4, document, 5);
+
+ ok(a11yrange.crop(getAccessible("table")), "Range failed to crop #7.");
+ testTextRange(a11yrange, "cropped range #7", "table", 0, "table", 1);
+
+ // test compare points for selection with start in nested node
+ range.setStart(a.firstChild, 2);
+ range.setEnd(p.lastChild, 3);
+ a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+ var res = a11yrange.compareEndPoints(EndPoint_Start, a11yrange, EndPoint_End);
+ is(res, -1, "start must be lesser than end");
+
+ res = a11yrange.compareEndPoints(EndPoint_End, a11yrange, EndPoint_Start);
+ is(res, 1, "end must be greater than start");
+
+ // Crop a range to its next sibling.
+ range.selectNode(getNode("c3p1").firstChild);
+ a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+ testTextRange(a11yrange, "selection range #8", "c3p1", 0, "c3p1", 1);
+ ok(!a11yrange.crop(getAccessible("c3p2")), "Crop #8 succeeded but shouldn't have.");
+ // Crop a range to its previous sibling.
+ range.selectNode(getNode("c3p2").firstChild);
+ a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+ testTextRange(a11yrange, "selection range #9", "c3p2", 0, "c3p2", 1);
+ ok(!a11yrange.crop(getAccessible("c3p1")), "Crop #9 succeeded but shouldn't have.");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Implement IAccessible2_3::selectionRanges"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1233118">Bug 1233118</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="p1">text <a id="p2_a" href="www">link<img id="p2_img", src="../moz.png"></a> text</p>
+
+ <div id="c2">start<table id="table"><tr><td>cell</td></tr></table>end</div>
+
+ <div id="c3"><p id="c3p1">a</p><p id="c3p2">b</p></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textselection/a11y.ini b/accessible/tests/mochitest/textselection/a11y.ini
new file mode 100644
index 0000000000..6581af56db
--- /dev/null
+++ b/accessible/tests/mochitest/textselection/a11y.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_general.html]
+[test_userinput.html]
diff --git a/accessible/tests/mochitest/textselection/test_general.html b/accessible/tests/mochitest/textselection/test_general.html
new file mode 100644
index 0000000000..92e7988a87
--- /dev/null
+++ b/accessible/tests/mochitest/textselection/test_general.html
@@ -0,0 +1,221 @@
+<html>
+
+<head>
+ <title>Text selection testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Helper function to test selection bounds.
+ * @param {string} aID The ID to test.
+ * @param {nsIAccessibleText} acc The accessible to test.
+ * @param {int} index The selection's index to test.
+ * @param {array} offsets The start and end offset to test against.
+ * @param {string} msgStart The start of the message to return in test
+ * messages.
+ */
+ function testSelectionBounds(aID, acc, index, offsets, msgStart) {
+ const [expectedStart, expectedEnd] = offsets;
+ const startOffset = {}, endOffset = {};
+ acc.getSelectionBounds(index, startOffset, endOffset);
+
+ is(startOffset.value, Math.min(expectedStart, expectedEnd),
+ msgStart + ": Wrong start offset for " + aID);
+ is(endOffset.value, Math.max(expectedStart, expectedEnd),
+ msgStart + ": Wrong end offset for " + aID);
+ }
+
+ /**
+ * Test adding selections to accessibles.
+ * @param {string} aID The ID of the element to test.
+ * @param {array} aSelections Array of selection start and end indices.
+ */
+ async function addSelections(aID, aSelections) {
+ info("Test adding selections to " + aID);
+ const hyperText = getAccessible(aID, [ nsIAccessibleText ]);
+ const initialSelectionCount = hyperText.selectionCount;
+
+ // Multiple selection changes will be coalesced, so just listen for one.
+ const selectionChange = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, aID);
+ for (let [startOffset, endOffset] of aSelections) {
+ hyperText.addSelection(startOffset, endOffset);
+ }
+ await selectionChange;
+
+ is(hyperText.selectionCount,
+ aSelections.length + initialSelectionCount,
+ "addSelection: Wrong selection count for " + aID);
+
+ for (let i in aSelections) {
+ testSelectionBounds(aID, hyperText, initialSelectionCount + i,
+ aSelections[i], "addSelection");
+ }
+
+ is(hyperText.caretOffset, aSelections[hyperText.selectionCount -1][1],
+ "addSelection: caretOffset not at selection end for " + aID);
+ }
+
+ /**
+ * Test changing selections in accessibles.
+ * @param {string} aID The ID of the element to test.
+ * @param {int} aIndex The index of the selection to change.
+ * @param {array} aSelection Array of the selection's new start and end
+ * indices.
+ */
+ async function changeSelection(aID, aIndex, aSelection) {
+ info("Test changing the selection of " + aID + " at index " + aIndex);
+ const [startOffset, endOffset] = aSelection;
+ const hyperText = getAccessible(aID, [ nsIAccessibleText ]);
+
+ const selectionChanged = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, aID);
+ hyperText.setSelectionBounds(aIndex, startOffset, endOffset);
+ await selectionChanged;
+
+ testSelectionBounds(aID, hyperText, aIndex,
+ aSelection, "setSelectionBounds");
+
+ is(hyperText.caretOffset, endOffset,
+ "setSelectionBounds: caretOffset not at selection end for " + aID);
+ }
+
+ /**
+ * Test removing all selections from accessibles.
+ * @param {string} aID The ID of the element to test.
+ */
+ async function removeSelections(aID) {
+ info("Testing removal of all selections from " + aID);
+ const hyperText = getAccessible(aID, [ nsIAccessibleText ]);
+
+ let selectionsRemoved = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, document);
+ const selectionCount = hyperText.selectionCount;
+ for (let i = 0; i < selectionCount; i++) {
+ hyperText.removeSelection(0);
+ }
+ await selectionsRemoved;
+
+ is(hyperText.selectionCount, 0,
+ "removeSelection: Wrong selection count for " + aID);
+ }
+
+ /**
+ * Test that changing the DOM selection is reflected in the accessibles.
+ * @param {string} aID The container ID to test in
+ * @param {string} aNodeID1 The start node of the selection
+ * @param {int} aNodeOffset1 The offset where the selection should start
+ * @param {string} aNodeID2 The node in which the selection should end
+ * @param {int} aNodeOffset2 The index at which the selection should end
+ * @param {array} aTests An array of accessibles and their start and end
+ * offsets to test.
+ */
+ async function changeDOMSelection(aID, aNodeID1, aNodeOffset1,
+ aNodeID2, aNodeOffset2,
+ aTests) {
+ info("Test that DOM selection changes are reflected in the accessibles");
+
+ let selectionChanged = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, aID);
+ // HyperTextAccessible::GetSelectionDOMRanges ignores hidden selections.
+ // Here we may be focusing an editable element (and thus hiding the
+ // main document selection), so blur it so that we test what we want to
+ // test.
+ document.activeElement.blur();
+
+ const sel = window.getSelection();
+ const range = document.createRange();
+ range.setStart(getNode(aNodeID1), aNodeOffset1);
+ range.setEnd(getNode(aNodeID2), aNodeOffset2);
+ sel.addRange(range);
+ await selectionChanged;
+
+ for (let i = 0; i < aTests.length; i++) {
+ const text = getAccessible(aTests[i][0], nsIAccessibleText);
+ is(text.selectionCount, 1,
+ "setSelectionBounds: Wrong selection count for " + aID);
+ testSelectionBounds(aID, text, 0, [aTests[i][1], aTests[i][2]],
+ "setSelectionBounds");
+ }
+ }
+
+ /**
+ * Test expected and unexpected events for selecting
+ * all text and focusing both an input and text area. We expect a caret
+ * move, but not a text selection change.
+ * @param {string} aID The ID of the element to test.
+ */
+ async function eventsForSelectingAllTextAndFocus(aID) {
+ info("Test expected caretMove and unexpected textSelection events for " +aID);
+ let events = waitForEvents({
+ expected: [[EVENT_TEXT_CARET_MOVED, aID]],
+ unexpected: [[EVENT_TEXT_SELECTION_CHANGED, aID]]}, aID);
+ selectAllTextAndFocus(aID);
+ await events;
+ }
+
+ /**
+ * Do tests
+ */
+
+ async function doTests() {
+ await addSelections("paragraph", [[1, 3], [6, 10]]);
+ await changeSelection("paragraph", 0, [2, 4]);
+ await removeSelections("paragraph");
+
+ // reverse selection
+ await addSelections("paragraph", [[1, 3], [10, 6]]);
+ await removeSelections("paragraph");
+
+ await eventsForSelectingAllTextAndFocus("textbox");
+ await changeSelection("textbox", 0, [1, 3]);
+
+ // reverse selection
+ await changeSelection("textbox", 0, [3, 1]);
+
+ await eventsForSelectingAllTextAndFocus("textarea");
+ await changeSelection("textarea", 0, [1, 3]);
+
+ await changeDOMSelection("c1", "c1_span1", 0, "c1_span2", 0,
+ [["c1", 2, 2]]);
+ await changeDOMSelection("c2", "c2", 0, "c2_div2", 1,
+ [["c2", 0, 3], ["c2_div2", 0, 2]]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=688126"
+ title="nsIAccessibleText::setSelectionBounds doesn't fire text selection changed events in some cases">
+ Bug 688126
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=688124"
+ title="no text selection changed event when selection is removed">
+ Bug 688124
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="paragraph">hello world</p>
+ <input id="textbox" value="hello"/>
+ <textarea id="textarea">hello</textarea>
+ <div id="c1">hi<span id="c1_span1"></span><span id="c1_span2"></span>hi</div>
+ <div id="c2">hi<div id="c2_div2">hi</div></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textselection/test_userinput.html b/accessible/tests/mochitest/textselection/test_userinput.html
new file mode 100644
index 0000000000..1aa0b2abb6
--- /dev/null
+++ b/accessible/tests/mochitest/textselection/test_userinput.html
@@ -0,0 +1,76 @@
+<html>
+
+<head>
+ <title>Text selection by user input</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+ async function doTests() {
+ // Tab to 't2' and then tab out of it: it must not have any selection.
+ info("Select all text in t1 and focus it");
+ let focused = waitForEvent(EVENT_FOCUS, "t1");
+ // Simulate tabbing to t1 by selecting all text before focusing it.
+ selectAllTextAndFocus("t1");
+ await focused;
+
+ info("Tab to t2");
+ const t2 = getNode("t2");
+ focused = waitForEvent(EVENT_FOCUS, t2);
+ synthesizeKey("VK_TAB");
+ await focused;
+
+ info("Tab to t3 and make sure there is no selection in t2 afterwards");
+ const t3 = getNode("t3");
+ focused = waitForEvent(EVENT_FOCUS, t3);
+ synthesizeKey("VK_TAB");
+ await focused;
+ const prevFocus = getAccessible(t2, [ nsIAccessibleText ]);
+ is(prevFocus.selectionCount, 0,
+ "Wrong selection count for t2");
+
+ let exceptionCaught = false;
+ try {
+ const startOffsetObj = {}, endOffsetObj = {};
+ prevFocus.getSelectionBounds(0, startOffsetObj, endOffsetObj);
+ } catch (e) {
+ exceptionCaught = true;
+ }
+
+ ok(exceptionCaught, "No selection was expected for t2");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=440590"
+ title="Text selection information is not updated when HTML and XUL entries lose focus">
+ Bug 440590
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input type="text" id="t1" maxlength="3" size="3" value="1">
+ <input type="text" id="t2" maxlength="3" size="3" value="1">
+ <input type="text" id="t3" maxlength="3" size="3" value="1">
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/a11y.ini b/accessible/tests/mochitest/tree/a11y.ini
new file mode 100644
index 0000000000..c2a78c76a7
--- /dev/null
+++ b/accessible/tests/mochitest/tree/a11y.ini
@@ -0,0 +1,58 @@
+[DEFAULT]
+support-files =
+ dockids.html
+ wnd.xhtml
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/formimage.png
+ !/accessible/tests/mochitest/letters.gif
+ !/accessible/tests/mochitest/moz.png
+ !/accessible/tests/mochitest/tree/wnd.xhtml
+ !/dom/media/test/bug461281.ogg
+
+[test_applicationacc.xhtml]
+skip-if = true # Bug 561508
+[test_aria_display_contents.html]
+[test_aria_globals.html]
+[test_aria_grid.html]
+[test_aria_imgmap.html]
+[test_aria_list.html]
+[test_aria_menu.html]
+[test_aria_owns.html]
+[test_aria_presentation.html]
+[test_aria_table.html]
+[test_brokencontext.html]
+[test_button.xhtml]
+[test_canvas.html]
+[test_combobox.xhtml]
+[test_cssflexbox.html]
+[test_cssoverflow.html]
+[test_display_contents.html]
+[test_divs.html]
+[test_dochierarchy.html]
+[test_dockids.html]
+[test_filectrl.html]
+[test_formctrl.html]
+[test_formctrl.xhtml]
+[test_gencontent.html]
+[test_groupbox.xhtml]
+[test_html_in_mathml.html]
+[test_iframe.html]
+[test_image.xhtml]
+[test_img.html]
+[test_invalid_img.xhtml]
+[test_invalidationlist.html]
+[test_list.html]
+[test_map.html]
+[test_media.html]
+[test_select.html]
+[test_svg.html]
+[test_tabbox.xhtml]
+[test_tabbrowser.xhtml]
+skip-if = (os == 'linux' && debug) || (os == 'win' && ccov) # Bug 1389365 || bug 1423218
+[test_table.html]
+[test_table_2.html]
+[test_table_3.html]
+[test_tree.xhtml]
+[test_txtcntr.html]
+[test_txtctrl.html]
+[test_txtctrl.xhtml]
diff --git a/accessible/tests/mochitest/tree/dockids.html b/accessible/tests/mochitest/tree/dockids.html
new file mode 100644
index 0000000000..e964cd759e
--- /dev/null
+++ b/accessible/tests/mochitest/tree/dockids.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+ <head>
+ <link rel="next" href="http://www.mozilla.org">
+ <style>
+ head, link, a { display: block; }
+ link:after { content: "Link to " attr(href); }
+ </style>
+ <script>
+ window.onload = function() {
+ document.documentElement.appendChild(document.createElement("input"));
+
+ var l = document.createElement("link");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ l.href = "http://www.mozilla.org";
+ l.textContent = "Another ";
+ document.documentElement.appendChild(l);
+
+ l = document.createElement("a");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ l.href = "http://www.mozilla.org";
+ l.textContent = "Yet another link to mozilla";
+ document.documentElement.appendChild(l);
+ };
+ </script>
+ </head>
+ <body>
+ Hey, I'm a <body> with three links that are not inside me and an input
+ that's not inside me.
+ </body>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_applicationacc.xhtml b/accessible/tests/mochitest/tree/test_applicationacc.xhtml
new file mode 100644
index 0000000000..5e00a2878f
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_applicationacc.xhtml
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible Application Accessible hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // Note: bug 560239 can be tested if this test runs in standalone mode only.
+
+ var gURL = "../tree/wnd.xhtml"
+ var gWnd = window.openDialog(gURL, "wnd", "chrome,width=600,height=600");
+
+ function doTest()
+ {
+ // Application accessible should contain two root document accessibles,
+ // one is for browser window, another one is for open dialog window.
+ var accTree = {
+ role: ROLE_APP_ROOT,
+ children: [
+ {
+ role: ROLE_CHROME_WINDOW,
+ name: "Accessibility Chrome Test Harness - Minefield"
+ },
+ {
+ role: ROLE_CHROME_WINDOW,
+ name: "Empty Window"
+ }
+ ]
+ };
+ testAccessibleTree(getApplicationAccessible(), accTree);
+
+ gWnd.close();
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ // We need to open dialog window before accessibility is started.
+ addLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=560239"
+ title="no children of application accessible for windows open before accessibility was started">
+ Mozilla Bug 560239
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/tree/test_aria_display_contents.html b/accessible/tests/mochitest/tree/test_aria_display_contents.html
new file mode 100644
index 0000000000..5c6f7f20fb
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_display_contents.html
@@ -0,0 +1,173 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>ARIA and style="display: contents;"</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // Test ARIA grids that have display: contents; on different elements.
+ // They should all have equivalent trees.
+ var accTree =
+ { TABLE: [
+ { ROW: [
+ { role: ROLE_COLUMNHEADER,
+ children: [ { TEXT_LEAF: [ ] }, ]
+ },
+ { role: ROLE_COLUMNHEADER,
+ children: [ { TEXT_LEAF: [ ] }, ]
+ },
+ ] },
+ { ROW: [
+ { ROWHEADER: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] };
+
+ testAccessibleTree("gridWithoutDisplayContents", accTree);
+ testAccessibleTree("gridWithDisplayContents", accTree);
+ testAccessibleTree("gridWithDisplayContentsRow", accTree);
+ testAccessibleTree("gridWithDisplayContentsColHeader", accTree);
+ testAccessibleTree("gridWithDisplayContentsRowHeader", accTree);
+ testAccessibleTree("gridWithDisplayContentsGridCell", accTree);
+
+ // Test divs with ARIA roles and attributes and display: contents to
+ // verify that Accessibles are created appropriately.
+ accTree =
+ { SECTION: [
+ { LIST: [
+ { LISTITEM: [
+ { TEXT_LEAF: [ ] }
+ ] },
+ ] },
+ { SECTION: [
+ { LISTITEM: [
+ { TEXT_LEAF: [ ] }
+ ] },
+ ] },
+ { LISTITEM: [
+ { TEXT_LEAF: [ ] }
+ ] },
+ ] };
+ testAccessibleTree("container", accTree);
+
+ // Test paragraph with display: contents. It should create a generic
+ // Accessible that reports the role correctly.
+ accTree =
+ { SECTION: [
+ { PARAGRAPH: [ { TEXT_LEAF: [ ] } ] },
+ { TEXT_LEAF: [ ] }, // space between paragraphs
+ { TEXT_LEAF: [ ] },
+ ] };
+ testAccessibleTree("paragraphContainer", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Element with ARIA role and display: contents doesn't get an accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1494196">
+ Mozilla Bug 1494196
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="gridWithoutDisplayContents" role="grid">
+ <div role="row">
+ <div role="columnheader">col1</div>
+ <div role="columnheader">col2</div>
+ </div>
+ <div role="row">
+ <div role="rowheader">row1</div>
+ <div role="gridcell">cell1</div>
+ </div>
+ </div>
+ <div id="gridWithDisplayContents" role="grid" style="display:contents;">
+ <div role="row">
+ <div role="columnheader">col1</div>
+ <div role="columnheader">col2</div>
+ </div>
+ <div role="row">
+ <div role="rowheader">row1</div>
+ <div role="gridcell">cell1</div>
+ </div>
+ </div>
+ <div id="gridWithDisplayContentsRow" role="grid">
+ <div role="row" style="display:contents;">
+ <div role="columnheader">col1</div>
+ <div role="columnheader">col2</div>
+ </div>
+ <div role="row">
+ <div role="rowheader">row1</div>
+ <div role="gridcell">cell1</div>
+ </div>
+ </div>
+ <div id="gridWithDisplayContentsColHeader" role="grid">
+ <div role="row">
+ <div role="columnheader" style="display:contents;">col1</div>
+ <div role="columnheader">col2</div>
+ </div>
+ <div role="row">
+ <div role="rowheader">row1</div>
+ <div role="gridcell">cell1</div>
+ </div>
+ </div>
+ <div id="gridWithDisplayContentsRowHeader" role="grid">
+ <div role="row">
+ <div role="columnheader">col1</div>
+ <div role="columnheader">col2</div>
+ </div>
+ <div role="row">
+ <div role="rowheader" style="display:contents;">row1</div>
+ <div role="gridcell">cell1</div>
+ </div>
+ </div>
+ <div id="gridWithDisplayContentsGridCell" role="grid">
+ <div role="row">
+ <div role="columnheader">col1</div>
+ <div role="columnheader">col2</div>
+ </div>
+ <div role="row">
+ <div role="rowheader">row1</div>
+ <div role="gridcell" style="display:contents;">cell1</div>
+ </div>
+ </div>
+
+ <div id="container">
+ <div role="list" style="display: contents;">
+ <div role="listitem">test</div>
+ </div>
+ <div aria-label="test" style="display: contents;">
+ <div role="listitem">test</div>
+ </div>
+ <div role="none" style="display: contents;">
+ <div role="listitem">test</div>
+ </div>
+ </div>
+
+ <div id="paragraphContainer">
+ <p style="display: contents;">test</p>
+ <p style="display: contents;" role="none">test</p>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_globals.html b/accessible/tests/mochitest/tree/test_aria_globals.html
new file mode 100644
index 0000000000..bb5fe14cdf
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_globals.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test Global ARIA States and Accessible Creation</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var globalIds = [
+ "atomic",
+ "busy",
+ "controls",
+ "describedby",
+ "description",
+ "disabled",
+ "dropeffect",
+ "flowto",
+ "grabbed",
+ "haspopup",
+ "invalid",
+ "label",
+ "labelledby",
+ "live",
+ "owns",
+ "relevant",
+ ];
+
+ // Elements having ARIA global state or properties or referred by another
+ // element must be accessible.
+ ok(isAccessible("pawn"),
+ "Must be accessible because referred by another element.");
+
+ for (let idx = 0; idx < globalIds.length; idx++) {
+ ok(isAccessible(globalIds[idx]),
+ "Must be accessible becuase of aria-" + globalIds[idx] +
+ " presence");
+ }
+
+ // Unfocusable elements, having ARIA global state or property with a valid
+ // IDREF value, and an inherited presentation role. A generic accessible
+ // is created (to prevent table cells text jamming).
+ ok(!isAccessible("td_nothing", nsIAccessibleTableCell),
+ "inherited presentation role takes a place");
+
+ for (let idx = 0; idx < globalIds.length; idx++) {
+ ok(isAccessible("td_" + globalIds[idx]),
+ "Inherited presentation role must be ignored becuase of " +
+ "aria-" + globalIds[idx] + " presence");
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Update universal ARIA attribute support to latest spec"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=551978">
+ Mozilla Bug 551978
+ </a>
+ <a target="_blank"
+ title="Presentational table related elements referred or having global ARIA attributes must be accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=809751">
+ Mozilla Bug 809751
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Test that global aria states and properties are enough to cause the
+ creation of accessible objects -->
+ <div id="global_aria_states_and_props" role="group">
+ <span id="pawn"></span>
+ <span id="atomic" aria-atomic="true"></span>
+ <span id="busy" aria-busy="false"></span>
+ <span id="controls" aria-controls="pawn"></span>
+ <span id="describedby" aria-describedby="pawn"></span>
+ <span id="description" aria-description="hi"></span>
+ <span id="disabled" aria-disabled="true"></span>
+ <span id="dropeffect" aria-dropeffect="move"></span>
+ <span id="flowto" aria-flowto="pawn"></span>
+ <span id="grabbed" aria-grabbed="false"></span>
+ <span id="haspopup" aria-haspopup="false"></span>
+ <span id="invalid" aria-invalid="false"></span>
+ <span id="label" aria-label="hi"></span>
+ <span id="labelledby" aria-labelledby="label"></span>
+ <span id="live" aria-live="polite"></span>
+ <span id="owns" aria-owns="pawn"></span>
+ <span id="relevant" aria-relevant="additions"></span>
+ </div>
+
+ <table role="presentation">
+ <tr>
+ <td id="td_nothing"></td>
+ <td id="td_atomic" aria-atomic="true"></td>
+ <td id="td_busy" aria-busy="false"></td>
+ <td id="td_controls" aria-controls="pawn"></td>
+ <td id="td_describedby" aria-describedby="pawn"></td>
+ <td id="td_description" aria-description="hi"></td>
+ <td id="td_disabled" aria-disabled="true"></td>
+ <td id="td_dropeffect" aria-dropeffect="move"></td>
+ <td id="td_flowto" aria-flowto="pawn"></td>
+ <td id="td_grabbed" aria-grabbed="false"></td>
+ <td id="td_haspopup" aria-haspopup="false"></td>
+ <td id="td_invalid" aria-invalid="false"></td>
+ <td id="td_label" aria-label="hi"></td>
+ <td id="td_labelledby" aria-labelledby="label"></td>
+ <td id="td_live" aria-live="polite"></td>
+ <td id="td_owns" aria-owns="pawn"></td>
+ <td id="td_relevant" aria-relevant="additions"></td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_grid.html b/accessible/tests/mochitest/tree/test_aria_grid.html
new file mode 100644
index 0000000000..80ff97095b
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_grid.html
@@ -0,0 +1,318 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML table tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // grid having rowgroups
+
+ var accTree =
+ { TABLE: [
+ { GROUPING: [
+ { ROW: [
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] },
+ ] };
+
+ testAccessibleTree("grid", accTree);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // strange grids (mix of ARIA and HTML tables)
+
+ accTree = {
+ role: ROLE_TABLE,
+ children: [
+ { // div@role="row"
+ role: ROLE_ROW,
+ tagName: "DIV",
+ children: [
+ { // caption text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "caption",
+ children: [ ],
+ },
+ { // th generic accessible
+ role: ROLE_TEXT_CONTAINER,
+ children: [
+ { // th text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "header1",
+ children: [ ],
+ },
+ ],
+ },
+ { // td@role="columnheader"
+ role: ROLE_COLUMNHEADER,
+ name: "header2",
+ children: [ { TEXT_LEAF: [ ] } ],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree("strange_grid1", accTree);
+
+ accTree = {
+ role: ROLE_TABLE,
+ children: [
+ { // tr@role="row"
+ role: ROLE_ROW,
+ tagName: "TR",
+ children: [
+ { // td implicit role="gridcell"
+ role: ROLE_GRID_CELL,
+ children: [
+ { // td text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "cell1",
+ children: [ ],
+ },
+ ],
+ },
+ { // td@role="gridcell"
+ role: ROLE_GRID_CELL,
+ name: "cell2",
+ children: [ { TEXT_LEAF: [ ] } ],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree("strange_grid2", accTree);
+
+ accTree = {
+ role: ROLE_TABLE,
+ children: [
+ { // div@role="row"
+ role: ROLE_ROW,
+ children: [
+ { // div@role="gridcell"
+ role: ROLE_GRID_CELL,
+ children: [
+ { // td generic accessible
+ role: ROLE_TEXT_CONTAINER,
+ children: [
+ { // text leaf from presentational table
+ role: ROLE_TEXT_LEAF,
+ name: "cell3",
+ children: [ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree("strange_grid3", accTree);
+
+ accTree = {
+ role: ROLE_TABLE,
+ children: [
+ { // div@role="row"
+ role: ROLE_ROW,
+ children: [
+ { // div@role="gridcell"
+ role: ROLE_GRID_CELL,
+ children: [
+ { // table
+ role: ROLE_TABLE,
+ children: [
+ { // tr
+ role: ROLE_ROW,
+ children: [
+ { // td
+ role: ROLE_CELL,
+ children: [
+ { // caption text leaf of presentational table
+ role: ROLE_TEXT_LEAF,
+ name: "caption",
+ children: [ ],
+ },
+ { // td generic accessible
+ role: ROLE_TEXT_CONTAINER,
+ children: [
+ { // td text leaf of presentational table
+ role: ROLE_TEXT_LEAF,
+ name: "cell4",
+ children: [ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ testAccessibleTree("strange_grid4", accTree);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // grids that could contain whitespace accessibles but shouldn't.
+
+ accTree =
+ { TREE_TABLE: [
+ { ROW: [
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] };
+
+ testAccessibleTree("whitespaces-grid", accTree);
+
+ // grids that could contain text container accessibles but shouldn't.
+
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ { ROW: [
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] };
+
+ testAccessibleTree("gridWithPresentationalBlockElement", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Support ARIA role rowgroup"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=525909">
+ Mozilla Bug 525909
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="grid" role="grid">
+ <div role="rowgroup">
+ <div role="row">
+ <div role="gridcell">cell</div>
+ </div>
+ </div>
+ </div>
+
+ <div id="strange_grid1" role="grid">
+ <div role="row">
+ <table role="presentation">
+ <caption>caption</caption>
+ <tr>
+ <th>header1</th>
+ <td role="columnheader">header2</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <div id="strange_grid2" role="grid">
+ <table role="presentation">
+ <tr role="row">
+ <td id="implicit_gridcell">cell1</td>
+ <td role="gridcell">cell2</td>
+ </tr>
+ </table>
+ </div>
+
+ <div id="strange_grid3" role="grid">
+ <div role="row">
+ <div role="gridcell">
+ <table role="presentation">
+ <tr>
+ <td>cell3</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
+
+ <div id="strange_grid4" role="grid">
+ <div role="row">
+ <div role="gridcell">
+ <table>
+ <tr>
+ <td>
+ <table role="presentation">
+ <caption>caption</caption>
+ <tr><td>cell4</td></tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
+
+ <div role="treegrid" id="whitespaces-grid">
+ <div role="row" aria-selected="false" tabindex="-1">
+ <span role="gridcell">03:30PM-04:30PM</span>
+ <span role="gridcell" style="font-weight:bold;">test</span>
+ <span role="gridcell">a user1</span>
+ </div>
+ </div>
+
+ <div id="gridWithPresentationalBlockElement" role="grid">
+ <span style="display: block;">
+ <div role="row">
+ <div role="gridcell">Cell 1</div>
+ <div role="gridcell">Cell 2</div>
+ </div>
+ </span>
+ <span style="display: block;">
+ <div role="row">
+ <span style="display: block;">
+ <div role="gridcell">Cell 3</div>
+ <div role="gridcell">Cell 4</div>
+ </span>
+ </div>
+ </span>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_imgmap.html b/accessible/tests/mochitest/tree/test_aria_imgmap.html
new file mode 100644
index 0000000000..0cd4867cae
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_imgmap.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test usemap elements and ARIA</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ // gA11yEventDumpToConsole = true;
+ function doPreTest() {
+ waitForImageMap("imagemap", doTest);
+ }
+
+ function doTest() {
+ var accTree = {
+ role: ROLE_IMAGE_MAP,
+ children: [
+ {
+ role: ROLE_ENTRY,
+ name: "first name",
+ },
+ {
+ role: ROLE_ENTRY,
+ name: "last name",
+ },
+ {
+ role: ROLE_RADIOBUTTON,
+ name: "male",
+ },
+ {
+ role: ROLE_RADIOBUTTON,
+ name: "female",
+ },
+ {
+ role: ROLE_CHECKBUTTON,
+ name: "have bike",
+ },
+ {
+ role: ROLE_EDITCOMBOBOX,
+ name: "bike model",
+ },
+ {
+ role: ROLE_CHECKBUTTON,
+ name: "have car",
+ },
+ {
+ role: ROLE_CHECKBUTTON,
+ name: "have airplane",
+ },
+ {
+ role: ROLE_PUSHBUTTON,
+ name: "submit",
+ },
+ ],
+ };
+
+ // Test image map tree structure, roles, and names.
+ testAccessibleTree("imagemap", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+
+</head>
+<body>
+
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=548291"
+ title="Accessible tree of ARIA image maps">
+Mozilla Bug 548291
+</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+
+<img id="imagemap" src="../formimage.png" width="219" height="229" border="0" usemap="#ariaMap">
+<map id="ariaMap" name="ariaMap">
+ <area id="t1" role="textbox" shape="rect" tabindex="0" alt="" title="first name" coords="4,20,108,48" href="#" />
+ <area id="t2" role="textbox" shape="rect" alt="" title="last name" coords="111,21,215,50" href="#" />
+ <area id="rb1" role="radio" aria-checked="true" shape="circle" alt="" title="male" coords="60,75,11" href="#" />
+ <area id="rb2" role="radio" shape="circle" alt="" title="female" coords="73,94,11" href="#" />
+ <area id="cb1" role="checkbox" aria-checked="true" shape="rect" alt="" title="have bike" coords="95,123,118,145" href="#" />
+ <area id="cbox" role="combobox" shape="rect" alt="" title="bike model" coords="120,124,184,146" href="#" />
+ <area id="cb2" role="checkbox" shape="rect" alt="" title="have car" coords="90,145,114,164" href="#" />
+ <area id="cb3" role="checkbox" shape="rect" alt="" title="have airplane" coords="130,163,152,184" href="#" />
+ <area id="b1" role="button" shape="rect" alt="" title="submit" coords="4,198,67,224" href="#" />
+</map>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_list.html b/accessible/tests/mochitest/tree/test_aria_list.html
new file mode 100644
index 0000000000..f3642c801d
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_list.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>ARIA lists</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // list
+
+ var accTree =
+ { LIST: [
+ { LISTITEM: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] };
+
+ testAccessibleTree("list", accTree);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // strange list (mix of ARIA and HTML)
+
+ accTree = { // div@role="list"
+ role: ROLE_LIST,
+ children: [
+ { // li
+ role: ROLE_TEXT_CONTAINER,
+ children: [
+ { // li text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "item1",
+ children: [ ],
+ },
+ ],
+ },
+ { // li@role="listitem"
+ role: ROLE_LISTITEM,
+ children: [
+ { // text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "item2",
+ children: [ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ testAccessibleTree("strange_list", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Build the context dependent tree"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=804461">
+ Mozilla Bug 804461
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="list" role="list">
+ <div role="listitem">item1</div>
+ </div>
+
+ <div id="strange_list" role="list">
+ <ul role="presentation">
+ <li>item1</li>
+ <li role="listitem">item2</li>
+ </ul>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_menu.html b/accessible/tests/mochitest/tree/test_aria_menu.html
new file mode 100644
index 0000000000..2f1f6645db
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_menu.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test accessible tree when ARIA role menuitem is used</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // Menuitem with no popup.
+ let tree =
+ { SECTION: [ // container
+ { MENUPOPUP: [ // menu
+ { MENUITEM: [
+ { LISTITEM_MARKER: [] }, // bullet
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("menu", tree);
+
+ // Menuitem with explicit no popup.
+ tree =
+ { SECTION: [ // container
+ { MENUPOPUP: [ // menu
+ { MENUITEM: [
+ { LISTITEM_MARKER: [] }, // bullet
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("menu_nopopup", tree);
+
+ // Menuitem with popup.
+ tree =
+ { SECTION: [ // container
+ { MENUPOPUP: [ // menu
+ { PARENT_MENUITEM: [ // menuitem with aria-haspopup="true"
+ { LISTITEM_MARKER: [] }, // bullet
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("menu_popup", tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=786566"
+ title="ARIA menuitem acting as submenu should have PARENT_MENUITEM role">
+ Mozilla Bug 786566
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="menu">
+ <ul role="menu">
+ <li role="menuitem">Normal Menu</li>
+ </ul>
+ </div>
+
+ <div id="menu_nopopup">
+ <ul role="menu">
+ <li role="menuitem" aria-haspopup="false">Menu with explicit no popup</li>
+ </ul>
+ </div>
+
+ <div id="menu_popup">
+ <ul role="menu">
+ <li role="menuitem" aria-haspopup="true">Menu with popup</li>
+ </ul>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_owns.html b/accessible/tests/mochitest/tree/test_aria_owns.html
new file mode 100644
index 0000000000..a01968521b
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_owns.html
@@ -0,0 +1,197 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>@aria-owns attribute testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Tests
+ // //////////////////////////////////////////////////////////////////////////
+
+ // enableLogging("tree,verbose"); // debug stuff
+
+ var gQueue = null;
+
+ function doTest() {
+ var tree =
+ { SECTION: [ // t1_1
+ { HEADING: [ // t1_2
+ // no kids, no loop
+ ] },
+ ] };
+ testAccessibleTree("t1_1", tree);
+
+ tree =
+ { SECTION: [ // t2_1
+ { GROUPING: [ // t2_2
+ { HEADING: [ // t2_3
+ // no kids, no loop
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("t2_1", tree);
+
+ tree =
+ { SECTION: [ // t3_3
+ { GROUPING: [ // t3_1
+ { NOTE: [ // t3_2
+ { HEADING: [ // DOM child of t3_2
+ // no kids, no loop
+ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("t3_3", tree);
+
+ tree =
+ { SECTION: [ // t4_1
+ { GROUPING: [ // DOM child of t4_1, aria-owns ignored
+ // no kids, no loop
+ ] },
+ ] };
+ testAccessibleTree("t4_1", tree);
+
+ tree =
+ { SECTION: [ // t5_1
+ { GROUPING: [ // DOM child of t5_1
+ { NOTE: [ // t5_2
+ { HEADING: [ // DOM child of t5_2
+ { FORM: [ // t5_3
+ { TOOLTIP: [ // DOM child of t5_3
+ // no kids, no loop
+ ]},
+ ]},
+ ]},
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("t5_1", tree);
+
+ tree =
+ { SECTION: [ // t6_1
+ { RADIOBUTTON: [ ] },
+ { CHECKBUTTON: [ ] }, // t6_3, rearranged by aria-owns
+ { PUSHBUTTON: [ ] }, // t6_2, rearranged by aria-owns
+ ] };
+ testAccessibleTree("t6_1", tree);
+
+ tree =
+ { SECTION: [ // ariaowns_container
+ { SECTION: [ // ariaowns_self
+ { SECTION: [ // ariaowns_uncle
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("ariaowns_container", tree);
+
+ tree =
+ { TABLE: [
+ { ROW: [
+ { GRID_CELL: [
+ { TEXT_LEAF: [] },
+ ] },
+ { GRID_CELL: [
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ { ROW: [
+ { GRID_CELL: [
+ { TEXT_LEAF: [] },
+ ] },
+ { GRID_CELL: [
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("grid", tree);
+
+ tree =
+ { SECTION: [ // presentation_owner
+ // Can't own ancestor, so no children.
+ ] };
+ testAccessibleTree("presentation_owner", tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+
+ </script>
+</head>
+
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- simple loop -->
+ <div id="t1_1" aria-owns="t1_2"></div>
+ <div id="t1_2" aria-owns="t1_1" role="heading"></div>
+
+ <!-- loop -->
+ <div id="t2_2" aria-owns="t2_3" role="group"></div>
+ <div id="t2_1" aria-owns="t2_2"></div>
+ <div id="t2_3" aria-owns="t2_1" role="heading"></div>
+
+ <!-- loop #2 -->
+ <div id="t3_1" aria-owns="t3_2" role="group"></div>
+ <div id="t3_2" role="note">
+ <div aria-owns="t3_3" role="heading"></div>
+ </div>
+ <div id="t3_3" aria-owns="t3_1"></div>
+
+ <!-- self loop -->
+ <div id="t4_1"><div aria-owns="t4_1" role="group"></div></div>
+
+ <!-- natural and aria-owns hierarchy -->
+ <div id="t5_2" role="note"><div aria-owns="t5_3" role="heading"></div></div>
+ <div id="t5_1"><div aria-owns="t5_2" role="group"></div></div>
+ <div id="t5_3" role="form"><div aria-owns="t5_1" role="tooltip"></div></div>
+
+ <!-- rearrange children -->
+ <div id="t6_1" aria-owns="t6_3 t6_2">
+ <div id="t6_2" role="button"></div>
+ <div id="t6_3" role="checkbox"></div>
+ <div role="radio"></div>
+ </div>
+
+ <div id="ariaowns_container">
+ <div id="ariaowns_self"
+ aria-owns="aria_ownscontainer ariaowns_self ariaowns_uncle"></div>
+ </div>
+ <div id="ariaowns_uncle"></div>
+
+ <!-- grid -->
+ <div aria-owns="grid-row2" role="grid" id="grid">
+ <div role="row">
+ <div role="gridcell">cell 1,1</div>
+ <div role="gridcell">cell 1,2</div>
+ </div>
+ </div>
+ <div role="row" id="grid-row2">
+ <div role="gridcell">cell 2,1</div>
+ <div role="gridcell">cell 2,2</div>
+ </div>
+
+ <!-- Owned child which is an ancestor of its owner but didn't yet exist when
+ aria-owns relocation was processed (bug 1485097). -->
+ <div id="presentation" role="presentation">
+ <div id="presentation_owner" aria-owns="presentation"></div>
+ </div>
+</body>
+
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_presentation.html b/accessible/tests/mochitest/tree/test_aria_presentation.html
new file mode 100644
index 0000000000..5680193441
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_presentation.html
@@ -0,0 +1,176 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test accessible tree when ARIA role presentation is used</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // Presentation role don't allow accessible.
+ var tree =
+ { SECTION: [ // container
+ { TEXT_LEAF: [ ] }, // child text of 'presentation' node
+ { TEXT_LEAF: [ ] }, // child text of 'none' node
+ ] };
+ testAccessibleTree("div_cnt", tree);
+
+ // Focusable element, 'presentation' and 'none' roles are ignored.
+ tree =
+ { SECTION: [ // container
+ { PUSHBUTTON: [ // button having 'presentation' role
+ { TEXT_LEAF: [ ] },
+ ] },
+ { PUSHBUTTON: [ // button having 'none' role
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] };
+ testAccessibleTree("btn_cnt", tree);
+
+ // Presentation table, no table structure is exposed.
+ tree =
+ { SECTION: [ // container
+ { TEXT_CONTAINER: [ // td generic accessible inside 'presentation' table
+ { TEXT_LEAF: [ ] }, // cell text
+ ] },
+ { TEXT_CONTAINER: [ // td generic accessible inside 'none' table
+ { TEXT_LEAF: [ ] }, // cell text
+ ] },
+ ] };
+ testAccessibleTree("tbl_cnt", tree);
+
+ // Focusable table, 'presentation' and 'none' roles are ignored.
+ tree =
+ { SECTION: [ // container
+ { TABLE: [ // table having 'presentation' role
+ { ROW: [ // tr
+ { CELL: [ // td
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] },
+ { TABLE: [ // table having 'none' role
+ { ROW: [ // tr
+ { CELL: [ // td
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("tblfocusable_cnt", tree);
+
+ // Presentation list, expose generic accesisble for list items.
+ tree =
+ { SECTION: [ // container
+ { TEXT_CONTAINER: [ // li generic accessible inside 'presentation' role
+ { TEXT_LEAF: [ ] }, // li text
+ ] },
+ { TEXT_CONTAINER: [ // li generic accessible inside 'none' role
+ { TEXT_LEAF: [ ] }, // li text
+ ] },
+ ] };
+ testAccessibleTree("list_cnt", tree);
+
+ // Has ARIA globals or referred by ARIA relationship, role='presentation'
+ // and role='none' are ignored.
+ tree =
+ { SECTION: [ // container
+ { LABEL: [ // label, has aria-owns
+ { TEXT_LEAF: [ ] },
+ { LABEL: [ // label, referenced by aria-owns
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ { LABEL: [ // label, has aria-owns
+ { TEXT_LEAF: [ ] },
+ { LABEL: [ // label, referenced by aria-owns
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("airaglobalprop_cnt", tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=548291"
+ title="Accessible tree of ARIA image maps">
+ Bug 548291
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=666504"
+ title="Ignore role presentation on focusable elements">
+ Bug 666504
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=971212"
+ title="Implement ARIA role=none">
+ Bug 971212
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="div_cnt"><div role="presentation">t</div><div role="none">t</div></div>
+
+ <div id="btn_cnt"><button role="presentation">btn</button><button role="none">btn</button></div>
+
+ <div id="tbl_cnt">
+ <table role="presentation">
+ <tr>
+ <td>cell</td>
+ </tr>
+ </table>
+ <table role="none">
+ <tr>
+ <td>cell</td>
+ </tr>
+ </table>
+ </div>
+
+ <div id="tblfocusable_cnt">
+ <table role="presentation" tabindex="0">
+ <tr>
+ <td>cell</td>
+ </tr>
+ </table>
+ <table role="none" tabindex="0">
+ <tr>
+ <td>cell</td>
+ </tr>
+ </table>
+ </div>
+
+ <div id="list_cnt">
+ <ul role="presentation">
+ <li>item</li>
+ </ul>
+ <ul role="none">
+ <li>item</li>
+ </ul>
+ </div>
+
+ <div id="airaglobalprop_cnt"><label
+ role="presentation" aria-owns="ariaowned">has aria-owns</label><label
+ role="presentation" id="ariaowned">referred by aria-owns</label><label
+ role="none" aria-owns="ariaowned2">has aria-owns</label><label
+ role="none" id="ariaowned2">referred by aria-owns</label></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_table.html b/accessible/tests/mochitest/tree/test_aria_table.html
new file mode 100644
index 0000000000..22375faf59
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_table.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>ARIA table tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // table having rowgroups
+
+ var accTree =
+ { TABLE: [
+ { GROUPING: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] },
+ ] };
+
+ testAccessibleTree("table", accTree);
+
+ // tables that could contain text container accessibles but shouldn't.
+
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] };
+
+ testAccessibleTree("tableWithPresentationalBlockElement", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="support ARIA table and cell roles"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173364">
+ Bug 1173364
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="table" role="table">
+ <div role="rowgroup">
+ <div role="row">
+ <div role="cell">cell</div>
+ </div>
+ </div>
+ </div>
+
+ <div id="tableWithPresentationalBlockElement" role="table">
+ <span style="display: block;">
+ <div role="row">
+ <div role="cell">Cell 1</div>
+ <div role="cell">Cell 2</div>
+ </div>
+ </span>
+ <span style="display: block;">
+ <div role="row">
+ <span style="display: block;">
+ <div role="cell">Cell 3</div>
+ <div role="cell">Cell 4</div>
+ </span>
+ </div>
+ </span>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_brokencontext.html b/accessible/tests/mochitest/tree/test_brokencontext.html
new file mode 100644
index 0000000000..fbdefd398f
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_brokencontext.html
@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Broken context hierarchy</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Return true if TD element has a generic accessible.
+ */
+ function isTDGeneric(aID) {
+ return isAccessible(aID) && !isAccessible(aID, nsIAccessibleTableCell);
+ }
+
+ function checkIfNotAccessible(aID) {
+ ok(!isAccessible(aID), "'" + aID + "' shouldn't be accessible");
+ }
+ function checkIfTDGeneric(aID) {
+ ok(isTDGeneric(aID), "'" + aID + "' shouldn't have cell accessible");
+ }
+
+ function doTest() {
+ // //////////////////////////////////////////////////////////////////////////
+ // HTML table elements outside table context.
+
+ // HTML table role="presentation"
+ checkIfNotAccessible("tr_in_presentation_table");
+ checkIfTDGeneric("th_in_presentation_table");
+ checkIfTDGeneric("td_in_presentation_table");
+
+ // HTML table role="button"
+ var tree =
+ { PUSHBUTTON: [ // table
+ { TEXT_CONTAINER: [ // tr
+ { TEXT_CONTAINER: [ // th
+ { TEXT_LEAF: [ ] },
+ ] },
+ { TEXT_CONTAINER: [ // td
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("button_table", tree);
+
+ // //////////////////////////////////////////////////////////////////////////
+ // HTML list elements outside list context.
+
+ ok(!isAccessible("presentation_ul"),
+ "presentational ul shouldn't be accessible");
+ ok(isAccessible("item_in_presentation_ul"),
+ "li in presentational ul should have generic accessible");
+ ok(isAccessible("styleditem_in_presentation_ul"),
+ "list styled span in presentational ul should have generic accessible");
+
+ ok(!isAccessible("presentation_ol"),
+ "presentational ol shouldn't be accessible");
+ ok(isAccessible("item_in_presentation_ol"),
+ "li in presentational ol should have generic accessible");
+
+ ok(!isAccessible("presentation_dl"),
+ "presentational dl shouldn't be accessible");
+ ok(!isAccessible("dt_in_presentation_dl"),
+ "dt in presentational dl shouldn't be accessible");
+ ok(!isAccessible("dd_in_presentation_dl"),
+ "dd in presentational dl shouldn't be accessible");
+
+ tree =
+ { PUSHBUTTON: [ // ul
+ { TEXT_CONTAINER: [ // li
+ { LISTITEM_MARKER: [ ] },
+ { TEXT_LEAF: [ ] },
+ ] },
+ { TEXT_CONTAINER: [ // span styled as a list
+ { LISTITEM_MARKER: [ ] },
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] };
+ testAccessibleTree("button_ul", tree);
+
+ tree =
+ { PUSHBUTTON: [ // ol
+ { TEXT_CONTAINER: [ // li
+ { LISTITEM_MARKER: [ ] },
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] };
+ testAccessibleTree("button_ol", tree);
+
+ tree =
+ { PUSHBUTTON: [ // dl
+ { TEXT_CONTAINER: [ // dt
+ { TEXT_LEAF: [ ] },
+ ] },
+ { TEXT_CONTAINER: [ // dd
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] };
+ testAccessibleTree("button_dl", tree);
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Styled as HTML table elements, accessible is created by tag name
+
+ tree =
+ { LINK: [ // a
+ { TEXT_LEAF: [ ] },
+ ] };
+ testAccessibleTree("a_as_td", tree);
+
+ tree =
+ { HEADING: [
+ { TEXT_LEAF: [ ] },
+ ] };
+ testAccessibleTree("h1_as_td", tree);
+ testAccessibleTree("h2_as_td", tree);
+ testAccessibleTree("h3_as_td", tree);
+ testAccessibleTree("h4_as_td", tree);
+ testAccessibleTree("h5_as_td", tree);
+ testAccessibleTree("h6_as_td", tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=706849"
+ title="Create accessible by tag name as fallback if table descendant style is used out of table context">
+ Bug 706849
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=804461"
+ title="Build the context dependent tree ">
+ Bug 804461
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=945435"
+ title="Create generic accessible for td to not jamm the cell text">
+ Bug 945435
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- HTML table elements out of table -->
+ <table role="presentation">
+ <tr id="tr_in_presentation_table">
+ <th id="th_in_presentation_table">not a header</th>
+ <td id="td_in_presentation_table">not a cell</td>
+ </tr>
+ </table>
+
+ <table role="button" id="button_table">
+ <tr id="tr_in_button_table">
+ <th id="th_in_button_table">not a header</th>
+ <td id="td_in_button_table">not a cell</td>
+ </tr>
+ </table>
+
+ <!-- HTML list elements out of list -->
+ <ul role="presentation" id="presentation_ul">
+ <li id="item_in_presentation_ul">item</li>
+ <span id="styleditem_in_presentation_ul"
+ style="display:list-item">Oranges</span>
+ </ul>
+
+ <ol role="presentation" id="presentation_ol">
+ <li id="item_in_presentation_ol">item</li>
+ </ol>
+
+ <dl role="presentation" id="presentation_dl">
+ <dt id="dt_in_presentation_dl">term</dt>
+ <dd id="dd_in_presentation_dl">definition</dd>
+ </dl>
+
+ <ul role="button" id="button_ul">
+ <li id="item_in_button_ul">item</li>
+ <span id="styleditem_in_button_ul"
+ style="display:list-item">Oranges</span>
+ </ul>
+
+ <ol role="button" id="button_ol">
+ <li id="item_in_button_ul">item</li>
+ </ol>
+
+ <dl role="button" id="button_dl">
+ <dt id="dt_in_button_dl">term</ld>
+ <dd id="dd_in_button_dl">definition</dd>
+ </dl>
+
+ <!-- styled as HTML table elements -->
+ <a id="a_as_td" style="display:table-cell;" href="http://www.google.com">Google</a>
+ <h1 id="h1_as_td" style="display: table-cell;">h1</h1>
+ <h2 id="h2_as_td" style="display: table-cell;">h2</h2>
+ <h3 id="h3_as_td" style="display: table-cell;">h3</h3>
+ <h4 id="h4_as_td" style="display: table-cell;">h4</h4>
+ <h5 id="h5_as_td" style="display: table-cell;">h5</h5>
+ <h6 id="h6_as_td" style="display: table-cell;">h6</h6>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_button.xhtml b/accessible/tests/mochitest/tree/test_button.xhtml
new file mode 100644
index 0000000000..fec453b717
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_button.xhtml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL button hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // button
+
+ var accTree = {
+ role: ROLE_PUSHBUTTON,
+ name: "hello",
+ children: [ ]
+ };
+ testAccessibleTree("button1", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // toolbarbutton
+
+ accTree = {
+ role: ROLE_PUSHBUTTON,
+ name: "hello",
+ children: [ ]
+ };
+ testAccessibleTree("button2", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // toolbarbutton with type="checkbox"
+
+ accTree = {
+ role: ROLE_TOGGLE_BUTTON,
+ name: "hello",
+ children: [ ]
+ };
+ testAccessibleTree("button3", accTree);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292"
+ title="Ensure accessible children for toolbarbutton types 'menu'">
+ Mozilla Bug 249292
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <button id="button1" label="hello"/>
+ <toolbarbutton id="button2" label="hello"/>
+ <toolbarbutton id="button3" type="checkbox" label="hello"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/tree/test_canvas.html b/accessible/tests/mochitest/tree/test_canvas.html
new file mode 100644
index 0000000000..804bcc1f6e
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_canvas.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=495912
+-->
+<head>
+ <title>File Input Control tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var accTree =
+ { CANVAS: [
+ { CHECKBUTTON: [] },
+ { ENTRY: [] },
+ ] };
+
+ testAccessibleTree("canvas", accTree);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Expose alternative content in Canvas element to ATs"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">Mozilla Bug 495912</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <canvas id="canvas" tabindex="0"><input type="checkbox"><input></canvas>
+
+ <script type="text/javascript">
+ var c = document.getElementById("canvas");
+ var cxt = c.getContext("2d");
+ cxt.fillStyle = "#005500";
+ cxt.fillRect(0, 0, 150, 75);
+ </script>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_combobox.xhtml b/accessible/tests/mochitest/tree/test_combobox.xhtml
new file mode 100644
index 0000000000..36e4e716c9
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_combobox.xhtml
@@ -0,0 +1,116 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Accessible XUL menulist and textbox @autocomplete hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // menulist
+
+ var selectedOptionChildren = [];
+ if (MAC) {
+ // checkmark is part of the Mac menu styling
+ selectedOptionChildren = [{
+ role: ROLE_STATICTEXT,
+ children: []
+ }];
+ }
+
+ var accTree = {
+ role: ROLE_COMBOBOX,
+ children: [
+ {
+ role: ROLE_COMBOBOX_LIST,
+ children: [
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: selectedOptionChildren
+ },
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: []
+ }
+ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("menulist", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // textbox@type=autocomplete #1 (history)
+
+ accTree = {
+ // html:input
+ role: ROLE_ENTRY,
+ children: [
+ {
+ // #text
+ role: ROLE_TEXT_LEAF,
+ name: "http://mochi.test:8888/redirect-a11y.html",
+ children: []
+ }
+ ],
+ };
+
+ testAccessibleTree("autocomplete", accTree);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292"
+ title="Ensure accessible children for toolbarbutton types 'menu'">
+ Mozilla Bug 249292
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660"
+ title="Cache rendered text on a11y side">
+ Mozilla Bug 626660
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menulist id="menulist">
+ <menupopup>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menulist>
+
+ <html:input is="autocomplete-input"
+ id="autocomplete"
+ value="http://mochi.test:8888/redirect-a11y.html"/>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/tree/test_cssflexbox.html b/accessible/tests/mochitest/tree/test_cssflexbox.html
new file mode 100644
index 0000000000..72fafba0a2
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_cssflexbox.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>CSS flexbox tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // Ensure that flexbox ordering and absolute positioning do not affect
+ // the accessibility tree.
+ // Note that there is no accessible for a div with display:flex style.
+ var accTree = {
+ role: ROLE_SECTION,
+ children: [
+ { // Bug 1277559. Button outside the flexed content
+ role: ROLE_PUSHBUTTON,
+ name: "Button",
+ },
+ { // Visually first button in the 3 button row
+ role: ROLE_PUSHBUTTON,
+ name: "First",
+ },
+ { // Flushed right third button in the 3 button row
+ role: ROLE_PUSHBUTTON,
+ name: "Second",
+ },
+ { // Middle button in the 3 button row
+ role: ROLE_PUSHBUTTON,
+ name: "Third",
+ }, // end bug 1277559
+ { // Bug 962558: DOM first, Order 2.
+ role: ROLE_PUSHBUTTON,
+ name: "two, tab first",
+ },
+ { // DOM order second, flex order 1
+ role: ROLE_PUSHBUTTON,
+ name: "one, tab second",
+ }, // end bug 962558
+ ],
+ };
+ testAccessibleTree("flex_elements", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="flex_elements">
+ <button type="button">Button</button>
+ <div style="position: relative; display: flex; width: 200px;">
+ <button type="button" style="order: 1">First</button>
+ <button type="button" style="order: 2; position: absolute; right: 0">Second</button>
+ <button type="button" style="order: 3">Third</button>
+ </div>
+ <div style="display: flex">
+ <button id="two" style="order: 2">two, tab first</button>
+ <button id="one" style="order: 1">one, tab second</button>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_cssoverflow.html b/accessible/tests/mochitest/tree/test_cssoverflow.html
new file mode 100644
index 0000000000..87898fef6c
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_cssoverflow.html
@@ -0,0 +1,135 @@
+<html>
+
+<head>
+ <title>CSS overflow testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ a.link:focus {
+ overflow: scroll;
+ }
+ </style>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function focusAnchor(aID) {
+ this.linkNode = getNode(aID);
+ this.link = getAccessible(this.linkNode);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getAccessible, this.linkNode),
+ ];
+
+ this.invoke = function focusAnchor_invoke() {
+ this.linkNode.focus();
+ };
+
+ this.check = function focusAnchor_check(aEvent) {
+ is(this.link, aEvent.accessible,
+ "Focus should be fired against new link accessible!");
+ };
+
+ this.getID = function focusAnchor_getID() {
+ return "focus a:focus{overflow:scroll} #1";
+ };
+ }
+
+ function tabAnchor(aID) {
+ this.linkNode = getNode(aID);
+ this.link = getAccessible(this.linkNode);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getAccessible, this.linkNode),
+ ];
+
+ this.invoke = function tabAnchor_invoke() {
+ synthesizeKey("VK_TAB", { shiftKey: false });
+ };
+
+ this.check = function tabAnchor_check(aEvent) {
+ is(this.link, aEvent.accessible,
+ "Focus should be fired against new link accessible!");
+ };
+
+ this.getID = function tabAnchor_getID() {
+ return "focus a:focus{overflow:scroll} #2";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ var gQueue = null;
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ function doTests() {
+ // Shift+Tab not working, and a test timeout, bug 746977
+ if (MAC) {
+ todo(false, "Shift+tab isn't working on OS X, needs to be disabled until bug 746977 is fixed!");
+ SimpleTest.finish();
+ return;
+ }
+
+ gQueue = new eventQueue();
+
+ // CSS 'overflow: scroll' property setting and unsetting causes accessible
+ // recreation (and fire show/hide events). For example, the focus and
+ // blur of HTML:a with ':focus {overflow: scroll; }' CSS style causes its
+ // accessible recreation. The focus event should be fired on new
+ // accessible.
+ gQueue.push(new focusAnchor("a"));
+ gQueue.push(new tabAnchor("a2"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=591163"
+ title="mochitest for bug 413777: focus the a:focus {overflow: scroll;} shouldn't recreate HTML a accessible">
+ Mozilla Bug 591163
+ </a><br>
+ <a target="_blank"
+ title="Rework accessible tree update code"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
+ Mozilla Bug 570275
+ </a><br>
+ <a target="_blank"
+ title="Text control frames should accept dynamic changes to the CSS overflow property"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=686247">
+ Mozilla Bug 686247
+ </a><br>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <div>
+ <a id="a" class="link" href="www">link</a>
+ </div>
+ <div>
+ <a id="a2" class="link" href="www">link2</a>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_display_contents.html b/accessible/tests/mochitest/tree/test_display_contents.html
new file mode 100644
index 0000000000..8393a35b41
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_display_contents.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>CSS display:contents tests</title>
+<link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="application/javascript"
+ src="../common.js"></script>
+<script type="application/javascript"
+ src="../role.js"></script>
+
+<script type="application/javascript">
+function doTest() {
+ let tree =
+ { LIST: [
+ { LISTITEM: [
+ { LISTITEM_MARKER: [] },
+ { TEXT_LEAF: [] },
+ ]},
+ { LISTITEM: [
+ { LISTITEM_MARKER: [] },
+ { TEXT_LEAF: [] },
+ ]},
+ ] };
+ testAccessibleTree("ul", tree);
+
+ tree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [{ TEXT_LEAF: [] } ] },
+ { CELL: [{ TEXT_LEAF: [] } ] },
+ ]},
+ ] };
+ testAccessibleTree("tableTableContents", tree);
+ testAccessibleTree("tableTrContents", tree);
+ testAccessibleTree("tableTdContents", tree);
+
+ tree =
+ { TABLE: [
+ { GROUPING : [
+ { ROW: [
+ { CELL: [{ TEXT_LEAF: [] } ] },
+ { CELL: [{ TEXT_LEAF: [] } ] },
+ ]},
+ ]},
+ ] };
+ testAccessibleTree("tableTbodyContents", tree);
+
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addA11yLoadEvent(doTest);
+</script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ul id="ul" style="display: contents;">
+ <li>Supermarket 1</li>
+ <li>Supermarket 2</li>
+ </ul>
+
+ <!-- The summary attribute in these tables ensures they are treated as data
+ tables. -->
+ <table id="tableTableContents" summary="summary" style="display: contents;">
+ <tr><td>a</td><td>b</td></tr>
+ </table>
+ <table id="tableTrContents" summary="table" style="display: block;">
+ <tr style="display: contents;"><td>a</td><td>b</td></tr>
+ </table>
+ <table id="tableTdContents" summary="summary">
+ <tr>
+ <td style="display: contents;">a</td>
+ <td style="display: contents;">b</td>
+ </tr>
+ </table>
+ <table id="tableTbodyContents" summary="summary" style="display: block;">
+ <tbody style="display: contents;">
+ <tr><td>a</td><td>b</td></tr>
+ </tbody>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_divs.html b/accessible/tests/mochitest/tree/test_divs.html
new file mode 100644
index 0000000000..24d610aef2
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_divs.html
@@ -0,0 +1,351 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>div element creation tests</title>
+<link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="application/javascript"
+ src="../common.js"></script>
+<script type="application/javascript"
+ src="../role.js"></script>
+<script type="application/javascript"
+ src="../attributes.js"></script>
+
+<script type="application/javascript">
+function getAccessibleDescendantFor(selector) {
+ return gAccService.getAccessibleDescendantFor(document.querySelector(selector));
+}
+
+function doTest() {
+ // All below test cases are wrapped in a div which always gets rendered.
+ // c1 through c10 are the containers, the actual test cases are inside.
+
+ // c1: Expose the div with text content
+ let tree =
+ { role: ROLE_SECTION, // outer div with ID
+ children: [
+ { role: ROLE_SECTION, // inner div
+ children: [
+ { TEXT_LEAF: [] },
+ ], // end children inner div
+ }, // end inner div
+ ], // end children outer div
+ };
+ testAccessibleTree("c1", tree);
+
+ // c2: Only the outermost and innermost divs are exposed.
+ // The middle one is skipped. This is identical to the above tree.
+ testAccessibleTree("c2", tree);
+
+ // c3: Make sure the inner div with ID is exposed, but the middle one is
+ // skipped.
+ tree =
+ { role: ROLE_SECTION, // outer div with ID
+ children: [
+ { role: ROLE_SECTION, // inner div
+ attributes: { id: "b" },
+ children: [
+ { TEXT_LEAF: [] },
+ ], // end children inner div
+ }, // end inner div
+ ], // end children outer div
+ };
+ testAccessibleTree("c3", tree);
+
+ // c4: Expose all three divs including the middle one due to its ID.
+ tree =
+ { role: ROLE_SECTION, // outer div with ID
+ children: [
+ { role: ROLE_SECTION, // middle div
+ attributes: { id: "a" },
+ children: [
+ { role: ROLE_SECTION, // inner div
+ attributes: { id: "b" },
+ children: [
+ { TEXT_LEAF: [] },
+ ], // end children inner div
+ }, // end inner div
+ ], // end children middle div
+ }, // end middle div
+ ], // end children outer div
+ };
+ testAccessibleTree("c4", tree);
+
+ // c5: Expose the inner div with class b due to its text contents.
+ tree =
+ { role: ROLE_SECTION, // outer div with ID
+ children: [
+ { role: ROLE_SECTION, // inner div with class and text
+ attributes: { class: "b" },
+ children: [
+ { TEXT_LEAF: [] },
+ ], // end children inner div
+ }, // end inner div
+ ], // end children outer div
+ };
+ testAccessibleTree("c5", tree);
+
+ // c6: Expose the outer div due to its ID, and the two inner divs due to
+ // their text contents. Skip the middle one.
+ tree =
+ { role: ROLE_SECTION, // outer div with ID
+ children: [
+ { role: ROLE_SECTION, // first inner div
+ children: [
+ { TEXT_LEAF: [] },
+ ], // end children first inner div
+ }, // end first inner div
+ { role: ROLE_SECTION, // second inner div
+ children: [
+ { TEXT_LEAF: [] },
+ ], // end children second inner div
+ }, // end second inner div
+ ], // end children outer div
+ };
+ testAccessibleTree("c6", tree);
+
+ // c7: Expose all three divs including the middle one due to it being block
+ // breaking.
+ tree =
+ { role: ROLE_SECTION, // outer div with ID
+ children: [
+ { role: ROLE_SECTION, // middle div
+ children: [
+ { TEXT_LEAF: [] }, // foo
+ { role: ROLE_SECTION, // inner div
+ children: [
+ { TEXT_LEAF: [] }, // bar
+ ], // end children inner div
+ }, // end inner div
+ { TEXT_LEAF: [] }, // baz
+ ], // end children middle div
+ }, // end middle div
+ ], // end children outer div
+ };
+ testAccessibleTree("c7", tree);
+
+ // c8: Expose all divs due to them all being text block breakers.
+ tree =
+ { role: ROLE_SECTION, // outer div with ID
+ children: [
+ { role: ROLE_SECTION, // foo div
+ children: [
+ { TEXT_LEAF: [] }, // foo
+ { role: ROLE_SECTION, // baz div
+ children: [
+ { role: ROLE_SECTION, // bar div
+ children: [
+ { TEXT_LEAF: [] }, // bar
+ ], // end children bar div
+ }, // end bar div
+ { TEXT_LEAF: [] }, // baz
+ ], // end children baz div
+ }, // end baz div
+ ], // end children foo div
+ }, // end foo div
+ ], // end children outer div
+ };
+ testAccessibleTree("c8", tree);
+
+ // c9: The same, but in a different nesting order.
+ tree =
+ { role: ROLE_SECTION, // outer div with ID
+ children: [
+ { role: ROLE_SECTION, // c div
+ children: [
+ { role: ROLE_SECTION, // b div
+ children: [
+ { role: ROLE_SECTION, // a div
+ children: [
+ { TEXT_LEAF: [] }, // a
+ ], // end children a div
+ }, // end a div
+ { TEXT_LEAF: [] }, // b
+ ], // end children b div
+ }, // end b div
+ { TEXT_LEAF: [] }, // c
+ ], // end children c div
+ }, // end foo div
+ ], // end children outer div
+ };
+ testAccessibleTree("c9", tree);
+
+ // c10: Both inner divs must be exposed so there is a break after b.
+ tree =
+ { role: ROLE_SECTION, // outer div with ID
+ children: [
+ { role: ROLE_SECTION, // first inner div
+ children: [
+ { TEXT_LEAF: [] }, // a
+ { TEXT_LEAF: [] }, // b
+ ], // end children first inner div
+ }, // end first inner div
+ { role: ROLE_SECTION, // second inner div
+ children: [
+ { TEXT_LEAF: [] }, // c
+ { TEXT_LEAF: [] }, // d
+ ], // end children second inner div
+ }, // end second inner div
+ ], // end children outer div
+ };
+ testAccessibleTree("c10", tree);
+
+ // c11: A div must be exposed if it contains a br element.
+ tree =
+ { role: ROLE_SECTION, // outer div
+ children: [
+ { role: ROLE_SECTION, // First line
+ children: [
+ { TEXT_LEAF: [] }, // text
+ ], // end children first line
+ }, // end first line
+ { role: ROLE_SECTION, // Second line
+ children: [
+ { WHITESPACE: [] }, // whitespace
+ ], // end children second line
+ }, // end second line
+ { role: ROLE_SECTION, // Third line
+ children: [
+ { TEXT_LEAF: [] }, // text
+ ], // end children third line
+ }, // end third line
+ ], // end children outer div
+ };
+ testAccessibleTree("c11", tree);
+
+ // c12: Div shouldn't be rendered if first/last child text node is invisible.
+ tree =
+ { role: ROLE_SECTION, // outer div
+ children: [
+ { role: ROLE_PARAGRAPH,
+ children: [
+ { TEXT_LEAF: [] }, // text
+ ],
+ },
+ ], // end children outer div
+ };
+ testAccessibleTree("c12", tree);
+
+ // c13: Div should be rendered if there is an inline frame after/before
+ // invisible text nodes.
+ tree =
+ { role: ROLE_SECTION, // outer div
+ children: [
+ { TEXT_LEAF: [] }, // l1
+ { role: ROLE_SECTION, // l2
+ children: [
+ { TEXT_LEAF: [] }, // l2
+ ], // end children l2
+ },
+ ], // end children outer div
+ };
+ testAccessibleTree("c13", tree);
+
+ // c14: Div should be rendered if it contains an inline-block.
+ tree =
+ { role: ROLE_SECTION, // outer div
+ children: [
+ { TEXT_LEAF: [] }, // l1
+ { role: ROLE_SECTION, // l2
+ children: [
+ { role: ROLE_PUSHBUTTON,
+ children: [
+ { TEXT_LEAF: [] },
+ ],
+ },
+ ], // end children l2
+ },
+ ], // end children outer div
+ };
+ testAccessibleTree("c14", tree);
+
+ // c15: Div should be rendered if previous sibling is text.
+ tree =
+ { role: ROLE_SECTION, // outer div
+ children: [
+ { TEXT_LEAF: [] }, // l1
+ { SECTION: [] }, // Block break
+ { TEXT_LEAF: [] }, // l2
+ ], // end children outer div
+ };
+ testAccessibleTree("c15", tree);
+
+ // Test getting descendants of unrendered nodes.
+ ok(!getAccessibleDescendantFor("#c16 > span"),
+ "Span has no accessible children");
+
+ ok(!getAccessibleDescendantFor("#c17 > span"),
+ "Span with relocated child should return null");
+
+ is(getAccessibleDescendantFor("#c12 > div").role, ROLE_PARAGRAPH,
+ "Descendant has correct role")
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addA11yLoadEvent(doTest);
+</script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Expose the div if it has plain text contents -->
+ <div id="c1"><div>foo</div></div>
+
+ <!-- Expose the outer and inner div, skip the middle one. -->
+ <div id="c2"><div><div>foo</div></div></div>
+
+ <!-- Expose the outer and inner divs due to the ID, but skip the middle one. -->
+ <div id="c3"><div><div id="b">foo</div></div></div>
+
+ <!-- Expose all three divs and their IDs. -->
+ <div id="c4"><div id="a"><div id="b">foo</div></div></div>
+
+ <!-- Expose outer and inner divs, due to text content, not class. -->
+ <div id="c5"><div class="a"><div class="b">foo</div></div></div>
+
+ <!-- Expose the outer and two inner divs, skip the single middle one. -->
+ <div id="c6"><div><div>foo</div><div>bar</div></div></div>
+
+ <!-- Expose all divs due to the middle one being block breaking. -->
+ <div id="c7"><div>foo<div>bar</div>baz</div></div>
+
+ <!-- Expose all divs due to them all being text block breakers. -->
+ <div id="c8"><div>foo<div><div>bar</div>baz</div></div></div>
+ <div id="c9"><div><div><div>a</div>b</div>c</div></div>
+
+ <!-- Both inner divs need to be rendered so there is a break after b. -->
+ <div id="c10"><div><b>a</b>b</div><div><b>c</b><b>d</b></div></div>
+
+ <!-- Div must be rendered if it contains a br -->
+ <div id="c11"><div>first line.</div><div><br /></div><div>third line</div></div>
+
+ <!-- Inner div shouldn't be rendered because although its first and last
+ children are text nodes, they are invisible.
+ -->
+ <div id="c12"><div> <p>Test</p> </div></div>
+
+ <!-- Inner div should be rendered because despite the first/last invisible
+ text nodes, there is also an inline frame.
+ -->
+ <div id="c13">l1<div> <span>l2 </span> </div></div>
+
+ <!-- Inner div should be rendered because it contains an inline-block. -->
+ <div id="c14">l1<div><button>l2</button></div></div>
+
+ <!-- Inner div should be rendered because previous sibling is text. -->
+ <div id="c15">l1<div></div>l2</div>
+
+ <div id="c16">hello <span></span> world</div>
+
+ <div id="c17"><div aria-owns="c"></div>hello <span><button id="c">b</button></span> world</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_dochierarchy.html b/accessible/tests/mochitest/tree/test_dochierarchy.html
new file mode 100644
index 0000000000..4822cecb84
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_dochierarchy.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test document hierarchy</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // tabDoc and testDoc are different documents depending on whether test
+ // is running in standalone mode or not.
+
+ var root = getRootAccessible();
+ var tabDoc = window.parent ?
+ getAccessible(window.parent.document, [nsIAccessibleDocument]) :
+ getAccessible(document, [nsIAccessibleDocument]);
+ var testDoc = getAccessible(document, [nsIAccessibleDocument]);
+ var iframeDoc = getAccessible("iframe").firstChild.
+ QueryInterface(nsIAccessibleDocument);
+
+ is(root.parentDocument, null,
+ "Wrong parent document of root accessible");
+ ok(root.childDocumentCount >= 1,
+ "Wrong child document count of root accessible");
+
+ var tabDocumentFound = false;
+ for (var i = 0; i < root.childDocumentCount && !tabDocumentFound; i++) {
+ tabDocumentFound = root.getChildDocumentAt(i) == tabDoc;
+ }
+ ok(tabDocumentFound,
+ "Tab document not found in children of the root accessible");
+
+ is(tabDoc.parentDocument, root,
+ "Wrong parent document of tab document");
+ is(tabDoc.childDocumentCount, 1,
+ "Wrong child document count of tab document");
+ is(tabDoc.getChildDocumentAt(0), (tabDoc == testDoc ? iframeDoc : testDoc),
+ "Wrong child document at index 0 of tab document");
+
+ if (tabDoc != testDoc) {
+ is(testDoc.parentDocument, tabDoc,
+ "Wrong parent document of test document");
+ is(testDoc.childDocumentCount, 1,
+ "Wrong child document count of test document");
+ is(testDoc.getChildDocumentAt(0), iframeDoc,
+ "Wrong child document at index 0 of test document");
+ }
+
+ is(iframeDoc.parentDocument, (tabDoc == testDoc ? tabDoc : testDoc),
+ "Wrong parent document of iframe document");
+ is(iframeDoc.childDocumentCount, 0,
+ "Wrong child document count of iframe document");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=592913"
+ title="Provide a way to quickly determine whether an accessible object is a descendant of a tab document">
+ Mozilla Bug 592913
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <iframe src="about:mozilla" id="iframe"></iframe>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_dockids.html b/accessible/tests/mochitest/tree/test_dockids.html
new file mode 100644
index 0000000000..b19a68624a
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_dockids.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test document hierarchy</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ // gA11yEventDumpToConsole = true;
+ // enableLogging("tree,verbose");
+ function doTest() {
+ var tree =
+ { DOCUMENT: [
+ { TEXT_CONTAINER: [ // head
+ { TEXT_CONTAINER: [ // link
+ { STATICTEXT: [] }, // generated content
+ { STATICTEXT: [] }, // generated content
+ ] },
+ ] },
+ { TEXT_LEAF: [ ] }, // body text
+ { ENTRY: [ ] }, // input under document element
+ { TEXT_CONTAINER: [ // link under document element
+ { TEXT_LEAF: [ ] }, // link content
+ { STATICTEXT: [ ] }, // generated content
+ { STATICTEXT: [ ] }, // generated content
+ ] },
+ { LINK: [ // anchor under document element
+ { TEXT_LEAF: [ ] }, // anchor content
+ ] },
+ ] };
+ testAccessibleTree(getNode("iframe").contentDocument, tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887"
+ title="Elements appended outside the body aren't accessible">
+ Mozilla Bug 608887
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <iframe src="dockids.html" id="iframe"></iframe>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_filectrl.html b/accessible/tests/mochitest/tree/test_filectrl.html
new file mode 100644
index 0000000000..f0cd5baa5b
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_filectrl.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=483573
+-->
+<head>
+ <title>File Input Control tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var accTree = {
+ role: ROLE_GROUPING,
+ children: [
+ {
+ role: ROLE_PUSHBUTTON,
+ },
+ {
+ role: ROLE_LABEL,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree("filectrl", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Expose HTML5 video and audio elements' embedded controls through accessibility APIs"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=483573">Mozilla Bug 483573</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input type="file" id="filectrl" />
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_formctrl.html b/accessible/tests/mochitest/tree/test_formctrl.html
new file mode 100644
index 0000000000..3046ec2bf7
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_formctrl.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>HTML form controls tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // input@type="checkbox"
+ var accTree = {
+ role: ROLE_CHECKBUTTON,
+ children: [ ],
+ };
+
+ testAccessibleTree("checkbox", accTree);
+
+ // input@type="radio"
+ accTree = {
+ role: ROLE_RADIOBUTTON,
+ children: [ ],
+ };
+
+ testAccessibleTree("radio", accTree);
+
+ // input@type="button" and input@type="submit"
+ // button
+ accTree = {
+ role: ROLE_PUSHBUTTON,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF, // XXX Bug 567203
+ },
+ ],
+ };
+
+ testAccessibleTree("btn1", accTree);
+ testAccessibleTree("submit", accTree);
+ testAccessibleTree("btn2", accTree);
+
+ // input@type="image"
+ accTree = {
+ role: ROLE_PUSHBUTTON,
+ children: [
+ {
+ role: ROLE_STATICTEXT,
+ },
+ ],
+ };
+ testAccessibleTree("image_submit", accTree);
+
+ // input@type="range"
+ accTree = { SLIDER: [ ] };
+ testAccessibleTree("range", accTree);
+
+ // input@type="number"
+ accTree = { SPINBUTTON: [ ] };
+ testAccessibleTree("number", accTree);
+
+ // output
+ accTree = {
+ role: ROLE_STATUSBAR,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ },
+ ],
+ };
+ testAccessibleTree("output", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Fix O(n^2) access to all the children of a container"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045">
+ Bug 342045
+ </a>
+ <a target="_blank"
+ title="add test for role of input type='image'"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=524521">
+ Bug 524521
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=558036"
+ title="make HTML <output> accessible">
+ Bug 558036
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764"
+ title="make HTML5 input@type=range element accessible">
+ Bug 559764
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input type="checkbox" id="checkbox">
+ <input type="radio" id="radio">
+ <input type="button" value="button" id="btn1">
+ <button id="btn2">button</button>
+
+ <input type="submit" id="submit">
+ <input type="image" id="image_submit">
+ <input type="range" id="range">
+ <input type="number" id="number">
+ <output id="output">1337</output>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_formctrl.xhtml b/accessible/tests/mochitest/tree/test_formctrl.xhtml
new file mode 100644
index 0000000000..d85a97171f
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_formctrl.xhtml
@@ -0,0 +1,129 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<!-- Firefox toolbar -->
+<?xml-stylesheet href="chrome://browser/content/browser.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL checkbox and radio hierarchy tests">
+
+ <!-- Firefox toolbar -->
+ <script type="application/javascript"
+ src="chrome://browser/content/browser.js"/>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ // checkbox
+ var accTree = {
+ role: ROLE_CHECKBUTTON,
+ children: [ ]
+ };
+
+ testAccessibleTree("checkbox", accTree);
+
+ // radiogroup
+ accTree = {
+ role: ROLE_RADIO_GROUP,
+ children: [
+ {
+ role: ROLE_RADIOBUTTON,
+ children: [ ]
+ },
+ {
+ role: ROLE_RADIOBUTTON,
+ children: [ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("radiogroup", accTree);
+
+ // toolbar
+ accTree = {
+ role: ROLE_TOOLBAR,
+ name: "My toolbar",
+ children: [
+ {
+ role: ROLE_PUSHBUTTON,
+ name: "hello",
+ children: [ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("toolbar", accTree);
+
+ // toolbar
+ accTree = {
+ role: ROLE_TOOLBAR,
+ name: "My second toolbar",
+ children: [
+ {
+ role: ROLE_PUSHBUTTON,
+ name: "hello",
+ children: [ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("toolbar2", accTree);
+
+ if (!SEAMONKEY)
+ testAccessibleTree("tb_customizable", { TOOLBAR: [] });
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045"
+ title="Fix O(n^2) access to all the children of a container">
+ Mozilla Bug 342045
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <checkbox id="checkbox" label="checkbox"/>
+ <radiogroup id="radiogroup">
+ <radio label="radio1"/>
+ <radio label="radio2"/>
+ </radiogroup>
+ <toolbar id="toolbar" toolbarname="My toolbar">
+ <toolbarbutton id="button1" label="hello"/>
+ </toolbar>
+ <toolbar id="toolbar2" toolbarname="2nd" aria-label="My second toolbar">
+ <toolbarbutton id="button2" label="hello"/>
+ </toolbar>
+
+ <toolbar id="tb_customizable" customizable="true"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/tree/test_gencontent.html b/accessible/tests/mochitest/tree/test_gencontent.html
new file mode 100644
index 0000000000..3cd5e35e9a
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_gencontent.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Generated content tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ .gentext:before {
+ content: "START"
+ }
+ .gentext:after {
+ content: "END"
+ }
+ </style>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // :before and :after pseudo styles
+ var accTree = {
+ role: ROLE_SECTION,
+ children: [
+ {
+ role: ROLE_STATICTEXT,
+ name: "START",
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "MIDDLE",
+ },
+ {
+ role: ROLE_STATICTEXT,
+ name: "END",
+ },
+ ],
+ };
+
+ testAccessibleTree("gentext", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Clean up our tree walker"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=530081">
+ Mozilla Bug 530081
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div class="gentext" id="gentext">MIDDLE</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_groupbox.xhtml b/accessible/tests/mochitest/tree/test_groupbox.xhtml
new file mode 100644
index 0000000000..ba873d68c8
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_groupbox.xhtml
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL groupbox hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ var accTree =
+ { GROUPING: [
+ { LABEL: [
+ { STATICTEXT: [ ] }
+ ] },
+ { CHECKBUTTON: [ ] }
+ ] };
+ testAccessibleTree("groupbox", accTree);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045"
+ title="Fix O(n^2) access to all the children of a container">
+ Mozilla Bug 342045
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <groupbox id="groupbox">
+ <label value="Some caption"/>
+ <checkbox label="some checkbox label" />
+ </groupbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/tree/test_html_in_mathml.html b/accessible/tests/mochitest/tree/test_html_in_mathml.html
new file mode 100644
index 0000000000..214658de4a
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_html_in_mathml.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML in MathML Tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // <label> is generally not valid in MathML and shouldn't create an HTMl
+ // label.
+ testAccessibleTree("invalidLabel", {
+ MATHML_MATH: [ {
+ TEXT: [ {
+ TEXT_LEAF: []
+ } ],
+ } ],
+ });
+
+ // HTML is valid inside <mtext>, so <label> should create an HTMl label.
+ testAccessibleTree("validLabel", {
+ MATHML_MATH: [ {
+ MATHML_TEXT: [ {
+ LABEL: [ {
+ TEXT_LEAF: []
+ } ],
+ } ],
+ } ],
+ });
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <math id="invalidLabel">
+ <label>Text</label>
+ </math>
+
+ <math id="validLabel">
+ <mtext>
+ <label>Text</label>
+ </mtext>
+ </math>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_iframe.html b/accessible/tests/mochitest/tree/test_iframe.html
new file mode 100644
index 0000000000..e0e691550b
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_iframe.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Outer document accessible tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var accTree = {
+ role: ROLE_INTERNAL_FRAME,
+ children: [
+ {
+ role: ROLE_DOCUMENT,
+ },
+ ],
+ };
+
+ testAccessibleTree("iframe", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Fix O(n^2) access to all the children of a container"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045">
+ Mozilla Bug 342045
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <iframe id="iframe" src="about:mozilla">
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_image.xhtml b/accessible/tests/mochitest/tree/test_image.xhtml
new file mode 100644
index 0000000000..ee7a6eabb1
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_image.xhtml
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL textbox and textarea hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ var accTree = {
+ role: ROLE_GRAPHIC,
+ children: []
+ };
+ testAccessibleTree("image", accTree);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1403231"
+ title="Remove the image XBL binding">
+ Mozilla Bug 1403231
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <image id="image" src="../moz.png" tooltiptext="hello"/>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/tree/test_img.html b/accessible/tests/mochitest/tree/test_img.html
new file mode 100644
index 0000000000..c2a7351a15
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_img.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML img tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ // gA11yEventDumpToConsole = true;
+ function doPreTest() {
+ waitForImageMap("imgmap", doTest);
+ }
+
+ function doTest() {
+ // image map
+ var accTree = {
+ role: ROLE_IMAGE_MAP,
+ children: [
+ {
+ role: ROLE_LINK,
+ children: [],
+ },
+ {
+ role: ROLE_LINK,
+ children: [],
+ },
+ ],
+ };
+
+ testAccessibleTree("imgmap", accTree);
+
+ // img
+ accTree = {
+ role: ROLE_GRAPHIC,
+ children: [],
+ };
+
+ testAccessibleTree("img", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ title="Fix O(n^2) access to all the children of a container"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045">
+ Mozilla Bug 342045
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <map name="atoz_map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,13,14" alt="a" shape="rect">
+ </map>
+
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="../letters.gif">
+
+ <img id="img" src="../moz.png">
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_invalid_img.xhtml b/accessible/tests/mochitest/tree/test_invalid_img.xhtml
new file mode 100644
index 0000000000..3d02900cd8
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_invalid_img.xhtml
@@ -0,0 +1,48 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>invalid html img</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script>
+ <![CDATA[
+ function doTest() {
+ document.getElementsByTagName("img")[0].firstChild.data = "2";
+
+ var accTree = {
+ role: ROLE_GRAPHIC,
+ children: [],
+ };
+ testAccessibleTree("the_img", accTree);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="use HyperTextAccessible for invalid img"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=852129">
+ Mozilla Bug 852129
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <img id="the_img">1</img>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_invalidationlist.html b/accessible/tests/mochitest/tree/test_invalidationlist.html
new file mode 100644
index 0000000000..bad908f3c8
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_invalidationlist.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test document hierarchy</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var tree =
+ {SECTION: [
+ { role: ROLE_PUSHBUTTON, name: "Hello" },
+ { SECTION: [
+ { TEXT: [ { role: ROLE_TEXT_LEAF, name: "Hello" } ] },
+ { role: ROLE_TEXT_LEAF, name: "World" }
+ ]}
+ ]};
+ testAccessibleTree("container", tree);
+ dumpTree("container");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673757"
+ title="Do not process invalidation list while tree is created">
+ Mozilla Bug 673757
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="container">
+ <div role="button" aria-labelledby="a"></div>
+ <div>
+ <span id="a">Hello</span><span id="b">World</span>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_list.html b/accessible/tests/mochitest/tree/test_list.html
new file mode 100644
index 0000000000..46acda1516
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_list.html
@@ -0,0 +1,346 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML ul/li element tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function listItemTree(aBulletText, aName, aSubtree) {
+ var obj = {
+ role: ROLE_LISTITEM,
+ children: [
+ {
+ role: ROLE_LISTITEM_MARKER,
+ name: aBulletText,
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: aName,
+ },
+ ],
+ };
+
+ if (aSubtree)
+ obj.children.push(aSubtree);
+
+ return obj;
+ }
+
+ function doTest() {
+ // list1
+ var discAccTree = {
+ role: ROLE_LIST,
+ children: [
+ new listItemTree(kDiscBulletText, "Oranges"),
+ new listItemTree(kDiscBulletText, "Apples"),
+ new listItemTree(kDiscBulletText, "Bananas"),
+ ],
+ };
+
+ testAccessibleTree("list1", discAccTree);
+
+ // list2
+ var circleAccTree = {
+ role: ROLE_LIST,
+ children: [
+ new listItemTree(kCircleBulletText, "Oranges"),
+ new listItemTree(kCircleBulletText, "Apples"),
+ new listItemTree(kCircleBulletText, "Bananas"),
+ ],
+ };
+
+ testAccessibleTree("list2", circleAccTree);
+
+ // list3
+ var squareAccTree = {
+ role: ROLE_LIST,
+ children: [
+ new listItemTree(kSquareBulletText, "Oranges"),
+ new listItemTree(kSquareBulletText, "Apples"),
+ new listItemTree(kSquareBulletText, "Bananas"),
+ ],
+ };
+
+ testAccessibleTree("list3", squareAccTree);
+
+ // list4
+ var nestedAccTree = {
+ role: ROLE_LIST,
+ children: [
+ new listItemTree("1. ", "Oranges"),
+ new listItemTree("2. ", "Apples"),
+ new listItemTree("3. ", "Bananas", circleAccTree),
+ ],
+ };
+
+ testAccessibleTree("list4", nestedAccTree);
+
+ // dl list
+ var tree =
+ { DEFINITION_LIST: [ // dl
+ { TERM: [ // dt
+ { TEXT_LEAF: [] },
+ ] },
+ { DEFINITION: [ // dd
+ { TEXT_LEAF: [] },
+ ] },
+ { TERM: [ // dt
+ { TEXT_LEAF: [] },
+ ] },
+ { DEFINITION: [ // dd
+ { TEXT_LEAF: [] },
+ ] },
+ ] };
+
+ testAccessibleTree("list5", tree);
+
+ // dl list inside ordered list
+ tree =
+ { LIST: [ // ol
+ { LISTITEM: [ // li
+ { LISTITEM_MARKER: [ ] },
+ { DEFINITION_LIST: [ // dl
+ { TERM: [ // dt
+ { TEXT_LEAF: [] },
+ ] },
+ { DEFINITION: [ // dd
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] },
+ ] };
+
+ testAccessibleTree("list6", tree);
+
+ // li having no display:list-item style
+ tree =
+ { LIST: [ // ul
+ { LISTITEM: [ // li
+ { TEXT_LEAF: [] },
+ ] },
+ { TEXT_LEAF: [] },
+ { LISTITEM: [ // li
+ { TEXT_LEAF: [] },
+ ] },
+ ] };
+ testAccessibleTree("list7", tree);
+
+ tree =
+ { LIST: [ // ul
+ { LISTITEM: [ // li
+ { TEXT_LEAF: [] },
+ ] },
+ { LISTITEM: [ // li
+ { TEXT_LEAF: [] },
+ ] },
+ ] };
+ testAccessibleTree("list8", tree);
+
+ // span having display:list-item style
+ testAccessibleTree("list9", discAccTree);
+
+ // dl with div grouping dt/dd
+ tree =
+ { DEFINITION_LIST: [ // dl
+ { TERM: [ // dt
+ { TEXT_LEAF: [] },
+ ] },
+ { DEFINITION: [ // dd
+ { TEXT_LEAF: [] },
+ ] },
+ { TERM: [ // dt
+ { TEXT_LEAF: [] },
+ ] },
+ { DEFINITION: [ // dd
+ { TEXT_LEAF: [] },
+ ] },
+ ] };
+
+ testAccessibleTree("list10", tree);
+
+ // list-style-image
+ testAccessibleTree("list11", discAccTree);
+
+ // list-style: none
+ tree =
+ { LIST: [ // ul
+ { LISTITEM: [ // li
+ { TEXT_LEAF: [] },
+ ] },
+ { LISTITEM: [ // li
+ { TEXT_LEAF: [] },
+ ] },
+ ] };
+ testAccessibleTree("list12", tree);
+
+ // ::marker with content
+ tree = { // ol
+ role: ROLE_LIST,
+ children: [
+ { // li
+ role: ROLE_LISTITEM,
+ children: [
+ { // ::marker content text and counter
+ role: ROLE_LISTITEM_MARKER,
+ name: "foo1",
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Oranges",
+ },
+ ],
+ },
+ { // li
+ role: ROLE_LISTITEM,
+ children: [
+ { // ::marker content text and counter
+ role: ROLE_LISTITEM_MARKER,
+ name: "foo2",
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Apples",
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree("list13", tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Fix O(n^2) access to all the children of a container"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045">
+ Mozilla Bug 342045
+ </a>
+ <a target="_blank"
+ title="Wrong accessible is created for HTML:li having block display style"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=507555">
+ Mozilla Bug 507555
+ </a>
+ <a target="_blank"
+ title="Bullets of nested not ordered lists have one and the same character."
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=604587">
+ Mozilla Bug 604587
+ </a>
+ <a target="_blank"
+ title="Fix list bullets for DL list (crash [@ nsBulletFrame::GetListItemText])"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=629114">
+ Mozilla Bug 629114
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ul id="list1">
+ <li id="l1_li1">Oranges</li>
+ <li id="l1_li2">Apples</li>
+ <li id="l1_li3">Bananas</li>
+ </ul>
+
+ <ul id="list2" style="list-style-type: circle">
+ <li id="l2_li1">Oranges</li>
+ <li id="l2_li2">Apples</li>
+ <li id="l2_li3">Bananas</li>
+ </ul>
+
+ <ul id="list3" style="list-style-type: square">
+ <li id="l3_li1">Oranges</li>
+ <li id="l3_li2">Apples</li>
+ <li id="l3_li3">Bananas</li>
+ </ul>
+
+ <ol id="list4">
+ <li id="li4">Oranges</li>
+ <li id="li5">Apples</li>
+ <li id="li6">Bananas<ul>
+ <li id="n_li4">Oranges</li>
+ <li id="n_li5">Apples</li>
+ <li id="n_li6">Bananas</li>
+ </ul>
+ </li>
+ </ol>
+
+ <dl id="list5">
+ <dt>item1</dt><dd>description</dd>
+ <dt>item2</td><dd>description</dd>
+ </dl>
+
+ <ol id="list6">
+ <li>
+ <dl id="dl">
+ <dt>item1</dt><dd>description</dd>
+ </dl>
+ </li>
+ </ol>
+
+ <!-- display style different than list-item -->
+ <ul id="list7">
+ <li id="l7_li1" style="display:inline-block;">Oranges</li>
+ <li id="l7_li2" style="display:inline-block;">Apples</li>
+ </ul>
+
+ <ul id="list8">
+ <li id="l8_li1" style="display:inline; float:right;">Oranges</li>
+ <li id="l8_li2" style="display:inline; float:right;">Apples</li>
+ </ul>
+
+ <!-- list-item display style -->
+ <ul id="list9">
+ <span id="l9_li1" style="display:list-item">Oranges</span>
+ <span id="l9_li2" style="display:list-item">Apples</span>
+ <span id="l9_li3" style="display:list-item">Bananas</span>
+ </ul>
+
+ <!-- dl with div grouping dd/dt elements (bug 1476347) -->
+ <dl id="list10">
+ <div><dt>item1</dt><dd>description</dd></div>
+ <div><dt>item2</td><dd>description</dd></div>
+ </dl>
+
+ <!-- list-style-image -->
+ <ul id="list11"
+ style="list-style-type: none; list-style-image: url('../moz.png');">
+ <li>Oranges</li>
+ <li>Apples</li>
+ <li>Bananas</li>
+ </ul>
+
+ <!-- list-style: none -->
+ <ul id="list12" style="list-style: none;">
+ <li>Oranges</li>
+ <li>Apples</li>
+ </ul>
+
+ <!-- ::marker with content -->
+ <style>
+ #list13 li {
+ counter-increment: list13counter;
+ }
+ #list13 li::marker {
+ content: 'foo' counter(list13counter);
+ }
+ </style>
+ <ol id="list13">
+ <li>Oranges</li>
+ <li>Apples</li>
+ </ol>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_map.html b/accessible/tests/mochitest/tree/test_map.html
new file mode 100644
index 0000000000..72de628f9f
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_map.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML map accessible tree tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // map used as imagemap, not accessible
+ var accTree =
+ { SECTION: [ ] };
+
+ testAccessibleTree("imagemapcontainer", accTree);
+
+ // map group. Imagemaps are inlines by default, so TEXT_CONTAINER.
+ accTree =
+ { TEXT_CONTAINER: [
+ { PARAGRAPH: [
+ { TEXT_LEAF: [ ] },
+ { LINK: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ { TEXT_LEAF: [ ] },
+ { LINK: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] };
+
+ testAccessibleTree("mapgroup", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Map used for grouping is not accessible under certain circumstances"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=627718">
+ Mozilla Bug 627718
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="imagemapcontainer">
+ <map name="atoz_map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,13,14" alt="a" shape="rect">
+ </map>
+ </div>
+
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="../letters.gif">
+
+ <map id="mapgroup" title="Navigation Bar" name="mapgroup">
+ <p>
+ [<a href="#how">Bypass navigation bar</a>]
+ [<a href="home.html">Home</a>]
+ </p>
+ </map>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_media.html b/accessible/tests/mochitest/tree/test_media.html
new file mode 100644
index 0000000000..5d9fe0ef24
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_media.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=483573
+-->
+<head>
+ <title>HTML5 audio/video tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+
+ async function loadAudioSource() {
+ /**
+ * Setting the source dynamically and wait for it to load,
+ * so we can test the accessibility tree of the control in its ready and
+ * stable state.
+ *
+ * See bug 1484048 comment 25 for discussion on how it switches UI when
+ * loading a statically declared source.
+ */
+ const bufferA11yShown = waitForEvent(
+ EVENT_SHOW,
+ evt => evt.accessible.role == ROLE_TEXT_LEAF &&
+ evt.accessible.indexInParent == 2 &&
+ evt.accessible.parent.role == ROLE_STATUSBAR
+ );
+ await new Promise(resolve => {
+ let el = document.getElementById("audio");
+ el.addEventListener("canplaythrough", resolve, {once: true});
+ el.src = "../bug461281.ogg";
+ });
+ // Wait for this to be reflected in the a11y tree.
+ await bufferA11yShown;
+
+ // Give Fluent time to update the value of the scrubber asynchronously.
+ let scrubber = document.getElementById("audio")
+ .openOrClosedShadowRoot.getElementById("scrubber");
+ await SimpleTest.promiseWaitForCondition(() =>
+ scrubber.getAttribute("aria-valuetext") == "0:00 / 0:02");
+
+ doTest();
+ }
+
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // test the accessible tree
+
+ var accTree = {
+ role: ROLE_GROUPING,
+ children: [
+ { // start/stop button
+ role: ROLE_PUSHBUTTON,
+ name: "Play",
+ children: [],
+ },
+ { // buffer bar
+ role: ROLE_STATUSBAR,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Loading:",
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: " ",
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ // The name is the percentage buffered; e.g. "0%", "100%".
+ // We can't check it here because it might be different
+ // depending on browser caching.
+ },
+ ],
+ },
+ { // slider of progress bar
+ role: ROLE_SLIDER,
+ name: "Position",
+ value: "0:00 / 0:02",
+ children: [],
+ },
+ { // mute button
+ role: ROLE_PUSHBUTTON,
+ name: "Mute",
+ children: [],
+ },
+ { // slider of volume bar
+ role: ROLE_SLIDER,
+ children: [],
+ name: "Volume",
+ },
+ ],
+ };
+ testAccessibleTree("audio", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(loadAudioSource);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Expose HTML5 video and audio elements' embedded controls through accessibility APIs"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=483573">Mozilla Bug 483573</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <audio id="audio" controls="true"></audio>
+
+ <div id="eventDump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_select.html b/accessible/tests/mochitest/tree/test_select.html
new file mode 100644
index 0000000000..80c17b9ef4
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_select.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML select control tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var accTree = {
+ role: ROLE_LISTBOX,
+ children: [
+ {
+ role: ROLE_GROUPING,
+ children: [
+ {
+ role: ROLE_STATICTEXT,
+ children: [ ],
+ },
+ {
+ role: ROLE_OPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ },
+ ],
+ },
+ {
+ role: ROLE_OPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ role: ROLE_OPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree("listbox", accTree);
+
+ accTree = {
+ role: ROLE_COMBOBOX,
+ children: [
+ {
+ role: ROLE_COMBOBOX_LIST,
+ children: [
+ {
+ role: ROLE_GROUPING,
+ children: [
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: [ ],
+ },
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: [ ],
+ },
+ ],
+ },
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: [ ],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree("combobox", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="remove all the code in #ifdef COMBO_BOX_WITH_THREE_CHILDREN"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=506616">
+ Mozilla Bug 506616
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="listbox" size="4">
+ <optgroup label="Colors">
+ <option>Red</option>
+ <option>Blue</option>
+ </optgroup>
+ <option>Animal</option>
+ </select>
+
+ <select id="combobox">
+ <optgroup label="Colors">
+ <option>Red</option>
+ <option>Blue</option>
+ </optgroup>
+ <option>Animal</option>
+ </select>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_svg.html b/accessible/tests/mochitest/tree/test_svg.html
new file mode 100644
index 0000000000..4a071d0f4a
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_svg.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>SVG Tree Tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // svgText
+ var accTree = {
+ role: ROLE_DIAGRAM,
+ children: [
+ {
+ role: ROLE_TEXT_CONTAINER,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree("svgText", accTree);
+
+ // svg1
+ accTree = {
+ role: ROLE_DIAGRAM,
+ children: []
+ };
+ testAccessibleTree("svg1", accTree);
+
+ // svg2
+ accTree = {
+ role: ROLE_DIAGRAM,
+ children: [
+ {
+ role: ROLE_GROUPING,
+ children: []
+ }
+ ]
+ };
+ testAccessibleTree("svg2", accTree);
+
+ // svg3
+ accTree = {
+ role: ROLE_DIAGRAM,
+ children: [
+ {
+ role: ROLE_GRAPHIC,
+ children: []
+ }
+ ]
+ };
+ testAccessibleTree("svg3", accTree);
+
+ // svg4
+ accTree = {
+ role: ROLE_DIAGRAM,
+ children: [
+ {
+ role: ROLE_GROUPING,
+ children: [
+ {
+ role: ROLE_GRAPHIC,
+ children: []
+ }
+ ]
+ }
+ ]
+ };
+ testAccessibleTree("svg4", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <svg id="svgText">
+ <text>This is some text</text>
+ </svg>
+
+ <!-- no accessible objects -->
+ <svg id="svg1">
+ <g id="g1">
+ <rect width="300" height="100" id="rect1" style="fill:#00f" />
+ </g>
+ </svg>
+
+ <svg id="svg2">
+ <g id="g2">
+ <title>g</title>
+ <rect width="300" height="100" id="rect2" style="fill:#00f" />
+ </g>
+ </svg>
+
+ <svg id="svg3">
+ <g id="g3">
+ <rect width="300" height="100" id="rect3" style="fill:#00f">
+ <title>rect</title>
+ </rect>
+ </g>
+ </svg>
+
+ <svg id="svg4">
+ <g id="g4">
+ <title>g</title>
+ <rect width="300" height="100" id="rect4" style="fill:#00f">
+ <title>rect</title>
+ </rect>
+ </g>
+ </svg>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_tabbox.xhtml b/accessible/tests/mochitest/tree/test_tabbox.xhtml
new file mode 100644
index 0000000000..27e9222873
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_tabbox.xhtml
@@ -0,0 +1,108 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tabbox hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // tabbox
+
+ var accTree = {
+ role: ROLE_PAGETABLIST,
+ children: [
+ {
+ role: ROLE_PAGETAB,
+ children: [
+ {
+ role: ROLE_STATICTEXT,
+ children: [],
+ }
+ ]
+ },
+ {
+ role: ROLE_PAGETAB,
+ children: [
+ {
+ role: ROLE_STATICTEXT,
+ children: [],
+ }
+ ]
+ }
+ ]
+ };
+ testAccessibleTree("tabs", accTree);
+
+ accTree = {
+ role: ROLE_PANE,
+ children: [
+ {
+ role: ROLE_PROPERTYPAGE,
+ children: []
+ },
+ {
+ role: ROLE_PROPERTYPAGE,
+ children: []
+ }
+ ]
+ };
+ testAccessibleTree("tabpanels", accTree);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=540389"
+ title=" WARNING: Bad accessible tree!: [tabbrowser tab] ">
+ Mozilla Bug 540389
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=552944"
+ title="No relationship between tabs and associated property page in new tabbrowser construct">
+ Mozilla Bug 552944
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tabbox>
+ <tabs id="tabs">
+ <tab label="tab1"/>
+ <tab label="tab2"/>
+ </tabs>
+ <tabpanels id="tabpanels">
+ <tabpanel/>
+ <tabpanel/>
+ </tabpanels>
+ </tabbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/tree/test_tabbrowser.xhtml b/accessible/tests/mochitest/tree/test_tabbrowser.xhtml
new file mode 100644
index 0000000000..401ea1a2b1
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_tabbrowser.xhtml
@@ -0,0 +1,261 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tabbrowser hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // invoker
+ function testTabHierarchy()
+ {
+ this.eventSeq = [
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 0),
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
+ ];
+
+ this.invoke = function testTabHierarchy_invoke()
+ {
+ var docURIs = ["about:license", "about:mozilla"];
+ tabBrowser().loadTabs(docURIs, {
+ inBackground: false,
+ replace: true,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ // Flush layout, so as to guarantee that the a11y tree is constructed.
+ browserDocument().documentElement.getBoundingClientRect();
+ }
+
+ this.finalCheck = function testTabHierarchy_finalCheck(aEvent)
+ {
+ ////////////////////
+ // Tab bar
+ ////////////////////
+ var tabsAccTree = {
+ // xul:tabs
+ role: ROLE_PAGETABLIST,
+ children: [
+ // Children depend on application (UI): see below.
+ ]
+ };
+
+ // SeaMonkey and Firefox tabbrowser UIs differ.
+ if (SEAMONKEY) {
+ SimpleTest.ok(true, "Testing SeaMonkey tabbrowser UI.");
+
+ tabsAccTree.children.splice(0, 0,
+ {
+ // xul:toolbarbutton ("Open a new tab")
+ role: ROLE_PUSHBUTTON,
+ children: []
+ },
+ {
+ // xul:tab ("about:license")
+ role: ROLE_PAGETAB,
+ children: []
+ },
+ {
+ // tab ("about:mozilla")
+ role: ROLE_PAGETAB,
+ children: []
+ },
+ {
+ // xul:toolbarbutton ("List all tabs")
+ role: ROLE_PUSHBUTTON,
+ children: [
+ {
+ // xul:menupopup
+ role: ROLE_MENUPOPUP,
+ children: []
+ }
+ ]
+ },
+ {
+ // xul:toolbarbutton ("Close current tab")
+ role: ROLE_PUSHBUTTON,
+ children: []
+ }
+ );
+ } else {
+ SimpleTest.ok(true, "Testing Firefox tabbrowser UI.");
+ let newTabChildren = [];
+ if (SpecialPowers.getBoolPref("privacy.userContext.enabled")) {
+ newTabChildren = [
+ {
+ role: ROLE_MENUPOPUP,
+ children: []
+ }
+ ];
+ }
+
+ // NB: The (3) buttons are not visible, unless manually hovered,
+ // probably due to size reduction in this test.
+ tabsAccTree.children.splice(0, 0,
+ {
+ // xul:tab ("about:license")
+ role: ROLE_PAGETAB,
+ children: [
+ {
+ // xul:text, i.e. the tab label text
+ role: ROLE_TEXT_LEAF,
+ children: []
+ }
+ ]
+ },
+ {
+ // tab ("about:mozilla")
+ role: ROLE_PAGETAB,
+ children: [
+ {
+ // xul:text, i.e. the tab label text
+ role: ROLE_TEXT_LEAF,
+ children: []
+ }
+ ]
+ },
+ {
+ // xul:toolbarbutton ("Open a new tab")
+ role: ROLE_PUSHBUTTON,
+ children: newTabChildren
+ }
+ // "List all tabs" dropdown
+ // XXX: This child(?) is not present in this test.
+ // I'm not sure why (though probably expected).
+ );
+ }
+
+ testAccessibleTree(tabBrowser().tabContainer, tabsAccTree);
+
+ ////////////////////
+ // Tab contents
+ ////////////////////
+ var tabboxAccTree = {
+ // xul:tabpanels
+ role: ROLE_PANE,
+ children: [
+ {
+ // xul:notificationbox
+ role: ROLE_PROPERTYPAGE,
+ children: [
+ {
+ // xul:browser
+ role: ROLE_INTERNAL_FRAME,
+ children: [
+ {
+ // #document ("about:license")
+ role: ROLE_DOCUMENT
+ // children: [ ... ] // Ignore document content.
+ }
+ ]
+ }
+ ]
+ },
+ {
+ // notificationbox
+ role: ROLE_PROPERTYPAGE,
+ children: [
+ {
+ // browser
+ role: ROLE_INTERNAL_FRAME,
+ children: [
+ {
+ // #document ("about:mozilla")
+ role: ROLE_DOCUMENT
+ // children: [ ... ] // Ignore document content.
+ }
+ ]
+ }
+ ]
+ },
+ {
+ // notificationbox
+ role: ROLE_PROPERTYPAGE,
+ children: [
+ {
+ // browser
+ role: ROLE_INTERNAL_FRAME,
+ children: [
+ {
+ // #document ("about:newtab" preloaded)
+ role: ROLE_DOCUMENT
+ // children: [ ... ] // Ignore document content.
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ };
+
+ testAccessibleTree(tabBrowser().tabbox.tabpanels, tabboxAccTree);
+ }
+
+ this.getID = function testTabHierarchy_getID()
+ {
+ return "hierarchy of tabs";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+ gA11yEventDumpToConsole = true;
+ //enableLogging("tree,verbose,stack");
+
+ var gQueue = null;
+ function doTest()
+ {
+ SimpleTest.requestCompleteLog();
+
+ // Load documents into tabs and wait for docLoadComplete events caused by these
+ // documents load before we start the test.
+ gQueue = new eventQueue();
+
+ gQueue.push(new testTabHierarchy());
+ gQueue.onFinish = function() { closeBrowserWindow(); }
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTest);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=540389"
+ title=" WARNING: Bad accessible tree!: [tabbrowser tab] ">
+ Mozilla Bug 540389
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=552944"
+ title="No relationship between tabs and associated property page in new tabbrowser construct">
+ Mozilla Bug 552944
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="eventdump"></vbox>
+ </vbox>
+
+</window>
diff --git a/accessible/tests/mochitest/tree/test_table.html b/accessible/tests/mochitest/tree/test_table.html
new file mode 100644
index 0000000000..5f34c12067
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_table.html
@@ -0,0 +1,507 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML table tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // tables having captions
+
+ // Two captions, first is used, second is ignored.
+ var accTree =
+ { TABLE: [
+ { CAPTION: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "caption",
+ },
+ ] },
+ { ROW: [
+ { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] },
+ { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] },
+ ] },
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ ] },
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ ] },
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ ] },
+ ] };
+
+ testAccessibleTree("table", accTree);
+
+ // One caption, empty text, caption is included.
+ accTree =
+ { TABLE: [
+ { CAPTION: [ ] },
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ ] },
+ ] };
+
+ testAccessibleTree("table_caption_empty", accTree);
+
+ // Two captions, first has empty text, second is ignored.
+ accTree =
+ { TABLE: [
+ { CAPTION: [ ] },
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ ] },
+ ] };
+
+ testAccessibleTree("table_caption_firstempty", accTree);
+
+ // One caption, placed in the end of table. In use.
+ accTree =
+ { TABLE: [
+ { CAPTION: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "caption",
+ },
+ ] },
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ ] },
+ ] };
+
+ testAccessibleTree("table_caption_intheend", accTree);
+
+ // One caption, collapsed to zero width and height. In use.
+ accTree =
+ { TABLE: [
+ { CAPTION: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "caption",
+ },
+ ] },
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ ] },
+ ] };
+
+ testAccessibleTree("table_caption_collapsed", accTree);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // table2 (consist of one column)
+
+ accTree = {
+ role: ROLE_TABLE,
+ children: [
+ {
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_COLUMNHEADER,
+ },
+ ],
+ },
+ {
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_CELL,
+ },
+ ],
+ },
+ ],
+ };
+
+ testAccessibleTree("table2", accTree);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // table3 (consist of one row)
+
+ accTree = {
+ role: ROLE_TABLE,
+ children: [
+ {
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_ROWHEADER,
+ },
+ {
+ role: ROLE_CELL,
+ },
+ ],
+ },
+ ],
+ };
+
+ testAccessibleTree("table3", accTree);
+
+ // ///////////////////////////////////////////////////////////////////////
+ // table4 (display: table-row)
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] } ],
+ };
+ testAccessibleTree("table4", accTree);
+
+ // ///////////////////////////////////////////////////////////////////////
+ // table5 (tbody with display: block should not get accessible)
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("table5", accTree);
+
+ // ///////////////////////////////////////////////////////////////////////
+ // log table
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("logtable", accTree);
+
+ // ///////////////////////////////////////////////////////////////////////
+ // display:block table
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("block_table", accTree);
+
+ // ///////////////////////////////////////////////////////////////////////
+ // display:inline table
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("inline_table1", accTree);
+
+ // ///////////////////////////////////////////////////////////////////////
+ // display:inline table
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("table_containing_inlinetable", accTree);
+
+ // ///////////////////////////////////////////////////////////////////////
+ // table with a cell that has display:block
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("table_containing_block_cell", accTree);
+
+ // ///////////////////////////////////////////////////////////////////////
+ // A table with all elements being display:block, including a row group.
+ // This makes us fall back to the ARIAGridRowAccessible.
+ // Strange example from Gmail.
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("table_all_display_block", accTree);
+
+ // ///////////////////////////////////////////////////////////////////////
+ // A table with a display:block tbody that has an aria role
+ // The tbody should get an accessible with the desired role.
+ accTree =
+ { TABLE: [
+ { DIALOG: [
+ { TEXT_CONTAINER: [
+ { TEXT_CONTAINER: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("table_with_block_tbody_and_role", accTree);
+
+ // ///////////////////////////////////////////////////////////////////////
+ // A table with a display:block tbody that is focusable
+ // The tbody should get a grouping accessible.
+ accTree =
+ { TABLE: [
+ { GROUPING: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] },
+ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("table_with_focusable_block_tbody", accTree);
+
+ // ///////////////////////////////////////////////////////////////////////
+ // Test that the CSS position property doesn't stop th elements from
+ // reporting the proper columnheader, rowheader roles.
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] },
+ { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] },
+ { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] },
+ { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] },
+ { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] },
+ ] },
+ { ROW: [
+ { ROWHEADER: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ ] },
+ { ROWHEADER: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ ] },
+ { ROWHEADER: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ ] },
+ { ROWHEADER: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ ] },
+ { ROWHEADER: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ ] },
+ ] },
+ ] };
+ testAccessibleTree("table_containing_pos_styled_th", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="When a table has only one column per row and that column happens to be a column header its role is exposed wrong"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=529621">
+ Mozilla Bug 529621
+ </a>
+ <a target="_blank"
+ title="when div has display style table-row"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=727722">
+ Mozilla Bug 727722
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table id="table">
+ <thead>
+ <tr>
+ <th>col1</th><th>col2</th>
+ </tr>
+ </thead>
+ <caption>caption</caption>
+ <tbody>
+ <tr>
+ <td>cell1</td><td>cell2</td>
+ </tr>
+ </tbody>
+ <tr>
+ <td>cell3</td><td>cell4</td>
+ </tr>
+ <caption>caption2</caption>
+ <tfoot>
+ <tr>
+ <td>cell5</td><td>cell6</td>
+ </tr>
+ </tfoot>
+ </table>
+
+ <table id="table_caption_empty">
+ <caption></caption>
+ <tr>
+ <td>cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <table id="table_caption_firstempty">
+ <caption></caption>
+ <tr>
+ <td>cell1</td><td>cell2</td>
+ </tr>
+ <caption>caption</caption>
+ </table>
+
+ <table id="table_caption_intheend">
+ <tr>
+ <td>cell1</td><td>cell2</td>
+ </tr>
+ <caption>caption</caption>
+ </table>
+
+ <table id="table_caption_collapsed">
+ <caption style="width: 0; height: 0">caption</caption>
+ <tr>
+ <td>cell1</td><td>cell2</td>
+ </tr>
+ </table>
+ <table id="table2">
+ <thead>
+ <tr>
+ <th>colheader</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>bla</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table3">
+ <tr>
+ <th>rowheader</th>
+ <td>cell</td>
+ </tr>
+ </table>
+
+ <table id="table4">
+ <div style="display: table-row">
+ <td>cell1</td>
+ </div>
+ </table>
+
+ <table id="table5">
+ <tbody style="display:block;">
+ <tr>
+ <td>bla</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="logtable" role="log"><tr><td>blah</td></tr></table>
+
+ <table id="block_table" style="display: block;">
+ <tr>
+ <td>bla</td>
+ </tr>
+ </table>
+
+ <table id="inline_table1" border="1" style="display:inline">
+ <tr>
+ <td>table1 cell1</td>
+ <td>table1 cell2</td>
+ </tr>
+ </table>
+
+ <table id="table_containing_inlinetable"><tr><td>
+ <table id="inline_table2" border="1" style="display:inline">
+ <tr id="tr_in_inline_table2">
+ <td id="td_in_inline_table2">cell</td>
+ </tr>
+ </table>
+ </td></tr></table>
+
+ <table id="table_containing_block_cell">
+ <tr>
+ <td>Normal cell</td>
+ <td style="display: block;">Block cell</td>
+ </tr>
+ </table>
+ <table id="table_all_display_block" style="display:block;">
+ <tbody style="display:block;">
+ <tr style="display:block;">
+ <td style="display:block;">text</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table_with_block_tbody_and_role">
+ <tbody style="display:block;" role="dialog">
+ <tr>
+ <td>text</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table_with_focusable_block_tbody">
+ <tbody style="display:block;" tabindex="0">
+ <tr>
+ <td>text</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table_containing_pos_styled_th">
+ <tr>
+ <th style="position: static">static colheader</th>
+ <th style="position: relative">relative colheader</th>
+ <th style="position: absolute">absolute colheader</th>
+ <th style="position: fixed">fixed colheader</th>
+ <th style="position: sticky">sticky colheader</th>
+ </tr>
+ <tr>
+ <th style="position: static">static rowheader</th>
+ <td/>
+ <th style="position: relative">relative rowheader</th>
+ <td/>
+ <th style="position: absolute">absolute rowheader</th>
+ <td/>
+ <th style="position: fixed">fixed rowheader</th>
+ <td/>
+ <th style="position: sticky">sticky rowheader</th>
+ <td/>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_table_2.html b/accessible/tests/mochitest/tree/test_table_2.html
new file mode 100644
index 0000000000..c0faece7e5
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_table_2.html
@@ -0,0 +1,242 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+<style>
+.responsive-table {
+ width: 100%;
+ margin-bottom: 1.5em;
+}
+.responsive-table thead {
+ position: absolute;
+ clip: rect(1px 1px 1px 1px);
+ /* IE6, IE7 */
+ clip: rect(1px, 1px, 1px, 1px);
+ padding: 0;
+ border: 0;
+ height: 1px;
+ width: 1px;
+ overflow: hidden;
+}
+.responsive-table thead th {
+ background-color: #1d96b2;
+ border: 1px solid #1d96b2;
+ font-weight: normal;
+ text-align: center;
+ color: white;
+}
+.responsive-table thead th:first-of-type {
+ text-align: left;
+}
+.responsive-table tbody,
+.responsive-table tr,
+.responsive-table th,
+.responsive-table td {
+ display: block;
+ padding: 0;
+ text-align: left;
+ white-space: normal;
+}
+.responsive-table th,
+.responsive-table td {
+ padding: .5em;
+ vertical-align: middle;
+}
+.responsive-table caption {
+ margin-bottom: 1em;
+ font-size: 1em;
+ font-weight: bold;
+ text-align: center;
+}
+.responsive-table tfoot {
+ font-size: .8em;
+ font-style: italic;
+}
+.responsive-table tbody tr {
+ margin-bottom: 1em;
+ border: 2px solid #1d96b2;
+}
+.responsive-table tbody tr:last-of-type {
+ margin-bottom: 0;
+}
+.responsive-table tbody th[scope="row"] {
+ background-color: #1d96b2;
+ color: white;
+}
+.responsive-table tbody td[data-type=currency] {
+ text-align: right;
+}
+.responsive-table tbody td[data-title]:before {
+ content: attr(data-title);
+ float: left;
+ font-size: .8em;
+ color: rgba(94, 93, 82, 0.75);
+}
+.responsive-table tbody td {
+ text-align: right;
+ border-bottom: 1px solid #1d96b2;
+}
+
+.responsive-table {
+ font-size: .9em;
+}
+.responsive-table thead {
+ position: relative;
+ clip: auto;
+ height: auto;
+ width: auto;
+ overflow: auto;
+}
+.responsive-table tr {
+ display: table-row;
+}
+.responsive-table th,
+.responsive-table td {
+ display: table-cell;
+ padding: .5em;
+}
+
+.responsive-table caption {
+ font-size: 1.5em;
+}
+.responsive-table tbody {
+ display: table-row-group;
+}
+.responsive-table tbody tr {
+ display: table-row;
+ border-width: 1px;
+}
+.responsive-table tbody tr:nth-of-type(even) {
+ background-color: rgba(94, 93, 82, 0.1);
+}
+.responsive-table tbody th[scope="row"] {
+ background-color: transparent;
+ color: #5e5d52;
+ text-align: left;
+}
+.responsive-table tbody td {
+ text-align: center;
+}
+.responsive-table tbody td[data-title]:before {
+ content: none;
+}
+</style>
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="application/javascript"
+ src="../common.js"></script>
+<script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+<script type="application/javascript">
+
+const COLHEADER = ROLE_COLUMNHEADER;
+const ROWHEADER = ROLE_ROWHEADER;
+const CELL = ROLE_CELL;
+const TEXT_LEAF = ROLE_TEXT_LEAF;
+
+function doTest() {
+ let accTree =
+ { TABLE: [
+ { CAPTION: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Top 10 Grossing Animated Films of All Time",
+ },
+ ] },
+ { TEXT_CONTAINER: [
+ { ROW: [
+ { role: COLHEADER, name: "Film Title" },
+ { role: COLHEADER, name: "Released" },
+ { role: COLHEADER, name: "Studio" },
+ { role: COLHEADER, name: "Worldwide Gross" },
+ { role: COLHEADER, name: "Domestic Gross" },
+ { role: COLHEADER, name: "Foreign Gross" },
+ { role: COLHEADER, name: "Budget" },
+ ] },
+ ] },
+ { ROW: [
+ { role: CELL },
+ ] },
+ { ROW: [
+ { role: ROWHEADER, name: "Toy Story 3" },
+ { CELL: [ { role: TEXT_LEAF, name: "2010" }] },
+ { CELL: [ { role: TEXT_LEAF, name: "Disney Pixar" }] },
+ { CELL: [ { role: TEXT_LEAF, name: "$1,063,171,911" }] },
+ { CELL: [ { role: TEXT_LEAF, name: "$415,004,880" }] },
+ { CELL: [ { role: TEXT_LEAF, name: "$648,167,031" }] },
+ { CELL: [ { role: TEXT_LEAF, name: "$200,000,000" }] },
+ ] },
+ { ROW: [
+ { role: ROWHEADER, name: "Shrek Forever After" },
+ { CELL: [ { role: TEXT_LEAF, name: "2010" }] },
+ { CELL: [ { role: TEXT_LEAF, name: "Dreamworks" }] },
+ { CELL: [ { role: TEXT_LEAF, name: "$752,600,867" }] },
+ { CELL: [ { role: TEXT_LEAF, name: "$238,736,787" }] },
+ { CELL: [ { role: TEXT_LEAF, name: "$513,864,080" }] },
+ { CELL: [ { role: TEXT_LEAF, name: "$165,000,000" }] },
+ ] },
+ ] };
+
+ testAccessibleTree("table", accTree);
+
+ SimpleTest.finish();
+}
+SimpleTest.waitForExplicitFinish();
+addA11yLoadEvent(doTest);
+</script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table class="responsive-table" id="table">
+ <caption>Top 10 Grossing Animated Films of All Time</caption>
+ <thead>
+ <tr>
+ <th scope="col">Film Title</th>
+ <th scope="col">Released</th>
+ <th scope="col">Studio</th>
+ <th scope="col">Worldwide Gross</th>
+ <th scope="col">Domestic Gross</th>
+ <th scope="col">Foreign Gross</th>
+ <th scope="col">Budget</th>
+ </tr>
+ </thead>
+ <tfoot>
+ <tr>
+ <td colspan="7">Sources: <a href="http://en.wikipedia.org/wiki/List_of_highest-grossing_animated_films" rel="external">Wikipedia</a> &amp; <a href="http://www.boxofficemojo.com/genres/chart/?id=animation.htm" rel="external">Box Office Mojo</a>. Data is current as of March 12, 2014</td>
+ </tr>
+ </tfoot>
+ <tbody>
+ <tr>
+ <th scope="row">Toy Story 3</th>
+ <td data-title="Released">2010</td>
+ <td data-title="Studio">Disney Pixar</td>
+ <td data-title="Worldwide Gross" data-type="currency">$1,063,171,911</td>
+ <td data-title="Domestic Gross" data-type="currency">$415,004,880</td>
+ <td data-title="Foreign Gross" data-type="currency">$648,167,031</td>
+ <td data-title="Budget" data-type="currency">$200,000,000</td>
+ </tr>
+ <tr>
+ <th scope="row">Shrek Forever After</th>
+ <td data-title="Released">2010</td>
+ <td data-title="Studio">Dreamworks</td>
+ <td data-title="Worldwide Gross" data-type="currency">$752,600,867</td>
+ <td data-title="Domestic Gross" data-type="currency">$238,736,787</td>
+ <td data-title="Foreign Gross" data-type="currency">$513,864,080</td>
+ <td data-title="Budget" data-type="currency">$165,000,000</td>
+ </tr>
+ </tbody>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_table_3.html b/accessible/tests/mochitest/tree/test_table_3.html
new file mode 100644
index 0000000000..60af4d7f82
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_table_3.html
@@ -0,0 +1,244 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+<style>
+.responsive-table {
+ width: 100%;
+ margin-bottom: 1.5em;
+}
+.responsive-table thead {
+ position: absolute;
+ clip: rect(1px 1px 1px 1px);
+ /* IE6, IE7 */
+ clip: rect(1px, 1px, 1px, 1px);
+ padding: 0;
+ border: 0;
+ height: 1px;
+ width: 1px;
+ overflow: hidden;
+}
+.responsive-table thead th {
+ background-color: #1d96b2;
+ border: 1px solid #1d96b2;
+ font-weight: normal;
+ text-align: center;
+ color: white;
+}
+.responsive-table thead th:first-of-type {
+ text-align: left;
+}
+.responsive-table tbody,
+.responsive-table tr,
+.responsive-table th,
+.responsive-table td {
+ display: block;
+ padding: 0;
+ text-align: left;
+ white-space: normal;
+}
+.responsive-table th,
+.responsive-table td {
+ padding: .5em;
+ vertical-align: middle;
+}
+.responsive-table caption {
+ margin-bottom: 1em;
+ font-size: 1em;
+ font-weight: bold;
+ text-align: center;
+}
+.responsive-table tfoot {
+ font-size: .8em;
+ font-style: italic;
+}
+.responsive-table tbody tr {
+ margin-bottom: 1em;
+ border: 2px solid #1d96b2;
+}
+.responsive-table tbody tr:last-of-type {
+ margin-bottom: 0;
+}
+.responsive-table tbody th[scope="row"] {
+ background-color: #1d96b2;
+ color: white;
+}
+.responsive-table tbody td[data-type=currency] {
+ text-align: right;
+}
+.responsive-table tbody td[data-title]:before {
+ content: attr(data-title);
+ float: left;
+ font-size: .8em;
+ color: #1d96b2;
+ font-weight: bold;
+}
+.responsive-table tbody td {
+ text-align: right;
+ border-bottom: 1px solid #1d96b2;
+}
+
+/* float everything */
+.responsive-table tbody tr {
+ float: left;
+ width: 48%;
+ margin-left: 2%;
+}
+</style>
+
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="application/javascript"
+ src="../common.js"></script>
+<script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+<script type="application/javascript">
+
+const COLHEADER = ROLE_COLUMNHEADER;
+const ROWHEADER = ROLE_ROWHEADER;
+const CELL = ROLE_CELL;
+const STATICTEXT = ROLE_STATICTEXT;
+const TEXT_LEAF = ROLE_TEXT_LEAF;
+const GROUPING = ROLE_GROUPING;
+
+function doTest() {
+ let accTree =
+ { TABLE: [
+ { CAPTION: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Top 10 Grossing Animated Films of All Time",
+ },
+ ] },
+ { TEXT_CONTAINER: [
+ { ROW: [
+ { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Film Title" } ] },
+ { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Released" } ] },
+ { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Studio" } ] },
+ { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Worldwide Gross" } ] },
+ { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Domestic Gross" } ] },
+ { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Foreign Gross" } ] },
+ { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Budget" } ] },
+ ] },
+ ] },
+ { ROW: [
+ { role: CELL },
+ ] },
+ { ROW: [
+ { ROWHEADER: [ { role: TEXT_LEAF, name: "Toy Story 3" } ] },
+ { CELL: [
+ { role: STATICTEXT, name: "Released" },
+ { role: TEXT_LEAF, name: "2010" },
+ ] },
+ { CELL: [
+ { role: STATICTEXT, name: "Studio" },
+ { role: TEXT_LEAF, name: "Disney Pixar" },
+ ] },
+ { CELL: [
+ { role: STATICTEXT, name: "Worldwide Gross" },
+ { role: TEXT_LEAF, name: "$1,063,171,911" },
+ ] },
+ { CELL: [
+ { role: STATICTEXT, name: "Domestic Gross" },
+ { role: TEXT_LEAF, name: "$415,004,880" },
+ ] },
+ { CELL: [
+ { role: STATICTEXT, name: "Foreign Gross" },
+ { role: TEXT_LEAF, name: "$648,167,031" },
+ ] },
+ { CELL: [
+ { role: STATICTEXT, name: "Budget" },
+ { role: TEXT_LEAF, name: "$200,000,000" },
+ ]},
+ ] },
+ { ROW: [
+ { ROWHEADER: [ { role: TEXT_LEAF, name: "Shrek Forever After" } ] },
+ { CELL: [
+ { role: STATICTEXT, name: "Released" },
+ { role: TEXT_LEAF, name: "2010" },
+ ] },
+ { CELL: [
+ { role: STATICTEXT, name: "Studio" },
+ { role: TEXT_LEAF, name: "Dreamworks" },
+ ] },
+ { CELL: [
+ { role: STATICTEXT, name: "Worldwide Gross" },
+ { role: TEXT_LEAF, name: "$752,600,867" },
+ ] },
+ { CELL: [
+ { role: STATICTEXT, name: "Domestic Gross" },
+ { role: TEXT_LEAF, name: "$238,736,787" },
+ ] },
+ { CELL: [
+ { role: STATICTEXT, name: "Foreign Gross" },
+ { role: TEXT_LEAF, name: "$513,864,080" },
+ ] },
+ { CELL: [
+ { role: STATICTEXT, name: "Budget" },
+ { role: TEXT_LEAF, name: "$165,000,000" },
+ ] },
+ ] },
+ ] };
+
+ testAccessibleTree("table", accTree);
+
+ SimpleTest.finish();
+}
+SimpleTest.waitForExplicitFinish();
+addA11yLoadEvent(doTest);
+</script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table class="responsive-table" id="table">
+ <caption>Top 10 Grossing Animated Films of All Time</caption>
+ <thead>
+ <tr>
+ <th scope="col">Film Title</th>
+ <th scope="col">Released</th>
+ <th scope="col">Studio</th>
+ <th scope="col">Worldwide Gross</th>
+ <th scope="col">Domestic Gross</th>
+ <th scope="col">Foreign Gross</th>
+ <th scope="col">Budget</th>
+ </tr>
+ </thead>
+ <tfoot>
+ <tr>
+ <td colspan="7">Sources: <a href="http://en.wikipedia.org/wiki/List_of_highest-grossing_animated_films" rel="external">Wikipedia</a> &amp; <a href="http://www.boxofficemojo.com/genres/chart/?id=animation.htm" rel="external">Box Office Mojo</a>. Data is current as of March 12, 2014</td>
+ </tr>
+ </tfoot>
+ <tbody>
+ <tr>
+ <th scope="row">Toy Story 3</th>
+ <td data-title="Released">2010</td>
+ <td data-title="Studio">Disney Pixar</td>
+ <td data-title="Worldwide Gross" data-type="currency">$1,063,171,911</td>
+ <td data-title="Domestic Gross" data-type="currency">$415,004,880</td>
+ <td data-title="Foreign Gross" data-type="currency">$648,167,031</td>
+ <td data-title="Budget" data-type="currency">$200,000,000</td>
+ </tr>
+ <tr>
+ <th scope="row">Shrek Forever After</th>
+ <td data-title="Released">2010</td>
+ <td data-title="Studio">Dreamworks</td>
+ <td data-title="Worldwide Gross" data-type="currency">$752,600,867</td>
+ <td data-title="Domestic Gross" data-type="currency">$238,736,787</td>
+ <td data-title="Foreign Gross" data-type="currency">$513,864,080</td>
+ <td data-title="Budget" data-type="currency">$165,000,000</td>
+ </tr>
+ </tbody>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_tree.xhtml b/accessible/tests/mochitest/tree/test_tree.xhtml
new file mode 100644
index 0000000000..e22b3faa9d
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_tree.xhtml
@@ -0,0 +1,182 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Accessible tree testers
+
+ function getTreeItemAccTree(aTableRole, acolumnCount)
+ {
+ var treeItemRole;
+ switch (aTableRole) {
+ case ROLE_LIST:
+ treeItemRole = ROLE_LISTITEM;
+ break;
+ case ROLE_OUTLINE:
+ treeItemRole = ROLE_OUTLINEITEM;
+ break;
+ case ROLE_TABLE: case ROLE_TREE_TABLE:
+ treeItemRole = ROLE_ROW;
+ break;
+ }
+
+ var accTree = {
+ role: treeItemRole,
+ children: []
+ };
+
+ if (aTableRole == ROLE_TABLE || aTableRole == ROLE_TREE_TABLE) {
+ for (var idx = 0; idx < acolumnCount; idx++) {
+ var cellAccTree = {
+ role: ROLE_GRID_CELL,
+ children: []
+ };
+ accTree.children.push(cellAccTree);
+ }
+ }
+
+ return accTree;
+ }
+
+ function testAccessibleTreeFor(aTree, aRole)
+ {
+ var accTreeForColumns = {
+ role: ROLE_LIST,
+ children: []
+ };
+
+ var accTreeForTree = {
+ role: aRole,
+ children: [
+ accTreeForColumns
+ ]
+ };
+
+ var view = aTree.view;
+ var columnCount = aTree.columns.count;
+
+ for (let idx = 0; idx < columnCount; idx++)
+ accTreeForColumns.children.push({ COLUMNHEADER: [ ] });
+ if (!aTree.hasAttribute("hidecolumnpicker")) {
+ accTreeForColumns.children.push({ PUSHBUTTON: [ ] });
+ accTreeForColumns.children.push({ MENUPOPUP: [ ] });
+ }
+
+ for (let idx = 0; idx < view.rowCount; idx++)
+ accTreeForTree.children.push(getTreeItemAccTree(aRole, columnCount));
+
+ testAccessibleTree(aTree, accTreeForTree);
+ }
+
+ /**
+ * Event queue invoker object to test accessible tree for XUL tree element.
+ */
+ function treeChecker(aID, aView, aRole)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.invoke = function invoke()
+ {
+ this.DOMNode.view = aView;
+ }
+ this.check = function check(aEvent)
+ {
+ testAccessibleTreeFor(this.DOMNode, aRole);
+ }
+ this.getID = function getID()
+ {
+ return "Tree testing of " + aID;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue(EVENT_REORDER);
+
+ gQueue.push(new treeChecker("list", new nsTableTreeView(3), ROLE_LIST));
+ gQueue.push(new treeChecker("tree", new nsTreeTreeView(), ROLE_OUTLINE));
+ gQueue.push(new treeChecker("table", new nsTableTreeView(3), ROLE_TABLE));
+ gQueue.push(new treeChecker("treetable", new nsTreeTreeView(), ROLE_TREE_TABLE));
+
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="list" flex="1" hidecolumnpicker="true">
+ <treecols>
+ <treecol id="col" flex="1" hideheader="true"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="table" flex="1">
+ <treecols>
+ <treecol id="col1" flex="1" label="column"/>
+ <treecol id="col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="treetable" flex="1">
+ <treecols>
+ <treecol id="col1" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/tree/test_txtcntr.html b/accessible/tests/mochitest/tree/test_txtcntr.html
new file mode 100644
index 0000000000..4c44701a41
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_txtcntr.html
@@ -0,0 +1,234 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>HTML text containers tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var accTree = {
+ role: ROLE_SECTION,
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ children: [],
+ },
+ ],
+ };
+
+ testAccessibleTree("c1", accTree);
+ testAccessibleTree("c2", accTree);
+
+ accTree = {
+ role: ROLE_SECTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Hello1",
+ },
+ {
+ role: ROLE_WHITESPACE,
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Hello2",
+ },
+ {
+ role: ROLE_SEPARATOR,
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Hello3 ",
+ },
+ {
+ role: ROLE_PARAGRAPH,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Hello4 ",
+ },
+ ],
+ },
+ ],
+ };
+
+ testAccessibleTree("c3", accTree);
+
+ // contentEditable div
+ accTree = {
+ role: ROLE_SECTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "helllo ",
+ },
+ {
+ role: ROLE_PARAGRAPH,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "blabla",
+ },
+ ],
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "hello ",
+ },
+ ],
+ };
+
+ testAccessibleTree("c4", accTree);
+
+ // blockquote
+ accTree = {
+ role: ROLE_SECTION,
+ children: [
+ { // block quote
+ role: ROLE_BLOCKQUOTE,
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ name: "Hello",
+ children: [],
+ },
+ ],
+ },
+ ],
+ };
+
+ testAccessibleTree("c5", accTree);
+
+ // abbreviation tag
+ accTree = {
+ role: ROLE_SECTION,
+ children: [
+ { // text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "This ",
+ children: [],
+ },
+ { // abbr tag
+ role: ROLE_TEXT,
+ name: "accessibility",
+ children: [
+ { // text leaf with actual text
+ role: ROLE_TEXT_LEAF,
+ name: "a11y",
+ children: [],
+ },
+ ],
+ },
+ { // text leaf
+ role: ROLE_TEXT_LEAF,
+ name: " test",
+ children: [],
+ },
+ ],
+ };
+
+ testAccessibleTree("c6", accTree);
+
+ // acronym tag
+ accTree = {
+ role: ROLE_SECTION,
+ children: [
+ { // text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "This ",
+ children: [],
+ },
+ { // acronym tag
+ role: ROLE_TEXT,
+ name: "personal computer",
+ children: [
+ { // text leaf with actual text
+ role: ROLE_TEXT_LEAF,
+ name: "PC",
+ children: [],
+ },
+ ],
+ },
+ { // text leaf
+ role: ROLE_TEXT_LEAF,
+ name: " is broken",
+ children: [],
+ },
+ ],
+ };
+
+ testAccessibleTree("c7", accTree);
+
+ // only whitespace between images should be exposed
+ accTree = {
+ SECTION: [
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] },
+ ],
+ };
+ testAccessibleTree("c8", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="overflowed content doesn't expose child text accessibles"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=489306">
+ Mozilla Bug 489306</a>
+ <a target="_blank"
+ title="Create child accessibles for text controls from native anonymous content"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=542824">
+ Mozilla Bug 542824</a>
+ <a target="_blank"
+ title="Update accessible tree on content insertion after layout"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=498015">
+ Mozilla Bug 498015</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="c1" style="width: 100px; height: 100px; overflow: auto;">
+ 1hellohello 2hellohello 3hellohello 4hellohello 5hellohello 6hellohello 7hellohello
+ </div>
+ <div id="c2">
+ 1hellohello 2hellohello 3hellohello 4hellohello 5hellohello 6hellohello 7hellohello
+ </div>
+ <div id="c3">
+ Hello1<br>
+ Hello2<hr>
+ Hello3
+ <p>
+ Hello4
+ </p>
+ </div>
+ <div id="c4" contentEditable="true">
+ helllo <p>blabla</p> hello
+ </div>
+ <div id="c5"><blockquote>Hello</blockquote></div>
+ <div id="c6">This <abbr title="accessibility">a11y</abbr> test</div>
+ <div id="c7">This <acronym title="personal computer">PC</acronym> is broken</div>
+
+ <!-- Whitespace between images should be exposed. Whitespace between the
+ div and img tags will be inconsistent depending on the image cache
+ state and what optimizations layout was able to apply. -->
+ <div id="c8"><img src="../moz.png"> <img src="../moz.png"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_txtctrl.html b/accessible/tests/mochitest/tree/test_txtctrl.html
new file mode 100644
index 0000000000..ee66fbf801
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_txtctrl.html
@@ -0,0 +1,171 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>HTML text controls tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // editable div
+ var accTree = {
+ role: ROLE_SECTION,
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ children: [],
+ },
+ ],
+ };
+
+ testAccessibleTree("txc1", accTree);
+
+ // input@type="text", value
+ accTree = {
+ role: ROLE_ENTRY,
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ children: [],
+ },
+ ],
+ };
+
+ testAccessibleTree("txc2", accTree);
+
+ // input@type="text", no value
+ accTree =
+ { ENTRY: [ ] };
+
+ testAccessibleTree("txc3", accTree);
+
+ // textarea
+ accTree = {
+ role: ROLE_ENTRY,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF, // hello1\nhello2 text
+ },
+ ],
+ };
+
+ testAccessibleTree("txc4", accTree);
+
+ // input@type="password"
+ accTree = {
+ role: ROLE_PASSWORD_TEXT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ children: [],
+ },
+ ],
+ };
+
+ testAccessibleTree("txc5", accTree);
+
+ // input@type="tel", value
+ accTree = {
+ role: ROLE_ENTRY,
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ children: [],
+ },
+ ],
+ };
+
+ testAccessibleTree("txc6", accTree);
+
+ // input@type="email", value
+ accTree = {
+ role: ROLE_EDITCOMBOBOX, // Because of list attribute
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ children: [],
+ },
+ ],
+ };
+
+ testAccessibleTree("txc7", accTree);
+
+ // input@type="search", value
+ accTree = {
+ role: ROLE_EDITCOMBOBOX, // Because of list attribute
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ children: [],
+ },
+ ],
+ };
+
+ testAccessibleTree("txc8", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="overflowed content doesn't expose child text accessibles"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=489306">
+ Mozilla Bug 489306
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=542824"
+ title="Create child accessibles for text controls from native anonymous content">
+ Mozilla Bug 542824
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652"
+ title="Make sure accessible tree is correct when rendered text is changed">
+ Mozilla Bug 625652
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="txc1" contentEditable="true">
+ 1hellohello
+ </div>
+ <input id="txc2" value="hello">
+ <input id="txc3">
+ <textarea id="txc4">
+ hello1
+ hello2
+ </textarea>
+ <input id="txc5" type="password" value="hello">
+ <input id="txc6" type="tel" value="4167771234">
+
+ Email Address:
+ <input id="txc7" type="email" list="contacts" value="xyzzy">
+ <datalist id="contacts">
+ <option>xyzzy@plughs.com</option>
+ <option>nobody@mozilla.org</option>
+ </datalist>
+
+ </br>Search for:
+ <input id="txc8" type="search" list="searchhisty" value="Gamma">
+ <datalist id="searchhisty">
+ <option>Gamma Rays</option>
+ <option>Gamma Ray Bursts</option>
+ </datalist>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_txtctrl.xhtml b/accessible/tests/mochitest/tree/test_txtctrl.xhtml
new file mode 100644
index 0000000000..ff3d4977f0
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_txtctrl.xhtml
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL textbox and textarea hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ //gA11yEventDumpToConsole = true;
+ //enableLogging("tree,verbose"); // debug stuff
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // search textbox
+ let accTree =
+ { GROUPING: [
+ { ENTRY: [ { TEXT_LEAF: [] } ] },
+ ] };
+ testAccessibleTree("txc_search", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // search textbox with search button
+
+ if (MAC) {
+ accTree =
+ { GROUPING: [
+ { ENTRY: [ { TEXT_LEAF: [] } ] },
+ ] };
+ } else {
+ accTree =
+ { GROUPING: [
+ { ENTRY: [ { TEXT_LEAF: [] } ] },
+ { PUSHBUTTON: [] },
+ ] };
+ }
+
+ testAccessibleTree("txc_search_searchbutton", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=542824"
+ title="Create child accessibles for text controls from native anonymous content">
+ Mozilla Bug 542824
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <box role="group" id="txc_search">
+ <search-textbox value="hello"/>
+ </box>
+ <box role="group" id="txc_search_searchbutton">
+ <search-textbox searchbutton="true" value="hello"/>
+ </box>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/tree/wnd.xhtml b/accessible/tests/mochitest/tree/wnd.xhtml
new file mode 100644
index 0000000000..3b87cb5e0d
--- /dev/null
+++ b/accessible/tests/mochitest/tree/wnd.xhtml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Empty Window">
+
+</window>
+
diff --git a/accessible/tests/mochitest/treeupdate/a11y.ini b/accessible/tests/mochitest/treeupdate/a11y.ini
new file mode 100644
index 0000000000..a40ef0ce05
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/a11y.ini
@@ -0,0 +1,46 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+ !/accessible/tests/mochitest/moz.png
+
+[test_ariadialog.html]
+[test_ariahidden.html]
+[test_ariaowns.html]
+[test_bug852150.xhtml]
+[test_bug883708.xhtml]
+[test_bug884251.xhtml]
+[test_bug895082.html]
+[test_bug1040735.html]
+[test_bug1175913.html]
+[test_bug1189277.html]
+[test_bug1276857.html]
+support-files = test_bug1276857_subframe.html
+[test_canvas.html]
+[test_contextmenu.xhtml]
+[test_cssoverflow.html]
+[test_deck.xhtml]
+[test_delayed_removal.html]
+[test_doc.html]
+[test_gencontent.html]
+[test_general.html]
+[test_hidden.html]
+[test_imagemap.html]
+[test_inert.html]
+[test_inner_reorder.html]
+[test_list.html]
+[test_list_editabledoc.html]
+[test_list_style.html]
+[test_listbox.xhtml]
+[test_menu.xhtml]
+[test_menubutton.xhtml]
+[test_optgroup.html]
+[test_recreation.html]
+[test_select.html]
+[test_shadow_slots.html]
+[test_shutdown.xhtml]
+[test_table.html]
+[test_textleaf.html]
+[test_tooltip.xhtml]
+[test_visibility.html]
+[test_whitespace.html]
diff --git a/accessible/tests/mochitest/treeupdate/test_ariadialog.html b/accessible/tests/mochitest/treeupdate/test_ariadialog.html
new file mode 100644
index 0000000000..0b7f4bbb56
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_ariadialog.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Table creation in ARIA dialog test</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function showARIADialog(aID) {
+ this.node = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.node),
+ ];
+
+ this.invoke = function showARIADialog_invoke() {
+ getNode("dialog").style.display = "block";
+ getNode("table").style.visibility = "visible";
+ getNode("a").textContent = "link";
+ getNode("input").value = "hello";
+ getNode("input").focus();
+ };
+
+ this.finalCheck = function showARIADialog_finalCheck() {
+ var tree = {
+ role: ROLE_DIALOG,
+ children: [
+ {
+ role: ROLE_PUSHBUTTON,
+ children: [ { role: ROLE_TEXT_LEAF } ],
+ },
+ {
+ role: ROLE_ENTRY,
+ },
+ ],
+ };
+ testAccessibleTree(aID, tree);
+ };
+
+ this.getID = function showARIADialog_getID() {
+ return "show ARIA dialog";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest() {
+ // enableLogging("tree");
+ gQueue = new eventQueue();
+
+ // make the accessible an inaccessible
+ gQueue.push(new showARIADialog("dialog"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Rework accessible tree update code"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
+ Mozilla Bug 570275
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="dialog" role="dialog" style="display: none;">
+ <table id="table" role="presentation"
+ style="display: block; position: absolute; top: 88px; left: 312.5px; z-index: 10010; visibility: hidden;">
+ <tbody>
+ <tr>
+ <td role="presentation">
+ <div role="presentation">
+ <a id="a" role="button">text</a>
+ </div>
+ <input id="input">
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_ariahidden.html b/accessible/tests/mochitest/treeupdate/test_ariahidden.html
new file mode 100644
index 0000000000..302465b59f
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_ariahidden.html
@@ -0,0 +1,118 @@
+<html>
+
+<head>
+ <title>aria-hidden tree update tests</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function t1_setARIAHidden() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, "t1"),
+ ];
+
+ this.invoke = function t1_setARIAHidden_invoke() {
+ getNode("t1_child").setAttribute("aria-hidden", "true");
+ };
+
+ this.finalCheck = function t1_setARIAHidden_finalCheck() {
+ ok(!isAccessible("t1_child"), "No accessible for aria-hidden");
+ };
+
+ this.getID = function t1_setARIAHidden_getID() {
+ return "aria-hidden set to true";
+ };
+ }
+
+ function t1_removeARIAHidden() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, "t1"),
+ ];
+
+ this.invoke = function t1_removeARIAHidden_invoke() {
+ getNode("t1_child").removeAttribute("aria-hidden");
+ };
+
+ this.finalCheck = function t1_removeARIAHidden_finalCheck() {
+ ok(isAccessible("t1_child"), "No aria-hidden, has to be accessible");
+ };
+
+ this.getID = function t1_removeARIAHidden_getID() {
+ return "remove aria-hidden";
+ };
+ }
+
+ function t2_setARIAHidden() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, "t2"),
+ ];
+
+ this.invoke = function t2_setARIAHidden_invoke() {
+ getNode("t2_child").setAttribute("aria-hidden", "true");
+ };
+
+ this.finalCheck = function t2_setARIAHidden_finalCheck() {
+ testAccessibleTree("t2", { SECTION: []});
+ };
+
+ this.getID = function t2_setARIAHidden_getID() {
+ return "t2: set aria-hidden";
+ };
+ }
+
+ function t2_insertUnderARIAHidden() {
+ this.eventSeq = [
+ new unexpectedInvokerChecker(EVENT_REORDER, "t2"),
+ ];
+
+ this.invoke = function t2_insertUnderARIAHidden_invoke() {
+ getNode("t2_child").innerHTML = "<input>";
+ };
+
+ this.finalCheck = function t2_insertUnderARIAHidden_finalCheck() {
+ testAccessibleTree("t2", { SECTION: []});
+ };
+
+ this.getID = function t2_insertUnderARIAHidden_getID() {
+ return "t2: insert under aria-hidden";
+ };
+ }
+
+ // gA11yEventDumpToConsole = true;
+ function doTests() {
+ ok(!isAccessible("t1_child"), "No accessible for aria-hidden");
+
+ const gQueue = new eventQueue();
+ gQueue.push(new t1_removeARIAHidden());
+ gQueue.push(new t1_setARIAHidden());
+ gQueue.push(new t2_setARIAHidden());
+ gQueue.push(new t2_insertUnderARIAHidden());
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="t1"><div id="t1_child" aria-hidden="true">Hi</div><div>there</div></div>
+ <div id="t2">
+ <span id="t2_child">hoho</span>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_ariaowns.html b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
new file mode 100644
index 0000000000..89d49efd5f
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
@@ -0,0 +1,851 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>@aria-owns attribute testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+ // //////////////////////////////////////////////////////////////////////////
+
+ function changeARIAOwns() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_button")),
+ // no hide for t1_subdiv because it is contained by hidden t1_checkbox
+ new invokerChecker(EVENT_HIDE, getNode("t1_checkbox")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_checkbox")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_button")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
+ new invokerChecker(EVENT_REORDER, getNode("t1_container")),
+ ];
+
+ this.invoke = function setARIAOwns_invoke() {
+ // children are swapped by ARIA owns
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [
+ { SECTION: [] },
+ ] },
+ { PUSHBUTTON: [ ] },
+ ] };
+ testAccessibleTree("t1_container", tree);
+
+ getNode("t1_container").
+ setAttribute("aria-owns", "t1_button t1_subdiv");
+ };
+
+ this.finalCheck = function setARIAOwns_finalCheck() {
+ // children are swapped again, button and subdiv are appended to
+ // the children.
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] }, // checkbox, native order
+ { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own
+ { SECTION: [ ] }, // subdiv from the subtree, ARIA owned
+ ] };
+ testAccessibleTree("t1_container", tree);
+ };
+
+ this.getID = function setARIAOwns_getID() {
+ return "Change @aria-owns attribute";
+ };
+ }
+
+ function removeARIAOwns() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_button")),
+ new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
+ new orderChecker(),
+ new asyncInvokerChecker(EVENT_SHOW, getNode("t1_button")),
+ new asyncInvokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
+ new orderChecker(),
+ new invokerChecker(EVENT_REORDER, getNode("t1_container")),
+ new unexpectedInvokerChecker(EVENT_REORDER, getNode("t1_checkbox")),
+ ];
+
+ this.invoke = function removeARIAOwns_invoke() {
+ getNode("t1_container").removeAttribute("aria-owns");
+ };
+
+ this.finalCheck = function removeARIAOwns_finalCheck() {
+ // children follow the DOM order
+ var tree =
+ { SECTION: [
+ { PUSHBUTTON: [ ] },
+ { CHECKBUTTON: [
+ { SECTION: [] },
+ ] },
+ ] };
+ testAccessibleTree("t1_container", tree);
+ };
+
+ this.getID = function removeARIAOwns_getID() {
+ return "Remove @aria-owns attribute";
+ };
+ }
+
+ function setARIAOwns() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_button")),
+ new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_button")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
+ new invokerChecker(EVENT_REORDER, getNode("t1_container")),
+ ];
+
+ this.invoke = function setARIAOwns_invoke() {
+ getNode("t1_container").
+ setAttribute("aria-owns", "t1_button t1_subdiv");
+ };
+
+ this.finalCheck = function setARIAOwns_finalCheck() {
+ // children are swapped again, button and subdiv are appended to
+ // the children.
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] }, // checkbox
+ { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own
+ { SECTION: [ ] }, // subdiv from the subtree, ARIA owned
+ ] };
+ testAccessibleTree("t1_container", tree);
+ };
+
+ this.getID = function setARIAOwns_getID() {
+ return "Set @aria-owns attribute";
+ };
+ }
+
+ function addIdToARIAOwns() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_group")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_group")),
+ new invokerChecker(EVENT_REORDER, document),
+ ];
+
+ this.invoke = function addIdToARIAOwns_invoke() {
+ getNode("t1_container").
+ setAttribute("aria-owns", "t1_button t1_subdiv t1_group");
+ };
+
+ this.finalCheck = function addIdToARIAOwns_finalCheck() {
+ // children are swapped again, button and subdiv are appended to
+ // the children.
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] }, // t1_checkbox
+ { PUSHBUTTON: [ ] }, // button, t1_button
+ { SECTION: [ ] }, // subdiv from the subtree, t1_subdiv
+ { GROUPING: [ ] }, // group from outside, t1_group
+ ] };
+ testAccessibleTree("t1_container", tree);
+ };
+
+ this.getID = function addIdToARIAOwns_getID() {
+ return "Add id to @aria-owns attribute value";
+ };
+ }
+
+ function appendEl() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getNode, "t1_child3"),
+ new invokerChecker(EVENT_REORDER, getNode("t1_container")),
+ ];
+
+ this.invoke = function appendEl_invoke() {
+ var div = document.createElement("div");
+ div.setAttribute("id", "t1_child3");
+ div.setAttribute("role", "radio");
+ getNode("t1_container").appendChild(div);
+ };
+
+ this.finalCheck = function appendEl_finalCheck() {
+ // children are invalidated, they includes aria-owns swapped kids and
+ // newly inserted child.
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] }, // existing explicit, t1_checkbox
+ { RADIOBUTTON: [ ] }, // new explicit, t1_child3
+ { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+ { SECTION: [ ] }, // ARIA owned, t1_subdiv
+ { GROUPING: [ ] }, // ARIA owned, t1_group
+ ] };
+ testAccessibleTree("t1_container", tree);
+ };
+
+ this.getID = function appendEl_getID() {
+ return "Append child under @aria-owns element";
+ };
+ }
+
+ function removeEl() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
+ new invokerChecker(EVENT_REORDER, getNode("t1_container")),
+ ];
+
+ this.invoke = function removeEl_invoke() {
+ // remove a container of t1_subdiv
+ getNode("t1_span").remove();
+ };
+
+ this.finalCheck = function removeEl_finalCheck() {
+ // subdiv should go away
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] }, // explicit, t1_checkbox
+ { RADIOBUTTON: [ ] }, // explicit, t1_child3
+ { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+ { GROUPING: [ ] }, // ARIA owned, t1_group
+ ] };
+ testAccessibleTree("t1_container", tree);
+ };
+
+ this.getID = function removeEl_getID() {
+ return "Remove a container of ARIA owned element";
+ };
+ }
+
+ function removeId() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_group")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_group")),
+ new invokerChecker(EVENT_REORDER, document),
+ ];
+
+ this.invoke = function removeId_invoke() {
+ getNode("t1_group").removeAttribute("id");
+ };
+
+ this.finalCheck = function removeId_finalCheck() {
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] },
+ { RADIOBUTTON: [ ] },
+ { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+ ] };
+ testAccessibleTree("t1_container", tree);
+ };
+
+ this.getID = function removeId_getID() {
+ return "Remove ID from ARIA owned element";
+ };
+ }
+
+ function setId() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_grouptmp")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_grouptmp")),
+ new invokerChecker(EVENT_REORDER, document),
+ ];
+
+ this.invoke = function setId_invoke() {
+ getNode("t1_grouptmp").setAttribute("id", "t1_group");
+ };
+
+ this.finalCheck = function setId_finalCheck() {
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] },
+ { RADIOBUTTON: [ ] },
+ { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+ { GROUPING: [ ] }, // ARIA owned, t1_group, previously t1_grouptmp
+ ] };
+ testAccessibleTree("t1_container", tree);
+ };
+
+ this.getID = function setId_getID() {
+ return "Set ID that is referred by ARIA owns";
+ };
+ }
+
+ /**
+ * Remove an accessible DOM element containing an element referred by
+ * ARIA owns.
+ */
+ function removeA11eteiner() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode("t2_container1")),
+ ];
+
+ this.invoke = function removeA11eteiner_invoke() {
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] }, // ARIA owned, 't2_owned'
+ ] };
+ testAccessibleTree("t2_container1", tree);
+
+ getNode("t2_container2").removeChild(getNode("t2_container3"));
+ };
+
+ this.finalCheck = function removeA11eteiner_finalCheck() {
+ var tree =
+ { SECTION: [
+ ] };
+ testAccessibleTree("t2_container1", tree);
+ };
+
+ this.getID = function removeA11eteiner_getID() {
+ return "Remove an accessible DOM element containing an element referred by ARIA owns";
+ };
+ }
+
+ /**
+ * Attempt to steal an element from other ARIA owns element. This should
+ * not be possible. The only child that will get owned into this
+ * container is a previously not aria-owned one.
+ */
+ function stealFromOtherARIAOwns() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode("t3_container3")),
+ ];
+
+ this.invoke = function stealFromOtherARIAOwns_invoke() {
+ getNode("t3_container3").setAttribute("aria-owns", "t3_child t3_child2");
+ };
+
+ this.finalCheck = function stealFromOtherARIAOwns_finalCheck() {
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [
+ ] },
+ ] };
+ testAccessibleTree("t3_container1", tree);
+
+ tree =
+ { SECTION: [
+ ] };
+ testAccessibleTree("t3_container2", tree);
+
+ tree =
+ { SECTION: [
+ { CHECKBUTTON: [
+ ] },
+ ] };
+ testAccessibleTree("t3_container3", tree);
+ };
+
+ this.getID = function stealFromOtherARIAOwns_getID() {
+ return "Steal an element from other ARIA owns element";
+ };
+ }
+
+ function appendElToRecacheChildren() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode("t3_container3")),
+ ];
+
+ this.invoke = function appendElToRecacheChildren_invoke() {
+ var div = document.createElement("div");
+ div.setAttribute("role", "radio");
+ getNode("t3_container3").appendChild(div);
+ };
+
+ this.finalCheck = function appendElToRecacheChildren_finalCheck() {
+ var tree =
+ { SECTION: [
+ ] };
+ testAccessibleTree("t3_container2", tree);
+
+ tree =
+ { SECTION: [
+ { RADIOBUTTON: [ ] },
+ { CHECKBUTTON: [ ] }, // ARIA owned
+ ] };
+ testAccessibleTree("t3_container3", tree);
+ };
+
+ this.getID = function appendElToRecacheChildren_getID() {
+ return "Append a child under @aria-owns element to trigger children recache";
+ };
+ }
+
+ function showHiddenElement() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode("t4_container1")),
+ ];
+
+ this.invoke = function showHiddenElement_invoke() {
+ var tree =
+ { SECTION: [
+ { RADIOBUTTON: [] },
+ ] };
+ testAccessibleTree("t4_container1", tree);
+
+ getNode("t4_child1").style.display = "block";
+ };
+
+ this.finalCheck = function showHiddenElement_finalCheck() {
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [] },
+ { RADIOBUTTON: [] },
+ ] };
+ testAccessibleTree("t4_container1", tree);
+ };
+
+ this.getID = function showHiddenElement_getID() {
+ return "Show hidden ARIA owns referred element";
+ };
+ }
+
+ function rearrangeARIAOwns(aContainer, aAttr, aIdList, aRoleList) {
+ this.eventSeq = [];
+ for (let id of aIdList) {
+ this.eventSeq.push(new invokerChecker(EVENT_HIDE, getNode(id)));
+ }
+
+ for (let id of aIdList) {
+ this.eventSeq.push(new invokerChecker(EVENT_SHOW, getNode(id)));
+ }
+ this.eventSeq.push(new invokerChecker(EVENT_REORDER, getNode(aContainer)));
+
+ this.invoke = function rearrangeARIAOwns_invoke() {
+ getNode(aContainer).setAttribute("aria-owns", aAttr);
+ };
+
+ this.finalCheck = function rearrangeARIAOwns_finalCheck() {
+ var tree = { SECTION: [ ] };
+ for (var role of aRoleList) {
+ var ch = {};
+ ch[role] = [];
+ tree.SECTION.push(ch);
+ }
+ testAccessibleTree(aContainer, tree);
+ };
+
+ this.getID = function rearrangeARIAOwns_getID() {
+ return `Rearrange @aria-owns attribute to '${aAttr}'`;
+ };
+ }
+
+ function removeNotARIAOwnedEl(aContainer, aChild) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aContainer),
+ ];
+
+ this.invoke = function removeNotARIAOwnedEl_invoke() {
+ var tree = {
+ SECTION: [
+ { TEXT_LEAF: [ ] },
+ { GROUPING: [ ] },
+ ],
+ };
+ testAccessibleTree(aContainer, tree);
+
+ getNode(aContainer).removeChild(getNode(aChild));
+ };
+
+ this.finalCheck = function removeNotARIAOwnedEl_finalCheck() {
+ var tree = {
+ SECTION: [
+ { GROUPING: [ ] },
+ ],
+ };
+ testAccessibleTree(aContainer, tree);
+ };
+
+ this.getID = function removeNotARIAOwnedEl_getID() {
+ return `remove not ARIA owned child`;
+ };
+ }
+
+ function setARIAOwnsOnElToRemove(aParent, aChild) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getAccessible(aParent)),
+ ];
+
+ this.invoke = function setARIAOwnsOnElToRemove_invoke() {
+ getNode(aChild).setAttribute("aria-owns", "no_id");
+ getNode(aParent).removeChild(getNode(aChild));
+ getNode(aParent).remove();
+ };
+
+ this.getID = function setARIAOwnsOnElToRemove_getID() {
+ return `set ARIA owns on an element, and then remove it, and then remove its parent`;
+ };
+ }
+
+ /**
+ * Set ARIA owns on inaccessible span element that contains
+ * accessible children. This will move children from the container for
+ * the span.
+ */
+ function test8() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, "t8_container"),
+ ];
+
+ this.invoke = function test8_invoke() {
+ var tree =
+ { SECTION: [
+ { PUSHBUTTON: [] },
+ { ENTRY: [] },
+ { ENTRY: [] },
+ { ENTRY: [] },
+ ] };
+ testAccessibleTree("t8_container", tree);
+
+ getNode(t8_container).setAttribute("aria-owns", "t8_span t8_button");
+ };
+
+ this.finalCheck = function test8_finalCheck() {
+ var tree =
+ { SECTION: [
+ { TEXT: [
+ { ENTRY: [] },
+ { ENTRY: [] },
+ { ENTRY: [] },
+ ] },
+ { PUSHBUTTON: [] },
+ ] };
+ testAccessibleTree("t8_container", tree);
+ };
+
+ this.getID = function test8_getID() {
+ return `Set ARIA owns on inaccessible span element that contains accessible children`;
+ };
+ }
+
+ function test9_prepare() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, () => {
+ return getNode("t9_container").contentDocument;
+ }),
+ ];
+
+ this.invoke = () => {
+ // The \ before the final /script avoids the script from being terminated
+ // by the html parser.
+ getNode("t9_container").src = `data:text/html,
+ <html><body></body>
+ <script>
+ let el = document.createElement('div');
+ el.id = 'container';
+ el.innerHTML = "<input id='input'>";
+ document.documentElement.appendChild(el);
+ <\/script></html>`;
+ };
+
+ this.finalCheck = () => {
+ var tree =
+ { INTERNAL_FRAME: [
+ { DOCUMENT: [
+ { SECTION: [
+ { ENTRY: [] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("t9_container", tree);
+ };
+
+ this.getID = () => {
+ return `Set ARIA owns on a document (part1)`;
+ };
+ }
+
+ function test9_setARIAOwns() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, () => {
+ let doc = getNode("t9_container").contentDocument;
+ return doc && doc.getElementById("input");
+ }),
+ ];
+
+ this.invoke = () => {
+ let doc = getNode("t9_container").contentDocument;
+ doc.body.setAttribute("aria-owns", "input");
+ };
+
+ this.finalCheck = () => {
+ var tree =
+ { INTERNAL_FRAME: [
+ { DOCUMENT: [
+ { SECTION: [] },
+ { ENTRY: [] },
+ ] },
+ ] };
+ testAccessibleTree("t9_container", tree);
+ };
+
+ this.getID = () => {
+ return `Set ARIA owns on a document (part2)`;
+ };
+ }
+
+ function test9_finish() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, () => {
+ return getNode("t9_container").contentDocument;
+ }),
+ ];
+
+ this.invoke = () => {
+ // trigger a tree update.
+ let doc = getNode("t9_container").contentDocument;
+ doc.body.appendChild(doc.createElement("p"));
+ };
+
+ this.finalCheck = () => {
+ var tree =
+ { INTERNAL_FRAME: [
+ { DOCUMENT: [
+ { PARAGRAPH: [] },
+ { SECTION: [] },
+ { ENTRY: [] },
+ ] },
+ ] };
+ testAccessibleTree("t9_container", tree);
+ };
+
+ this.getID = () => {
+ return `Set ARIA owns on a document (part3)`;
+ };
+ }
+
+ /**
+ * Put ARIA owned child back when ARIA owner removed.
+ */
+ function test10_removeARIAOwner() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getAccessible("t10_owner")),
+ ];
+
+ this.invoke = () => {
+ let tree =
+ { SECTION: [ // t10_container
+ { SECTION: [ // t10_owner
+ { ENTRY: [] }, // t10_child
+ ] },
+ ] };
+ testAccessibleTree("t10_container", tree);
+
+ getNode("t10_owner").remove();
+ };
+
+ this.getID = () => {
+ return "Put aria owned child back when aria owner removed";
+ };
+ }
+
+ function test10_finishTest() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, "t10_container"),
+ ];
+
+ this.invoke = () => {
+ // trigger a tree update.
+ getNode("t10_container").append(document.createElement("p"));
+ };
+
+ this.finalCheck = () => {
+ let tree =
+ { SECTION: [ // t10_container
+ // { ENTRY: [] }, // t10_child
+ { PARAGRAPH: [] },
+ ] };
+ testAccessibleTree("t10_container", tree);
+ todo(false, "Input accessible has be moved back in the tree");
+ };
+
+ this.getID = () => {
+ return `Put aria owned child back when aria owner removed (finish test)`;
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+ // //////////////////////////////////////////////////////////////////////////
+
+ // gA11yEventDumpToConsole = true;
+ // enableLogging("tree,eventTree,verbose"); // debug stuff
+
+ var gQueue = null;
+
+ async function doTest() {
+ let PromEvents = {};
+ Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js",
+ PromEvents);
+
+ gQueue = new eventQueue();
+ let queueFinished = new Promise(resolve => {
+ gQueue.onFinish = function() {
+ resolve();
+ return DO_NOT_FINISH_TEST;
+ };
+ });
+
+ // test1
+ gQueue.push(new changeARIAOwns());
+ gQueue.push(new removeARIAOwns());
+ gQueue.push(new setARIAOwns());
+ gQueue.push(new addIdToARIAOwns());
+ gQueue.push(new appendEl());
+ gQueue.push(new removeEl());
+ gQueue.push(new removeId());
+ gQueue.push(new setId());
+
+ // test2
+ gQueue.push(new removeA11eteiner());
+
+ // test3
+ gQueue.push(new stealFromOtherARIAOwns());
+ gQueue.push(new appendElToRecacheChildren());
+
+ // test4
+ gQueue.push(new showHiddenElement());
+
+ // test5
+ gQueue.push(new rearrangeARIAOwns(
+ "t5_container", "t5_checkbox t5_radio t5_button",
+ [ "t5_checkbox", "t5_radio", "t5_button" ],
+ [ "CHECKBUTTON", "RADIOBUTTON", "PUSHBUTTON" ]));
+ gQueue.push(new rearrangeARIAOwns(
+ "t5_container", "t5_radio t5_button t5_checkbox",
+ [ "t5_radio", "t5_button" ],
+ [ "RADIOBUTTON", "PUSHBUTTON", "CHECKBUTTON" ]));
+
+ gQueue.push(new removeNotARIAOwnedEl("t6_container", "t6_span"));
+
+ gQueue.push(new setARIAOwnsOnElToRemove("t7_parent", "t7_child"));
+
+ gQueue.push(new test8());
+ gQueue.push(new test9_prepare());
+ gQueue.push(new test9_setARIAOwns());
+ gQueue.push(new test9_finish());
+
+ gQueue.push(new test10_removeARIAOwner());
+ gQueue.push(new test10_finishTest());
+
+ gQueue.invoke();
+ await queueFinished;
+
+ let owned = document.createElement('div');
+ owned.id = 't11_child';
+ owned.textContent = 'owned';
+ let evtPromise = PromEvents.waitForEvent(EVENT_SHOW, "t11_child");
+ getNode("t11_container").append(owned);
+ let evt = await evtPromise;
+ is(evt.accessible.parent.name, "t11_owner");
+
+ // Test owning an ancestor which isn't created yet.
+ testAccessibleTree("t12_container", { SECTION: [ // t12_container
+ { SECTION: [ // t12b
+ { SECTION: [] }, // t12c
+ ] },
+ { SECTION: [] }, // t12d
+ ] });
+ // Owning t12a would create a cycle, so we expect it to do nothing.
+ // We own t12d so we get an event when aria-owns relocation is complete.
+ evtPromise = PromEvents.waitForEvent(EVENT_SHOW, "t12d");
+ getNode("t12c").setAttribute("aria-owns", "t12a t12d");
+ await evtPromise;
+ testAccessibleTree("t12_container", { SECTION: [ // t12_container
+ { SECTION: [ // t12b
+ { SECTION: [ // t12c
+ { SECTION: [] }, // t12d
+ ] },
+ ] },
+ ] });
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+
+ </script>
+</head>
+
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="t1_container" aria-owns="t1_checkbox t1_button">
+ <div role="button" id="t1_button"></div>
+ <div role="checkbox" id="t1_checkbox">
+ <span id="t1_span">
+ <div id="t1_subdiv"></div>
+ </span>
+ </div>
+ </div>
+ <div id="t1_group" role="group"></div>
+ <div id="t1_grouptmp" role="group"></div>
+
+ <div id="t2_container1" aria-owns="t2_owned"></div>
+ <div id="t2_container2">
+ <div id="t2_container3"><div id="t2_owned" role="checkbox"></div></div>
+ </div>
+
+ <div id="t3_container1" aria-owns="t3_child"></div>
+ <div id="t3_child" role="checkbox"></div>
+ <div id="t3_container2">
+ <div id="t3_child2" role="checkbox"></div>
+ </div>
+ <div id="t3_container3"></div>
+
+ <div id="t4_container1" aria-owns="t4_child1 t4_child2"></div>
+ <div id="t4_container2">
+ <div id="t4_child1" style="display:none" role="checkbox"></div>
+ <div id="t4_child2" role="radio"></div>
+ </div>
+
+ <div id="t5_container">
+ <div role="button" id="t5_button"></div>
+ <div role="checkbox" id="t5_checkbox"></div>
+ <div role="radio" id="t5_radio"></div>
+ </div>
+
+ <div id="t6_container" aria-owns="t6_fake">
+ <span id="t6_span">hey</span>
+ </div>
+ <div id="t6_fake" role="group"></div>
+
+ <div id="t7_container">
+ <div id="t7_parent">
+ <div id="t7_child"></div>
+ </div>
+ </div>
+
+ <div id="t8_container">
+ <input id="t8_button" type="button"><span id="t8_span"><input><input><input></span>
+ </div>
+
+ <iframe id="t9_container"></iframe>
+
+ <div id="t10_container">
+ <div id="t10_owner" aria-owns="t10_child"></div>
+ <input id="t10_child">
+ </div>
+
+ <div id="t11_container" aria-label="t11_container">
+ <div aria-owns="t11_child" aria-label="t11_owner"></div>
+ </div>
+
+ <div id="t12_container">
+ <span id="t12a">
+ <div id="t12b" aria-owns="t12c"></div>
+ </span>
+ <div id="t12c"></div>
+ <div id="t12d"></div>
+ </div>
+</body>
+
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug1040735.html b/accessible/tests/mochitest/treeupdate/test_bug1040735.html
new file mode 100644
index 0000000000..b7d0e472d0
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1040735.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Adopt DOM node from anonymous subtree</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ document.body.appendChild(document.getElementById("mw_a"));
+ setTimeout(function() { ok(true, "no crash and assertions"); SimpleTest.finish(); }, 0);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1040735"
+ title="Bug 1040735 - DOM node reinsertion under anonymous content may trigger a11y child adoption">
+ Bug 1040735</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <marquee>
+ <div id="mw_a" style="visibility: hidden;">
+ <div style="visibility: visible;" id="mw_inside"></div>
+ </div>
+ </marquee>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug1175913.html b/accessible/tests/mochitest/treeupdate/test_bug1175913.html
new file mode 100644
index 0000000000..1fe2720434
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1175913.html
@@ -0,0 +1,95 @@
+<html>
+
+<head>
+ <title>Test hide/show events on event listener changes</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ function dummyListener() {}
+
+ function testAddListener() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getNode("parent")),
+ ];
+
+ this.invoke = function testAddListener_invoke() {
+ is(getAccessible("parent", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that parent is not accessible.");
+ is(getAccessible("child", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that child is not accessible.");
+ getNode("parent").addEventListener("click", dummyListener);
+ };
+
+ this.finalCheck = function testAddListener_finalCheck() {
+ var tree = { TEXT: [] };
+ testAccessibleTree("parent", tree);
+ };
+
+ this.getID = function testAddListener_getID() {
+ return "Test that show event is sent when click listener is added";
+ };
+ }
+
+ function testRemoveListener() {
+ this.eventSeq = [
+ new unexpectedInvokerChecker(EVENT_HIDE, getNode("parent")),
+ ];
+
+ this.invoke = function testRemoveListener_invoke() {
+ getNode("parent").removeEventListener("click", dummyListener);
+ };
+
+ this.finalCheck = function testRemoveListener_finalCheck() {
+ ok(getAccessible("parent", null, null, DONOTFAIL_IF_NO_ACC),
+ "Parent stays accessible after click event listener is removed");
+ ok(!getAccessible("child", null, null, DONOTFAIL_IF_NO_ACC),
+ "Child stays inaccessible");
+ };
+
+ this.getID = function testRemoveListener_getID() {
+ return "Test that hide event is sent when click listener is removed";
+ };
+ }
+
+ var gQueue = null;
+ function doTest() {
+ gQueue = new eventQueue();
+ gQueue.push(new testAddListener());
+ gQueue.push(new testRemoveListener());
+ gQueue.invoke(); // SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175913"
+ title="Crash in mozilla::a11y::DocAccessibleParent::RemoveAccessible(ProxyAccessible* aAccessible)">
+ Mozilla Bug 1175913
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <span id="parent">
+ <span id="child">
+ </span>
+ </span>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug1189277.html b/accessible/tests/mochitest/treeupdate/test_bug1189277.html
new file mode 100644
index 0000000000..95efe5135a
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1189277.html
@@ -0,0 +1,82 @@
+<html>
+
+<head>
+ <title>Test hide/show events for HTMLListBulletAccessibles on list restyle</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function runTest() {
+ this.containerNode = getNode("outerDiv");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("child")),
+ new invokerChecker(EVENT_HIDE, getNode("childDoc")),
+ new invokerChecker(EVENT_SHOW, "newChildDoc"),
+ new invokerChecker(EVENT_REORDER, this.containerNode),
+ ];
+
+ this.invoke = function runTest_invoke() {
+ this.containerNode.removeChild(getNode("child"));
+
+ var docContainer = getNode("docContainer");
+ var iframe = document.createElement("iframe");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ iframe.setAttribute("src", "http://example.com");
+ iframe.setAttribute("id", "newChildDoc");
+
+ docContainer.removeChild(getNode("childDoc"));
+ docContainer.appendChild(iframe);
+ };
+
+ this.getID = function runTest_getID() {
+ return "check show events are not incorrectly coalesced";
+ };
+ }
+
+ // enableLogging("tree");
+ gA11yEventDumpToConsole = true;
+ var gQueue = null;
+ function doTest() {
+ gQueue = new eventQueue();
+ gQueue.push(new runTest());
+ gQueue.invoke(); // SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1189277"
+ title="content process crash caused by missing show event">
+ Mozilla Bug 1189277
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="outerDiv">
+ <div id="child">foo</div>
+ <div id="docContainer">
+ <iframe id="childDoc" src="about:blank">
+ </iframe>
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug1276857.html b/accessible/tests/mochitest/treeupdate/test_bug1276857.html
new file mode 100644
index 0000000000..a164247534
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1276857.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>DOM mutations test</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function runTest() {
+ let iframe = document.getElementById("iframe");
+
+ // children change will recreate the table
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, () => {
+ let doc = getNode("iframe").contentDocument;
+ return doc && doc.getElementById("c1");
+ }),
+ ];
+
+ this.invoke = function runTest_invoke() {
+ var tree = {
+ SECTION: [ // c1
+ { TEXT_LEAF: [] }, // Some text
+ { TEXT_CONTAINER: [
+ { TEXT_LEAF: [] }, // something with ..
+ ] },
+ { TEXT_LEAF: [] }, // More text
+ ],
+ };
+ testAccessibleTree(iframe.contentDocument.getElementById("c1"), tree);
+
+ iframe.contentDocument.getElementById("c1_t").querySelector("span").remove();
+ };
+
+ this.finalCheck = function runTest_finalCheck() {
+ var tree = {
+ SECTION: [ // c1
+ { TEXT_LEAF: [] }, // Some text
+ { TEXT_LEAF: [] }, // More text
+ ],
+ };
+ testAccessibleTree(iframe.contentDocument.getElementById("c1"), tree);
+ };
+
+ this.getID = function runTest_getID() {
+ return "child DOM node is removed before the layout notifies the a11y about parent removal/show";
+ };
+ }
+
+ function runShadowTest() {
+ // children change will recreate the table
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, () => {
+ let doc = getNode("iframe").contentDocument;
+ return doc && doc.getElementById("c2");
+ }),
+ ];
+
+ this.invoke = function runShadowTest_invoke() {
+ var tree = {
+ SECTION: [ // c2
+ { TEXT_LEAF: [] }, // Some text
+ { TEXT_CONTAINER: [
+ { TEXT_LEAF: [] }, // something with ..
+ ] },
+ { TEXT_LEAF: [] }, // More text
+ ],
+ };
+ const iframe = document.getElementById("iframe");
+ testAccessibleTree(iframe.contentDocument.getElementById("c2"), tree);
+
+ var shadowRoot = iframe.contentDocument.getElementById("c2_c").shadowRoot;
+ shadowRoot.firstElementChild.querySelector("span").remove();
+ // bug 1487312
+ shadowRoot.firstElementChild.offsetTop;
+ shadowRoot.appendChild(document.createElement("button"));
+ };
+
+ this.finalCheck = function runShadowTest_finalCheck() {
+ var tree = {
+ SECTION: [ // c2
+ { TEXT_LEAF: [] }, // Some text
+ { TEXT_LEAF: [] }, // More text
+ { PUSHBUTTON: [] }, // The button we appended.
+ ],
+ };
+ const iframe = document.getElementById("iframe");
+ testAccessibleTree(iframe.contentDocument.getElementById("c2"), tree);
+ };
+
+ this.getID = function runShadowTest_getID() {
+ return "child DOM node is removed before the layout notifies the a11y about parent removal/show in shadow DOM";
+ };
+ }
+
+ // enableLogging("tree");
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTest() {
+ gQueue = new eventQueue();
+ gQueue.push(new runTest());
+ gQueue.push(new runShadowTest());
+ gQueue.invoke(); // will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ window.onload = () => {
+ let iframe = document.createElement("iframe");
+ iframe.id = "iframe";
+ iframe.src = "test_bug1276857_subframe.html";
+ addA11yLoadEvent(doTest, iframe.contentWindow);
+ document.body.appendChild(iframe);
+ };
+ </script>
+
+</head>
+<body>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug1276857_subframe.html b/accessible/tests/mochitest/treeupdate/test_bug1276857_subframe.html
new file mode 100644
index 0000000000..869c9ebe6c
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1276857_subframe.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>DOM mutations test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript" src="../role.js"></script>
+</head>
+<body>
+ <div id="c1">
+ <div id="c1_t" style="display: table" role="presentation">
+ Some text
+ <span style="display: table-cell">something with accessibles goes here</span>
+ More text
+ </div>
+ </div>
+
+ <template id="tmpl">
+ <div style="display: table" role="presentation">
+ Some text
+ <span style="display: table-cell">something with accessibles goes here</span>
+ More text
+ </div>
+ </template>
+
+ <div id="c2"><div id="c2_c" role="presentation"></div></div>
+
+ <script>
+ var gShadowRoot = document.getElementById("c2_c").attachShadow({mode: "open"});
+ var tmpl = document.getElementById("tmpl");
+ gShadowRoot.appendChild(document.importNode(tmpl.content, true));
+ </script>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml b/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml
new file mode 100644
index 0000000000..51a3c39047
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml
@@ -0,0 +1,57 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Canvas subdom mutation</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script>
+ <![CDATA[
+ function doTest() {
+ var the_displayNone = getNode("the_displaynone");
+ var the_table = getNode("the_table");
+ var the_row = getNode("the_row");
+ ok(isAccessible(the_table), "table should be accessible");
+ the_displayNone.appendChild(the_table);
+ ok(!isAccessible(the_table), "table in display none tree shouldn't be accessible");
+
+ setTimeout(function() {
+ document.body.removeChild(the_row);
+ // make sure no accessibles have stuck around.
+ ok(!isAccessible(the_row), "row shouldn't be accessible");
+ ok(!isAccessible(the_table), "table shouldn't be accessible");
+ ok(!isAccessible(the_displayNone), "display none things shouldn't be accessible");
+ SimpleTest.finish();
+ }, 0);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="test accessible removal when reframe root isn't accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=852150">
+ Mozilla Bug 852150
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="the_displaynone" style="display: none;"></div>
+ <table id="the_table"></table>
+ <tr id="the_row"></tr>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml b/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml
new file mode 100644
index 0000000000..5d9e813f3a
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+
+function boom() {
+ var newSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ document.getElementById("c").insertBefore(newSpan, document.getElementById("d"));
+ document.getElementById("a").style.visibility = "visible";
+ ok(true, "test didn't crash or assert");
+ SimpleTest.finish();
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+ <a target="_blank"
+ title="test reparenting accessible subtree when inaccessible element becomes accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=883708">
+ Mozilla Bug 883708
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+<div style="visibility: collapse;" id="a"><div style="float: right; visibility: visible;"><div id="c"><td id="d"></td></div></div></div></body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml b/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml
new file mode 100644
index 0000000000..7e3cf14fac
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+
+function boom() {
+ document.getElementById("k").removeAttribute("href");
+ ok(true, "changing iframe contents doesn't cause assertions");
+ SimpleTest.finish();
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<iframe src="data:text/html,1"><link id="k" href="data:text/html,2" /></iframe>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug895082.html b/accessible/tests/mochitest/treeupdate/test_bug895082.html
new file mode 100644
index 0000000000..8332c5206e
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug895082.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Replace body test</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+function doTest() {
+ var y = document.getElementById("y");
+ var oldBody = document.body;
+ var newBody = document.createElement("body");
+ document.documentElement.insertBefore(newBody, oldBody);
+ setTimeout(function() {
+ document.documentElement.removeChild(oldBody);
+ newBody.appendChild(y);
+ ok(true, "we didn't assert");
+ SimpleTest.finish();
+ }, 0);
+}
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=895082"
+ title="Bug 895082 - replacing body element asserts">
+ Bug 895082</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+<div><div id="y"></div></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_canvas.html b/accessible/tests/mochitest/treeupdate/test_canvas.html
new file mode 100644
index 0000000000..229bf4f2e3
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_canvas.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Canvas subdom mutation</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function addSubtree(aID) {
+ this.node = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.node),
+ ];
+
+ this.invoke = function addSubtree_invoke() {
+ // ensure we start with no subtree
+ testAccessibleTree("canvas", { CANVAS: [] });
+ getNode("dialog").style.display = "block";
+ };
+
+ this.finalCheck = function addSubtree_finalCheck() {
+ testAccessibleTree("dialog", { DIALOG: [] });
+ };
+
+ this.getID = function addSubtree_getID() {
+ return "show canvas subdom";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest() {
+ gQueue = new eventQueue();
+
+ // make the subdom come alive!
+ gQueue.push(new addSubtree("dialog"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Expose content in Canvas element"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">
+ Mozilla Bug 495912
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <canvas id="canvas">
+ <div id="dialog" role="dialog" style="display: none;">
+ </div>
+ </canvas>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml b/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml
new file mode 100644
index 0000000000..f81d77332d
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml
@@ -0,0 +1,315 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="menu tree and events">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ function openMenu(aID, aTree)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENUPOPUP_START, getNode(aID))
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ var button = getNode("button");
+ getNode(aID).openPopup(button, "after_start", 0, 0, true, false);
+ }
+
+ this.finalCheck = function openMenu_finalCheck(aEvent)
+ {
+ testAccessibleTree(aID, aTree);
+ }
+
+ this.getID = function openMenu_getID()
+ {
+ return "open menu " + prettyName(aID);
+ }
+ }
+
+ function selectNextMenuItem(aID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getNode(aID))
+ ];
+
+ this.invoke = function selectMenuItem_invoke()
+ {
+ synthesizeKey("KEY_ArrowDown");
+ }
+
+ this.getID = function selectMenuItem_getID()
+ {
+ return "select menuitem " + prettyName(aID);
+ }
+ }
+
+ function openSubMenu(aSubMenuID, aItemID, aMenuID, aTree)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getNode(aItemID)),
+ ];
+
+ this.invoke = function openSubMenu_invoke()
+ {
+ synthesizeKey("KEY_Enter");
+ }
+
+ this.finalCheck = function openSubMenu_finalCheck(aEvent)
+ {
+ testAccessibleTree(aMenuID, aTree);
+ }
+
+ this.getID = function openSubMenu_getID()
+ {
+ return "open submenu " + prettyName(aSubMenuID) + " focusing item " + prettyName(aItemID);
+ }
+ }
+
+ function closeSubMenu(aSubMenuID, aItemID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getNode(aItemID)),
+ ];
+
+ this.invoke = function closeSubMenu_invoke()
+ {
+ synthesizeKey("KEY_Escape");
+ }
+
+ this.getID = function closeSubMenu_getID()
+ {
+ return "close submenu " + prettyName(aSubMenuID) + " focusing item " + prettyName(aItemID);
+ }
+ }
+
+ function closeMenu(aID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENUPOPUP_END, getNode(aID))
+ ];
+
+ this.invoke = function closeMenu_invoke()
+ {
+ synthesizeKey("KEY_Escape");
+ }
+
+ this.getID = function closeMenu_getID()
+ {
+ return "close menu " + prettyName(aID);
+ }
+ }
+
+ //gA11yEventDumpToConsole = true;
+ //enableLogging("tree,verbose");
+
+ var gQueue = null;
+ var gContextTree = {};
+
+ // Linux and Windows menu trees discrepancy: bug 527646.
+
+ /**
+ * Return the context menu tree before submenus were open.
+ */
+ function getMenuTree1()
+ {
+ if (LINUX || SOLARIS) {
+ let tree = {
+ role: ROLE_MENUPOPUP,
+ children: [
+ {
+ name: "item0",
+ role: ROLE_MENUITEM,
+ children: []
+ },
+ {
+ name: "item1",
+ role: ROLE_MENUITEM,
+ children: []
+ },
+ {
+ name: "item2",
+ role: ROLE_PARENT_MENUITEM,
+ children: [ ]
+ }
+ ]
+ };
+ return tree;
+ }
+
+ // Windows
+ let tree = {
+ role: ROLE_MENUPOPUP,
+ children: [
+ {
+ name: "item0",
+ role: ROLE_MENUITEM,
+ children: []
+ },
+ {
+ name: "item1",
+ role: ROLE_MENUITEM,
+ children: []
+ },
+ {
+ name: "item2",
+ role: ROLE_PARENT_MENUITEM,
+ children: [
+ {
+ name: "item2",
+ role: ROLE_MENUPOPUP,
+ children: [ ]
+ }
+ ]
+ }
+ ]
+ };
+ return tree;
+ }
+
+ /**
+ * Return context menu tree when submenu was open.
+ */
+ function getMenuTree2()
+ {
+ var tree = getMenuTree1();
+ if (LINUX || SOLARIS) {
+ let submenuTree =
+ {
+ name: "item2.0",
+ role: ROLE_PARENT_MENUITEM,
+ children: [ ]
+ };
+ tree.children[2].children.push(submenuTree);
+ return tree;
+ }
+
+ // Windows
+ let submenuTree =
+ {
+ name: "item2.0",
+ role: ROLE_PARENT_MENUITEM,
+ children: [
+ {
+ name: "item2.0",
+ role: ROLE_MENUPOPUP,
+ children: [ ]
+ }
+ ]
+ };
+
+ tree.children[2].children[0].children.push(submenuTree);
+ return tree;
+ }
+
+ /**
+ * Return context menu tree when subsub menu was open.
+ */
+ function getMenuTree3()
+ {
+ var tree = getMenuTree2();
+ var subsubmenuTree =
+ {
+ name: "item2.0.0",
+ role: ROLE_MENUITEM,
+ children: []
+ };
+
+ if (LINUX || SOLARIS)
+ tree.children[2].children[0].children.push(subsubmenuTree);
+ else
+ tree.children[2].children[0].children[0].children[0].children.push(subsubmenuTree);
+
+ return tree;
+ }
+
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ // Check initial empty tree
+ testAccessibleTree("context", { MENUPOPUP: [] });
+
+ // Open context menu and check that menu item accesibles are created.
+ gQueue.push(new openMenu("context", getMenuTree1()));
+
+ // Select items and check focus event on them.
+ gQueue.push(new selectNextMenuItem("item0"));
+ gQueue.push(new selectNextMenuItem("item1"));
+ gQueue.push(new selectNextMenuItem("item2"));
+
+ // Open sub menu and check menu accessible tree and focus event.
+ gQueue.push(new openSubMenu("submenu2", "item2.0",
+ "context", getMenuTree2()));
+ gQueue.push(new openSubMenu("submenu2.0", "item2.0.0",
+ "context", getMenuTree3()));
+
+ // Close submenus and check that focus goes to parent.
+ gQueue.push(new closeSubMenu("submenu2.0", "item2.0"));
+ gQueue.push(new closeSubMenu("submenu2", "item2"));
+
+ gQueue.push(new closeMenu("context"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630194"
+ title="Update accessible tree when opening the menu popup">
+ Mozilla Bug 630194
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486"
+ title="Don't force accessible creation for popup children.">
+ Mozilla Bug 630486
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <menupopup id="context">
+ <menuitem id="item0" label="item0"/>
+ <menuitem id="item1" label="item1"/>
+ <menu id="item2" label="item2">
+ <menupopup id="submenu2">
+ <menu id="item2.0" label="item2.0">
+ <menupopup id="submenu2.0">
+ <menuitem id="item2.0.0" label="item2.0.0"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menupopup>
+
+ <button context="context" id="button">btn</button>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/treeupdate/test_cssoverflow.html b/accessible/tests/mochitest/treeupdate/test_cssoverflow.html
new file mode 100644
index 0000000000..6b60fce975
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_cssoverflow.html
@@ -0,0 +1,149 @@
+<html>
+
+<head>
+ <title>Testing HTML scrollable frames (css overflow style)</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+ // //////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Change scroll range to not empty size and inserts a child into container
+ * to trigger tree update of the container. Prior to bug 677154 not empty
+ * size resulted to accessible creation for scroll area, container tree
+ * update picked up that accessible unattaching scroll area accessible
+ * subtree.
+ */
+ function changeScrollRange(aContainerID, aScrollAreaID) {
+ this.containerNode = getNode(aContainerID);
+ this.container = getAccessible(this.containerNode);
+ this.scrollAreaNode = getNode(aScrollAreaID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.container),
+ ];
+
+ this.invoke = function changeScrollRange_invoke() {
+ this.scrollAreaNode.style.width = "20px";
+ this.containerNode.appendChild(document.createElement("input"));
+ };
+
+ this.finalCheck = function changeScrollRange_finalCheck() {
+ var accTree =
+ { SECTION: [ // container
+ { SECTION: [ // scroll area
+ { ENTRY: [] }, // child content
+ ] },
+ { ENTRY: [] }, // inserted input
+ ] };
+ testAccessibleTree(this.container, accTree);
+ };
+
+ this.getID = function changeScrollRange_getID() {
+ return "change scroll range for " + prettyName(aScrollAreaID);
+ };
+ }
+
+ /**
+ * Change scrollbar styles from visible to auto to make the scroll area focusable.
+ * That causes us to create an accessible for it.
+ * Make sure the tree stays intact.
+ * The scroll area has no ID on purpose to make it inaccessible initially.
+ */
+ function makeFocusableByScrollbarStyles(aContainerID) {
+ this.container = getAccessible(aContainerID);
+ this.scrollAreaNode = getNode(aContainerID).firstChild;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getAccessible, this.scrollAreaNode),
+ new invokerChecker(EVENT_REORDER, this.container),
+ ];
+
+ this.invoke = function makeFocusableByScrollbarStyles_invoke() {
+ var accTree =
+ { SECTION: [ // container
+ { PARAGRAPH: [ // paragraph
+ { TEXT_LEAF: [] },
+ ] },
+ ] };
+ testAccessibleTree(this.container, accTree);
+
+ this.scrollAreaNode.style.overflow = "auto";
+ };
+
+ this.finalCheck = function makeFocusableByScrollbarStyles_finalCheck() {
+ var accTree =
+ { SECTION: [ // container
+ { role: ROLE_SECTION, // focusable scroll area
+ states: STATE_FOCUSABLE,
+ children: [
+ { PARAGRAPH: [ // paragraph
+ { TEXT_LEAF: [] }, // text leaf
+ ] },
+ ],
+ }, // focusable scroll area
+ ] };
+ testAccessibleTree(this.container, accTree);
+ };
+
+ this.getID = function makeFocusableByScrollbarStyles_getID() {
+ return "make div focusable through scrollbar styles "
+ + prettyName(aContainerID);
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+ // //////////////////////////////////////////////////////////////////////////
+
+ var gQueue = null;
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new changeScrollRange("container", "scrollarea"));
+ gQueue.push(new makeFocusableByScrollbarStyles("container2"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=677154"
+ title="Detached document accessibility tree">
+ Mozilla Bug 677154</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <div id="container"><div id="scrollarea" style="overflow:auto;"><input></div></div>
+ <div id="container2"><div style="height: 1px;"><p>foo</p></div></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_deck.xhtml b/accessible/tests/mochitest/treeupdate/test_deck.xhtml
new file mode 100644
index 0000000000..979996a66c
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_deck.xhtml
@@ -0,0 +1,154 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Tree update on XUL deck panel switching">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function switchDeckPanel(aContainerID, aDeckID)
+ {
+ this.panelIndex = 0;
+
+ this.container = getAccessible(aContainerID);
+ this.deckNode = getNode(aDeckID);
+ this.prevPanel = getAccessible(this.deckNode.selectedPanel);
+ this.panelNode = this.deckNode.childNodes[this.panelIndex];
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.prevPanel),
+ new invokerChecker(EVENT_SHOW, this.panelNode),
+ new invokerChecker(EVENT_REORDER, this.container)
+ ];
+
+ this.invoke = function switchDeckPanel_invoke()
+ {
+ var tree =
+ { GROUPING: [ // role="group"
+ { GROUPING: [ // groupbox, a selected panel #2
+ { PUSHBUTTON: [ ] } // button
+ ] }
+ ] };
+ testAccessibleTree(this.container, tree);
+
+ this.deckNode.selectedIndex = this.panelIndex;
+ }
+
+ this.finalCheck = function switchDeckPanel_finalCheck()
+ {
+ var tree =
+ { GROUPING: [ // role="group"
+ { LABEL: [ // description, a selected panel #1
+ { TEXT_LEAF: [] } // text leaf, a description value
+ ] }
+ ] };
+ testAccessibleTree(this.container, tree);
+ }
+
+ this.getID = function switchDeckPanel_getID()
+ {
+ return "switch deck panel";
+ }
+ }
+
+ function showDeckPanel(aContainerID, aPanelID)
+ {
+ this.container = getAccessible(aContainerID);
+ this.deckNode = getNode(aPanelID);
+ var tree =
+ { GROUPING: [ // role="group"
+ { GROUPING: [ // grouping of panel 2
+ { PUSHBUTTON: [] } // push button in panel 2
+ ] }
+ ] };
+
+
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_REORDER, this.container)
+ ];
+
+ this.invoke = function showDeckPanel_invoke()
+ {
+ // This stops the refreh driver from doing its regular ticks, and leaves
+ // us in control. 100 is an arbitrary positive number to advance the clock
+ // it is not checked or used anywhere.
+ window.windowUtils.advanceTimeAndRefresh(100);
+
+ testAccessibleTree(this.container, tree);
+ this.deckNode.style.display = "-moz-box";
+
+ // This flushes our DOM mutations and forces any pending mutation events.
+ window.windowUtils.advanceTimeAndRefresh(100);
+ }
+
+ this.finalCheck = function showDeckPanel_finalCheck()
+ {
+ testAccessibleTree(this.container, tree);
+
+ // Return to regular refresh driver ticks.
+ window.windowUtils.restoreNormalRefresh();
+ }
+
+ this.getID = function showDeckPanel_getID()
+ {
+ return "show deck panel";
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new showDeckPanel("container", "hidden"));
+ gQueue.push(new switchDeckPanel("container", "deck"));
+ gQueue.invoke(); // will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=814836"
+ title=" xul:deck element messes up screen reader">
+ Mozilla Bug 814836
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1" id="container" role="group">
+
+ <deck id="deck" selectedIndex="1">
+ <description>This is the first page</description>
+ <groupbox>
+ <button label="This is the second page"/>
+ </groupbox>
+ <hbox id="hidden" style="display: none;"><label>This is the third page</label></hbox>
+ </deck>
+
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/treeupdate/test_delayed_removal.html b/accessible/tests/mochitest/treeupdate/test_delayed_removal.html
new file mode 100644
index 0000000000..3f421f0c5b
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_delayed_removal.html
@@ -0,0 +1,500 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test accessible delayed removal</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ .gentext:before {
+ content: "START"
+ }
+ .gentext:after {
+ content: "END"
+ }
+ </style>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+
+ async function hideDivFromInsideSpan() {
+ let msg = "hideDivFromInsideSpan";
+ info(msg);
+ let events = waitForOrderedEvents([
+ [EVENT_HIDE, "div1"], [EVENT_TEXT_REMOVED, "span1"],
+ [EVENT_REORDER, "span1"]
+ ], msg);
+ document.body.offsetTop; // Flush layout.
+ getNode("div1").style.display = "none";
+ await events;
+
+ testAccessibleTree("c1", { SECTION: [ { REGION: [] }, ] });
+ }
+
+ async function showDivFromInsideSpan() {
+ let msg = "showDivFromInsideSpan";
+ info(msg);
+ let events = waitForOrderedEvents(
+ [[EVENT_SHOW, "div2"], [EVENT_REORDER, "span2"]], msg);
+ document.body.offsetTop; // Flush layout.
+ getNode("div2").style.display = "block";
+ await events;
+
+ testAccessibleTree("c2",
+ { SECTION: [ { REGION: [{ SECTION: [ { TEXT_LEAF: [] } ] }] }, ] });
+ }
+
+ async function removeDivFromInsideSpan() {
+ let msg = "removeDivFromInsideSpan";
+ info(msg);
+ let events = waitForOrderedEvents([
+ [EVENT_HIDE, getNode("div3")], [EVENT_TEXT_REMOVED, "span3"],
+ [EVENT_REORDER, "span3"]
+ ], msg);
+ document.body.offsetTop; // Flush layout.
+ getNode("div3").remove();
+ await events;
+
+ testAccessibleTree("c3", { SECTION: [ { REGION: [] }, ] });
+ }
+
+ // Test to see that generated content is inserted
+ async function addCSSGeneratedContent() {
+ let msg = "addCSSGeneratedContent";
+ let c4_child = getAccessible("c4_child");
+ info(msg);
+ let events = waitForOrderedEvents([
+ [EVENT_SHOW, evt => evt.accessible == c4_child.firstChild],
+ [EVENT_SHOW, evt => evt.accessible == c4_child.lastChild],
+ [EVENT_REORDER, c4_child]], msg);
+ document.body.offsetTop; // Flush layout.
+ getNode("c4_child").classList.add('gentext');
+ await events;
+
+ testAccessibleTree("c4", { SECTION: [ // container
+ { SECTION: [ // inserted node
+ { STATICTEXT: [] }, // :before
+ { TEXT_LEAF: [] }, // primary text
+ { STATICTEXT: [] }, // :after
+ ] },
+ ] });
+ }
+
+ // Test to see that generated content gets removed
+ async function removeCSSGeneratedContent() {
+ let msg = "removeCSSGeneratedContent";
+ let c5_child = getAccessible("c5_child");
+ info(msg);
+ let events = waitForEvents([
+ [EVENT_HIDE, c5_child.firstChild],
+ [EVENT_HIDE, c5_child.lastChild],
+ [EVENT_REORDER, c5_child]], msg);
+ document.body.offsetTop; // Flush layout.
+ getNode("c5_child").classList.remove('gentext');
+ await events;
+
+ testAccessibleTree("c5",{ SECTION: [ // container
+ { SECTION: [ // inserted node
+ { TEXT_LEAF: [] }, // primary text
+ ] },
+ ] });
+ }
+
+ // Test to see that a non-accessible intermediate container gets its accessible
+ // descendants removed and inserted correctly.
+ async function intermediateNonAccessibleContainers() {
+ let msg = "intermediateNonAccessibleContainers";
+ info(msg);
+
+ testAccessibleTree("c6",{ SECTION: [
+ { SECTION: [
+ { role: ROLE_PUSHBUTTON, name: "Hello" },
+ ] },
+ ] });
+
+ let events = waitForOrderedEvents(
+ [[EVENT_HIDE, "b1"], [EVENT_SHOW, "b2"], [EVENT_REORDER, "scrollarea"]], msg);
+ document.body.offsetTop; // Flush layout.
+ getNode("scrollarea").style.overflow = "auto";
+ document.querySelector("#scrollarea > div > div:first-child").style.display = "none";
+ document.querySelector("#scrollarea > div > div:last-child").style.display = "block";
+ await events;
+
+ testAccessibleTree("c6",{ SECTION: [
+ { SECTION: [
+ { role: ROLE_PUSHBUTTON, name: "Goodbye" },
+ ] },
+ ] });
+ }
+
+ // Test to see that the button gets reparented into the new accessible container.
+ async function intermediateNonAccessibleContainerBecomesAccessible() {
+ let msg = "intermediateNonAccessibleContainerBecomesAccessible";
+ info(msg);
+
+ testAccessibleTree("c7",{ SECTION: [
+ { role: ROLE_PUSHBUTTON, name: "Hello" },
+ { TEXT_LEAF: [] }
+ ] });
+
+ let events = waitForOrderedEvents(
+ [[EVENT_HIDE, "b3"],
+ // b3 show event coalesced into its new container
+ [EVENT_SHOW, evt => evt.DOMNode.classList.contains('intermediate')],
+ [EVENT_REORDER, "c7"]], msg);
+ document.body.offsetTop; // Flush layout.
+ document.querySelector("#c7 > div").style.display = "block";
+ await events;
+
+ testAccessibleTree("c7",{ SECTION: [
+ { SECTION: [ { role: ROLE_PUSHBUTTON, name: "Hello" } ] }
+ ] });
+ }
+
+ // Test to ensure that relocated accessibles are removed when a DOM
+ // ancestor is hidden.
+ async function removeRelocatedWhenDomAncestorHidden() {
+ info("removeRelocatedWhenDomAncestorHidden");
+
+ testAccessibleTree("c8",{ SECTION: [
+ { EDITCOMBOBOX: [ // c8_owner
+ { COMBOBOX_LIST: [] }, // c8_owned
+ ]},
+ { SECTION: [] }, // c8_owned_container
+ ] });
+
+ let events = waitForOrderedEvents([
+ [EVENT_HIDE, "c8_owned_container"],
+ [EVENT_HIDE, "c8_owned"],
+ [EVENT_REORDER, "c8"],
+ ], "removeRelocatedWhenDomAncestorHidden");
+ document.body.offsetTop; // Flush layout.
+ getNode("c8_owned_container").hidden = true;
+ await events;
+
+ testAccessibleTree("c8",{ SECTION: [
+ { EDITCOMBOBOX: [] }, // c8_owner
+ ] });
+ }
+
+ // Bug 1572829
+ async function removeShadowRootHost() {
+ info("removeShadowRootHost");
+ document.body.offsetTop; // Flush layout.
+
+ let event = waitForEvent(EVENT_REORDER, "c9", "removeShadowRootHost");
+ getNode("c9").firstElementChild.attachShadow({mode: "open"});
+ getNode("c9").firstElementChild.replaceWith("");
+
+ await event;
+ }
+
+ function listItemReframe() {
+ testAccessibleTree("li",{ LISTITEM: [
+ { LISTITEM_MARKER: [] },
+ { TEXT_LEAF: [] },
+ ] });
+
+ getNode("li").style.listStylePosition = "inside";
+ document.body.offsetTop; // Flush layout.
+ window.windowUtils.advanceTimeAndRefresh(100);
+
+ testAccessibleTree("li",{ LISTITEM: [
+ { LISTITEM_MARKER: [] },
+ { TEXT_LEAF: [] },
+ ] });
+
+ window.windowUtils.restoreNormalRefresh();
+ }
+
+ // Check to see that a reframed body gets its children pruned correctly.
+ async function bodyReframe(argument) {
+ // Load sub-document in iframe.
+ let event = waitForEvent(EVENT_REORDER, "iframe", "bodyReframe");
+ getNode("iframe").src =
+ `data:text/html,<div>Hello</div><div style="display: none">World</div>`;
+ await event;
+
+ // Initial tree should have one section leaf.
+ testAccessibleTree("c10",{ SECTION: [
+ { INTERNAL_FRAME: [
+ { DOCUMENT: [
+ { SECTION: [
+ { role: ROLE_TEXT_LEAF, name: "Hello" }
+ ] }
+ ]}
+ ] }
+ ] });
+
+
+ let iframeDoc = getNode("iframe").contentWindow.document;
+
+ // Trigger coalesced reframing. Both the body node and its children
+ // will need reframing.
+ event = waitForEvent(EVENT_REORDER, iframeDoc, "bodyReframe");
+ iframeDoc.body.style.display = "inline-block";
+ iframeDoc.querySelector("div:first-child").style.display = "none";
+ iframeDoc.querySelector("div:last-child").style.display = "block";
+
+ await event;
+
+ // Only the second section should be showing
+ testAccessibleTree("c10",{ SECTION: [
+ { INTERNAL_FRAME: [
+ { DOCUMENT: [
+ { SECTION: [
+ { role: ROLE_TEXT_LEAF, name: "World" }
+ ] }
+ ]}
+ ] }
+ ] });
+ }
+
+ // Ensure that embed elements recreate their Accessible if they started
+ // without an src and then an src is set later.
+ async function embedBecomesOuterDoc() {
+ let msg = "embedBecomesOuterDoc";
+ info(msg);
+
+ testAccessibleTree("c12", { SECTION: [
+ { TEXT: [] }
+ ] });
+
+ let events = waitForOrderedEvents([
+ [EVENT_HIDE, "embed"],
+ [EVENT_SHOW, "embed"],
+ [EVENT_REORDER, "c12"],
+ ], msg);
+ getNode("embed").src = "data:text/html,";
+ await events;
+
+ testAccessibleTree("c12", { SECTION: [
+ { INTERNAL_FRAME: [
+ { DOCUMENT: [] }
+ ] }
+ ] });
+ }
+
+ // Test that we get a text removed event when removing generated content from a button
+ async function testCSSGeneratedContentRemovedFromButton() {
+ let msg = "testCSSGeneratedContentRemovedFromButton";
+ info(msg);
+
+ testAccessibleTree("c13", { SECTION: [
+ { role: ROLE_PUSHBUTTON, name: "beforego",
+ children: [{ STATICTEXT: [] }, { TEXT_LEAF: [] }] }
+ ] });
+
+ let events = waitForOrderedEvents([
+ [EVENT_HIDE, evt => evt.accessible.name == "before"],
+ [EVENT_TEXT_REMOVED, evt => evt.accessible.role == ROLE_PUSHBUTTON],
+ [EVENT_SHOW, evt => evt.DOMNode.tagName == "HR"],
+ [EVENT_REORDER, "c13"],
+ ], msg);
+ getNode("b13").click();
+ await events;
+
+ testAccessibleTree("c13", { SECTION: [
+ { role: ROLE_PUSHBUTTON, name: "go",
+ children: [{ TEXT_LEAF: [] }] },
+ { SEPARATOR: [] }
+ ] });
+ }
+
+ // Slack seems to often restyle containers and change children
+ // simultaneously, this results in an insertion queue filled with
+ // redundant insertions and unparented nodes.
+ // This test duplicates some of this.
+ async function testSlack() {
+ let msg = "testSlack";
+ info(msg);
+
+ window.windowUtils.advanceTimeAndRefresh(100);
+ let event = waitForEvent(EVENT_REORDER, "c14", "testSlack");
+
+ let keyContainer = document.querySelector("#c14 .intermediate");
+ keyContainer.style.display = "inline-block";
+ document.body.offsetTop; // Flush layout.
+
+ let one = document.querySelector("#c14 [aria-label='one']");
+ let three = document.querySelector("#c14 [aria-label='three']");
+ one.remove();
+ three.remove();
+ // insert one first
+ keyContainer.firstChild.before(one.cloneNode());
+ // insert three last
+ keyContainer.lastChild.after(three.cloneNode());
+
+ keyContainer.style.display = "flex";
+ document.body.offsetTop; // Flush layout.
+
+ window.windowUtils.restoreNormalRefresh();
+
+ await event;
+
+ is(getAccessible("c14").name, "one two three", "subtree has correct order");
+ }
+
+ // Ensure that a node is removed when visibility: hidden is set but the
+ // layout frame is reconstructed; e.g. because of position: fixed. Also
+ // ensure that visible children aren't clobbered.
+ async function visibilityHiddenWithReframe() {
+ let msg = "visibilityHiddenWithReframe";
+ info(msg);
+
+ testAccessibleTree("c15", { SECTION: [ // c15
+ { SECTION: [ // c15_inner
+ { TEXT_LEAF: [] }, // Text
+ { PARAGRAPH: [
+ { TEXT_LEAF: [] } // Para
+ ] },
+ { HEADING: [ // c15_visible
+ { TEXT_LEAF: [] } // Visible
+ ] }, // c15_visible
+ ] } // c15_inner
+ ] });
+
+ let events = waitForOrderedEvents([
+ [EVENT_HIDE, "c15_inner"],
+ [EVENT_SHOW, "c15_visible"],
+ [EVENT_REORDER, "c15"],
+ ], msg);
+ getNode("c15_inner").style = "visibility: hidden; position: fixed;";
+ await events;
+
+ testAccessibleTree("c15", { SECTION: [ // c15
+ { HEADING: [ // c15_visible
+ { TEXT_LEAF: [] } // Visible
+ ] }, // c15_visible
+ ] });
+ }
+
+ async function doTest() {
+ await hideDivFromInsideSpan();
+
+ await showDivFromInsideSpan();
+
+ await removeDivFromInsideSpan();
+
+ await addCSSGeneratedContent();
+
+ await removeCSSGeneratedContent();
+
+ await intermediateNonAccessibleContainers();
+
+ await intermediateNonAccessibleContainerBecomesAccessible();
+
+ await removeRelocatedWhenDomAncestorHidden();
+
+ await removeShadowRootHost();
+
+ listItemReframe();
+
+ await bodyReframe();
+
+ await embedBecomesOuterDoc();
+
+ await testCSSGeneratedContentRemovedFromButton();
+
+ await testSlack();
+
+ await visibilityHiddenWithReframe();
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="c1">
+ <span role="region" id="span1" aria-label="region"><div id="div1">hello</div></span>
+ </div>
+
+ <div id="c2">
+ <span role="region" id="span2" aria-label="region"><div id="div2" style="display: none">hello</div></span>
+ </div>
+
+ <div id="c3">
+ <span role="region" id="span3" aria-label="region"><div id="div3">hello</div></span>
+ </div>
+
+ <div id="c4"><div id="c4_child">text</div></div>
+
+ <div id="c5"><div id="c5_child" class="gentext">text</div></div>
+
+ <div id="c6">
+ <div id="scrollarea" style="overflow:hidden;">
+ <div><div role="none"><button id="b1">Hello</button></div><div role="none" style="display: none"><button id="b2">Goodbye</button></div></div>
+ </div>
+ </div>
+
+ <div id="c7">
+ <div style="display: inline;" class="intermediate">
+ <button id="b3">Hello</button>
+ </div>
+ </div>
+
+ <div id="c8">
+ <div id="c8_owner" role="combobox" aria-owns="c8_owned"></div>
+ <div id="c8_owned_container">
+ <div id="c8_owned" role="listbox"></div>
+ </div>
+ </div>
+
+ <div id="c9">
+ <div><dir>a</dir></div>
+ </div>
+
+ <div id="c11">
+ <ul>
+ <li id="li">Test</li>
+ </ul>
+ </div>
+
+ <div id="c12"><embed id="embed"></embed></div>
+
+ <div id="c10">
+ <iframe id="iframe"></iframe>
+ </div>
+
+ <div id="c13">
+ <style>
+ .before::before { content: 'before' }
+ </style>
+ <button id="b13" class="before" onclick="this.className = ''; this.insertAdjacentElement('afterend', document.createElement('hr'))">go</button>
+ </div>
+
+ <div role="heading" id="c14" data-qa="virtual-list-item">
+ <div class="intermediate">
+ <div role="img" aria-label="one"></div> two <div role="img"
+ aria-label="three"></div>
+ </div>
+ </div>
+
+ <div id="c15"><div id="c15_inner">
+ Text
+ <p>Para</p>
+ <h1 id="c15_visible" style="visibility: visible;">Visible</h1>
+ </div></div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_doc.html b/accessible/tests/mochitest/treeupdate/test_doc.html
new file mode 100644
index 0000000000..6bb2863df4
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_doc.html
@@ -0,0 +1,415 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test document root content mutations</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Helpers
+
+ function getDocNode(aID) {
+ return getNode(aID).contentDocument;
+ }
+ function getDocChildNode(aID) {
+ return getDocNode(aID).body.firstChild;
+ }
+
+ function rootContentReplaced(aID, aTextName, aRootContentRole) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getDocChildNode, aID),
+ new invokerChecker(EVENT_REORDER, getDocNode, aID),
+ ];
+
+ this.finalCheck = function rootContentReplaced_finalCheck() {
+ var tree = {
+ role: aRootContentRole || ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: aTextName,
+ },
+ ],
+ };
+ testAccessibleTree(getDocNode(aID), tree);
+ };
+ }
+
+ function rootContentRemoved(aID) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, null),
+ new invokerChecker(EVENT_REORDER, getDocNode, aID),
+ ];
+
+ this.preinvoke = function rootContentRemoved_preinvoke() {
+ // Set up target for hide event before we invoke.
+ var text = getAccessible(getAccessible(getDocNode(aID)).firstChild);
+ this.eventSeq[0].target = text;
+ };
+
+ this.finalCheck = function rootContentRemoved_finalCheck() {
+ var tree = {
+ role: ROLE_DOCUMENT,
+ children: [ ],
+ };
+ testAccessibleTree(getDocNode(aID), tree);
+ };
+ }
+
+ function rootContentInserted(aID, aTextName) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getDocChildNode, aID),
+ new invokerChecker(EVENT_REORDER, getDocNode, aID),
+ ];
+
+ this.finalCheck = function rootContentInserted_finalCheck() {
+ var tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: aTextName,
+ },
+ ],
+ };
+ testAccessibleTree(getDocNode(aID), tree);
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function writeIFrameDoc(aID) {
+ this.__proto__ = new rootContentReplaced(aID, "hello");
+
+ this.invoke = function writeIFrameDoc_invoke() {
+ var docNode = getDocNode(aID);
+
+ // We can't use open/write/close outside of iframe document because of
+ // security error.
+ var script = docNode.createElement("script");
+ script.textContent = "document.open(); document.write('hello'); document.close();";
+ docNode.body.appendChild(script);
+ };
+
+ this.getID = function writeIFrameDoc_getID() {
+ return "write document";
+ };
+ }
+
+ /**
+ * Replace HTML element.
+ */
+ function replaceIFrameHTMLElm(aID) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getDocChildNode, aID),
+ new invokerChecker(EVENT_REORDER, getDocNode, aID),
+ ];
+
+ this.invoke = function replaceIFrameHTMLElm_invoke() {
+ var docNode = getDocNode(aID);
+ var newHTMLNode = docNode.createElement("html");
+ newHTMLNode.innerHTML = `<body><p>New Wave</p></body`;
+ docNode.replaceChild(newHTMLNode, docNode.documentElement);
+ };
+
+ this.finalCheck = function replaceIFrameHTMLElm_finalCheck() {
+ var tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_PARAGRAPH,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "New Wave",
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(getDocNode(aID), tree);
+ };
+
+ this.getID = function replaceIFrameHTMLElm_getID() {
+ return "replace HTML element";
+ };
+ }
+
+ /**
+ * Replace HTML body on new body having ARIA role.
+ */
+ function replaceIFrameBody(aID) {
+ this.__proto__ = new rootContentReplaced(aID, "New Hello");
+
+ this.invoke = function replaceIFrameBody_invoke() {
+ var docNode = getDocNode(aID);
+ var newBodyNode = docNode.createElement("body");
+ var newTextNode = docNode.createTextNode("New Hello");
+ newBodyNode.appendChild(newTextNode);
+ docNode.documentElement.replaceChild(newBodyNode, docNode.body);
+ };
+
+ this.getID = function replaceIFrameBody_getID() {
+ return "replace body";
+ };
+ }
+
+ /**
+ * Replace HTML body on new body having ARIA role.
+ */
+ function replaceIFrameBodyOnARIARoleBody(aID) {
+ this.__proto__ = new rootContentReplaced(aID, "New Hello",
+ ROLE_APPLICATION);
+
+ this.invoke = function replaceIFrameBodyOnARIARoleBody_invoke() {
+ var docNode = getDocNode(aID);
+ var newBodyNode = docNode.createElement("body");
+ var newTextNode = docNode.createTextNode("New Hello");
+ newBodyNode.appendChild(newTextNode);
+ newBodyNode.setAttribute("role", "application");
+ docNode.documentElement.replaceChild(newBodyNode, docNode.body);
+ };
+
+ this.getID = function replaceIFrameBodyOnARIARoleBody_getID() {
+ return "replace body on body having ARIA role";
+ };
+ }
+
+ /**
+ * Open/close document pair.
+ */
+ function openIFrameDoc(aID) {
+ this.__proto__ = new rootContentRemoved(aID);
+
+ this.invoke = function openIFrameDoc_invoke() {
+ this.preinvoke();
+
+ // Open document.
+ var docNode = getDocNode(aID);
+ var script = docNode.createElement("script");
+ script.textContent = "function closeMe() { document.write('Works?'); document.close(); } window.closeMe = closeMe; document.open();";
+ docNode.body.appendChild(script);
+ };
+
+ this.getID = function openIFrameDoc_getID() {
+ return "open document";
+ };
+ }
+
+ function closeIFrameDoc(aID) {
+ this.__proto__ = new rootContentInserted(aID, "Works?");
+
+ this.invoke = function closeIFrameDoc_invoke() {
+ // Write and close document.
+ getDocNode(aID).write("Works?"); getDocNode(aID).close();
+ };
+
+ this.getID = function closeIFrameDoc_getID() {
+ return "close document";
+ };
+ }
+
+ /**
+ * Remove/insert HTML element pair.
+ */
+ function removeHTMLFromIFrameDoc(aID) {
+ this.__proto__ = new rootContentRemoved(aID);
+
+ this.invoke = function removeHTMLFromIFrameDoc_invoke() {
+ this.preinvoke();
+
+ // Remove HTML element.
+ var docNode = getDocNode(aID);
+ docNode.firstChild.remove();
+ };
+
+ this.getID = function removeHTMLFromIFrameDoc_getID() {
+ return "remove HTML element";
+ };
+ }
+
+ function insertHTMLToIFrameDoc(aID) {
+ this.__proto__ = new rootContentInserted(aID, "Haha");
+
+ this.invoke = function insertHTMLToIFrameDoc_invoke() {
+ // Insert HTML element.
+ var docNode = getDocNode(aID);
+ var html = docNode.createElement("html");
+ var body = docNode.createElement("body");
+ var text = docNode.createTextNode("Haha");
+ body.appendChild(text);
+ html.appendChild(body);
+ docNode.appendChild(html);
+ };
+
+ this.getID = function insertHTMLToIFrameDoc_getID() {
+ return "insert HTML element document";
+ };
+ }
+
+ /**
+ * Remove/insert HTML body pair.
+ */
+ function removeBodyFromIFrameDoc(aID) {
+ this.__proto__ = new rootContentRemoved(aID);
+
+ this.invoke = function removeBodyFromIFrameDoc_invoke() {
+ this.preinvoke();
+
+ // Remove body element.
+ var docNode = getDocNode(aID);
+ docNode.documentElement.removeChild(docNode.body);
+ };
+
+ this.getID = function removeBodyFromIFrameDoc_getID() {
+ return "remove body element";
+ };
+ }
+
+ function insertElmUnderDocElmWhileBodyMissed(aID) {
+ this.docNode = null;
+ this.inputNode = null;
+
+ function getInputNode() { return this.inputNode; }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getInputNode.bind(this)),
+ new invokerChecker(EVENT_REORDER, getDocNode, aID),
+ ];
+
+ this.invoke = function invoke() {
+ this.docNode = getDocNode(aID);
+ this.inputNode = this.docNode.createElement("input");
+ this.docNode.documentElement.appendChild(this.inputNode);
+ };
+
+ this.finalCheck = function finalCheck() {
+ var tree =
+ { DOCUMENT: [
+ { ENTRY: [ ] },
+ ] };
+ testAccessibleTree(this.docNode, tree);
+
+ // Remove aftermath of this test before next test starts.
+ this.docNode.documentElement.removeChild(this.inputNode);
+ };
+
+ this.getID = function getID() {
+ return "Insert element under document element while body is missed.";
+ };
+ }
+
+ function insertBodyToIFrameDoc(aID) {
+ this.__proto__ = new rootContentInserted(aID, "Yo ho ho i butylka roma!");
+
+ this.invoke = function insertBodyToIFrameDoc_invoke() {
+ // Insert body element.
+ var docNode = getDocNode(aID);
+ var body = docNode.createElement("body");
+ var text = docNode.createTextNode("Yo ho ho i butylka roma!");
+ body.appendChild(text);
+ docNode.documentElement.appendChild(body);
+ };
+
+ this.getID = function insertBodyToIFrameDoc_getID() {
+ return "insert body element";
+ };
+ }
+
+ function changeSrc(aID) {
+ this.containerNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.containerNode),
+ ];
+
+ this.invoke = function changeSrc_invoke() {
+ this.containerNode.src = "data:text/html,<html><input></html>";
+ };
+
+ this.finalCheck = function changeSrc_finalCheck() {
+ var tree =
+ { INTERNAL_FRAME: [
+ { DOCUMENT: [
+ { ENTRY: [ ] },
+ ] },
+ ] };
+ testAccessibleTree(this.containerNode, tree);
+ };
+
+ this.getID = function changeSrc_getID() {
+ return "change src on iframe";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpToConsole = true;
+ // enableLogging('tree,verbose');
+
+ var gQueue = null;
+
+ function doTest() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new writeIFrameDoc("iframe"));
+ gQueue.push(new replaceIFrameHTMLElm("iframe"));
+ gQueue.push(new replaceIFrameBody("iframe"));
+ gQueue.push(new openIFrameDoc("iframe"));
+ gQueue.push(new closeIFrameDoc("iframe"));
+ gQueue.push(new removeHTMLFromIFrameDoc("iframe"));
+ gQueue.push(new insertHTMLToIFrameDoc("iframe"));
+ gQueue.push(new removeBodyFromIFrameDoc("iframe"));
+ gQueue.push(new insertElmUnderDocElmWhileBodyMissed("iframe"));
+ gQueue.push(new insertBodyToIFrameDoc("iframe"));
+ gQueue.push(new changeSrc("iframe"));
+ gQueue.push(new replaceIFrameBodyOnARIARoleBody("iframe"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Update accessible tree when root element is changed"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=606082">Mozilla Bug 606082</a>
+ <a target="_blank"
+ title="Elements inserted outside the body aren't accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887">Mozilla Bug 608887</a>
+ <a target="_blank"
+ title="Reorder event for document must be fired after document initial tree creation"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=669263">Mozilla Bug 669263</a>
+ <a target="_blank"
+ title="Changing the HTML body doesn't pick up ARIA role"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=818407">Mozilla Bug 818407</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <iframe id="iframe"></iframe>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_gencontent.html b/accessible/tests/mochitest/treeupdate/test_gencontent.html
new file mode 100644
index 0000000000..9a0c107133
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_gencontent.html
@@ -0,0 +1,187 @@
+<html>
+
+<head>
+ <title>Elements with CSS generated content</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ .gentext:before {
+ content: "START"
+ }
+ .gentext:after {
+ content: "END"
+ }
+ </style>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+ // //////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Insert new node with CSS generated content style applied to container.
+ */
+ function insertNodeHavingGenContent(aContainerID) {
+ this.containerNode = getNode(aContainerID);
+ this.container = getAccessible(this.containerNode);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getFirstChild, this.container),
+ new invokerChecker(EVENT_REORDER, this.container),
+ ];
+
+ this.invoke = function insertNodeHavingGenContent_invoke() {
+ var node = document.createElement("div");
+ node.textContent = "text";
+ node.setAttribute("class", "gentext");
+ this.containerNode.appendChild(node);
+ };
+
+ this.finalCheck = function insertNodeHavingGenContent_finalCheck() {
+ var accTree =
+ { SECTION: [ // container
+ { SECTION: [ // inserted node
+ { STATICTEXT: [] }, // :before
+ { TEXT_LEAF: [] }, // primary text
+ { STATICTEXT: [] }, // :after
+ ] },
+ ] };
+ testAccessibleTree(this.container, accTree);
+ };
+
+ this.getID = function insertNodeHavingGenContent_getID() {
+ return "insert node having generated content to " + prettyName(aContainerID);
+ };
+ }
+
+ /**
+ * Add CSS generated content to the given node contained by container node.
+ */
+ function addGenContent(aContainerID, aNodeID) {
+ this.container = getAccessible(aContainerID);
+ this.nodeAcc = getAccessible(aNodeID);
+ this.node = getNode(aNodeID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getFirstChild, this.nodeAcc),
+ new invokerChecker(EVENT_SHOW, getLastChild, this.nodeAcc),
+ new invokerChecker(EVENT_REORDER, this.nodeAcc),
+ ];
+
+ this.invoke = function addGenContent_invoke() {
+ this.node.classList.add("gentext");
+ };
+
+ this.finalCheck = function insertNodeHavingGenContent_finalCheck() {
+ var accTree =
+ { SECTION: [ // container
+ { SECTION: [ // inserted node
+ { STATICTEXT: [] }, // :before
+ { TEXT_LEAF: [] }, // primary text
+ { STATICTEXT: [] }, // :after
+ ] },
+ ] };
+ testAccessibleTree(this.container, accTree);
+ };
+
+ this.getID = function addGenContent_getID() {
+ return "add generated content to" + prettyName(aNodeID);
+ };
+ }
+
+ /**
+ * Remove CSS generated content from the given node contained by container node.
+ */
+ function removeGenContent(aContainerID, aNodeID) {
+ this.container = getAccessible(aContainerID);
+ this.nodeAcc = getAccessible(aNodeID);
+ this.node = getNode(aNodeID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.nodeAcc.lastChild),
+ new invokerChecker(EVENT_HIDE, this.nodeAcc.firstChild),
+ new invokerChecker(EVENT_REORDER, this.nodeAcc),
+ ];
+
+ this.invoke = function removeGenContent_invoke() {
+ this.node.classList.remove("gentext");
+ };
+
+ this.finalCheck = function removeGenContent_finalCheck() {
+ var accTree =
+ { SECTION: [ // container
+ { SECTION: [ // inserted node
+ { TEXT_LEAF: [] }, // primary text
+ ] },
+ ] };
+ testAccessibleTree(this.container, accTree);
+ };
+
+ this.getID = function addGenContent_getID() {
+ return "remove generated content from" + prettyName(aNodeID);
+ };
+ }
+ /**
+ * Target getters.
+ */
+ function getFirstChild(aAcc) {
+ try { return aAcc.firstChild; } catch (e) { return null; }
+ }
+
+ function getLastChild(aAcc) {
+ try { return aAcc.lastChild; } catch (e) { return null; }
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+ // //////////////////////////////////////////////////////////////////////////
+
+ var gQueue = null;
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new insertNodeHavingGenContent("container1"));
+ gQueue.push(new addGenContent("container2", "container2_child"));
+ gQueue.push(new removeGenContent("container3", "container3_child"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=646350"
+ title="Add a test for dynamic chnages of CSS generated content">
+ Mozilla Bug 646350</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <div id="container1"></div>
+ <div id="container2"><div id="container2_child">text</div></div>
+ <div id="container3"><div id="container3_child" class="gentext">text</div></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_general.html b/accessible/tests/mochitest/treeupdate/test_general.html
new file mode 100644
index 0000000000..8129cae98a
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_general.html
@@ -0,0 +1,174 @@
+<html>
+
+<head>
+ <title>Testing the tree updates</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+ // //////////////////////////////////////////////////////////////////////////
+
+ function prependAppend(aContainer) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aContainer),
+ ];
+
+ this.invoke = function prependAppend_invoke() {
+ var checkbox = document.createElement("input");
+ checkbox.setAttribute("type", "checkbox");
+ getNode(aContainer).insertBefore(checkbox, getNode(aContainer).firstChild);
+
+ var button = document.createElement("input");
+ button.setAttribute("type", "button");
+ getNode(aContainer).appendChild(button);
+ };
+
+ this.finalCheck = function prependAppend_finalCheck() {
+ var accTree =
+ { SECTION: [ // container
+ { CHECKBUTTON: [ ] },
+ { ENTRY: [ ] },
+ { PUSHBUTTON: [ ] },
+ ] };
+ testAccessibleTree(aContainer, accTree);
+ };
+
+ this.getID = function prependAppend_getID() {
+ return "prepends a child and appends a child";
+ };
+ }
+
+ function removeRemove(aContainer) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aContainer),
+ ];
+
+ this.invoke = function removeRemove_invoke() {
+ getNode(aContainer).firstChild.remove();
+ };
+
+ this.finalCheck = function removeRemove_finalCheck() {
+ var accTree =
+ { SECTION: [ // container
+ { PUSHBUTTON: [ ] },
+ ] };
+ testAccessibleTree(aContainer, accTree);
+ };
+
+ this.getID = function removeRemove_getID() {
+ return "remove first and second children";
+ };
+ }
+
+ function insertInaccessibleAccessibleSiblings() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, "c3"),
+ ];
+
+ this.invoke = function insertInaccessibleAccessibleSiblings_invoke() {
+ getNode("c3").appendChild(document.createElement("span"));
+ getNode("c3").appendChild(document.createElement("input"));
+ };
+
+ this.finalCheck = function insertInaccessibleAccessibleSiblings_finalCheck() {
+ var accTree =
+ { SECTION: [ // container
+ { PUSHBUTTON: [
+ { TEXT_LEAF: [] },
+ ] },
+ { ENTRY: [ ] },
+ ] };
+ testAccessibleTree("c3", accTree);
+ };
+
+ this.getID = function insertInaccessibleAccessibleSiblings_getID() {
+ return "insert inaccessible and then accessible siblings";
+ };
+ }
+
+ // Test for bug 1500416.
+ function displayContentsInsertion() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, "c4"),
+ ];
+
+ this.invoke = function displayContentsInsertion_invoke() {
+ document.body.offsetTop; // Flush layout.
+
+ let list = document.createElement("ul");
+ list.style.display = "contents";
+ list.appendChild(document.createElement("li"));
+ list.firstChild.appendChild(document.createTextNode("Text"));
+ getNode("c4").appendChild(list);
+ };
+
+ this.finalCheck = function displayContentsInsertion_finalCheck() {
+ var accTree =
+ { SECTION: [ // container
+ { LIST: [
+ { LISTITEM: [
+ { LISTITEM_MARKER: [] },
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree("c4", accTree);
+ };
+
+ this.getID = function displayContentsInsertion_getID() {
+ return "insert accessible display: contents element.";
+ };
+ }
+
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Do tests
+ // //////////////////////////////////////////////////////////////////////////
+
+ var gQueue = null;
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new prependAppend("c1"));
+ gQueue.push(new removeRemove("c2"));
+ gQueue.push(new insertInaccessibleAccessibleSiblings());
+ gQueue.push(new displayContentsInsertion());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="c1"><input></div>
+ <div id="c2"><span><input type="checkbox"><input></span><input type="button"></div>
+
+ <div id="c3"><input type="button" value="button"></div>
+ <div id="c4"></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_hidden.html b/accessible/tests/mochitest/treeupdate/test_hidden.html
new file mode 100644
index 0000000000..e687fc97c9
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_hidden.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>@hidden attribute testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+ // //////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Set @hidden attribute
+ */
+ function setHiddenAttr(aContainerID, aChildID) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID)),
+ ];
+
+ this.invoke = function setHiddenAttr_invoke() {
+ var tree =
+ { SECTION: [
+ { ENTRY: [
+ ] },
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aChildID).setAttribute("hidden", "true");
+ };
+
+ this.finalCheck = function setHiddenAttr_finalCheck() {
+ var tree =
+ { SECTION: [
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ };
+
+ this.getID = function setHiddenAttr_getID() {
+ return "Set @hidden attribute on input and test accessible tree for div";
+ };
+ }
+
+ /**
+ * Remove @hidden attribute
+ */
+ function removeHiddenAttr(aContainerID, aChildID) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID)),
+ ];
+
+ this.invoke = function removeHiddenAttr_invoke() {
+ var tree =
+ { SECTION: [
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aChildID).removeAttribute("hidden");
+ };
+
+ this.finalCheck = function removeHiddenAttr_finalCheck() {
+ var tree =
+ { SECTION: [
+ { ENTRY: [
+ ] },
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ };
+
+ this.getID = function removeHiddenAttr_getID() {
+ return "Remove @hidden attribute on input and test accessible tree for div";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+ // //////////////////////////////////////////////////////////////////////////
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new setHiddenAttr("container", "child"));
+ gQueue.push(new removeHiddenAttr("container", "child"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+
+ </script>
+
+</head>
+
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="container"><input id="child"></div>
+
+ <div id="eventdump"></div>
+
+</body>
+
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_imagemap.html b/accessible/tests/mochitest/treeupdate/test_imagemap.html
new file mode 100644
index 0000000000..7befef6905
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_imagemap.html
@@ -0,0 +1,402 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML img map accessible tree update tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function insertArea(aImageMapID, aMapID) {
+ this.imageMap = getAccessible(aImageMapID);
+ this.mapNode = getNode(aMapID);
+
+ function getInsertedArea(aThisObj) {
+ return aThisObj.imageMap.firstChild;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getInsertedArea, this),
+ new invokerChecker(EVENT_REORDER, this.imageMap),
+ ];
+
+ this.invoke = function insertArea_invoke() {
+ var areaElm = document.createElement("area");
+ areaElm.setAttribute("href",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#a");
+ areaElm.setAttribute("coords", "0,0,13,14");
+ areaElm.setAttribute("alt", "a");
+ areaElm.setAttribute("shape", "rect");
+
+ this.mapNode.insertBefore(areaElm, this.mapNode.firstChild);
+ };
+
+ this.finalCheck = function insertArea_finalCheck() {
+ var accTree =
+ { IMAGE_MAP: [
+ {
+ role: ROLE_LINK,
+ name: "a",
+ children: [ ],
+ },
+ {
+ role: ROLE_LINK,
+ name: "b",
+ children: [ ],
+ },
+ ] };
+ testAccessibleTree(this.imageMap, accTree);
+ };
+
+ this.getID = function insertArea_getID() {
+ return "insert area element";
+ };
+ }
+
+ function appendArea(aImageMapID, aMapID) {
+ this.imageMap = getAccessible(aImageMapID);
+ this.mapNode = getNode(aMapID);
+
+ function getAppendedArea(aThisObj) {
+ return aThisObj.imageMap.lastChild;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getAppendedArea, this),
+ new invokerChecker(EVENT_REORDER, this.imageMap),
+ ];
+
+ this.invoke = function appendArea_invoke() {
+ var areaElm = document.createElement("area");
+ areaElm.setAttribute("href",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#c");
+ areaElm.setAttribute("coords", "34,0,47,14");
+ areaElm.setAttribute("alt", "c");
+ areaElm.setAttribute("shape", "rect");
+
+ this.mapNode.appendChild(areaElm);
+ };
+
+ this.finalCheck = function appendArea_finalCheck() {
+ var accTree =
+ { IMAGE_MAP: [
+ {
+ role: ROLE_LINK,
+ name: "a",
+ children: [ ],
+ },
+ {
+ role: ROLE_LINK,
+ name: "b",
+ children: [ ],
+ },
+ {
+ role: ROLE_LINK,
+ name: "c",
+ children: [ ],
+ },
+ ] };
+ testAccessibleTree(this.imageMap, accTree);
+ };
+
+ this.getID = function appendArea_getID() {
+ return "append area element";
+ };
+ }
+
+ function removeArea(aImageMapID, aMapID) {
+ this.imageMap = getAccessible(aImageMapID);
+ this.area = null;
+ this.mapNode = getNode(aMapID);
+
+ function getRemovedArea(aThisObj) {
+ return aThisObj.area;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getRemovedArea, this),
+ new invokerChecker(EVENT_REORDER, this.imageMap),
+ ];
+
+ this.invoke = function removeArea_invoke() {
+ this.area = this.imageMap.firstChild;
+ this.mapNode.removeChild(this.mapNode.firstElementChild);
+ };
+
+ this.finalCheck = function removeArea_finalCheck() {
+ var accTree =
+ { IMAGE_MAP: [
+ {
+ role: ROLE_LINK,
+ name: "b",
+ children: [ ],
+ },
+ {
+ role: ROLE_LINK,
+ name: "c",
+ children: [ ],
+ },
+ ] };
+ testAccessibleTree(this.imageMap, accTree);
+ };
+
+ this.getID = function removeArea_getID() {
+ return "remove area element";
+ };
+ }
+
+ function removeNameOnMap(aImageMapContainerID, aImageMapID, aMapID) {
+ this.container = getAccessible(aImageMapContainerID);
+ this.containerNode = this.container.DOMNode;
+ this.imageMap = getAccessible(aImageMapID);
+ this.imgNode = this.imageMap.DOMNode;
+ this.mapNode = getNode(aMapID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.imageMap),
+ new invokerChecker(EVENT_SHOW, this.imgNode),
+ new invokerChecker(EVENT_REORDER, this.container),
+ ];
+
+ this.invoke = function removeNameOnMap_invoke() {
+ this.mapNode.removeAttribute("name");
+ };
+
+ this.finalCheck = function removeNameOnMap_finalCheck() {
+ var accTree =
+ { SECTION: [
+ { GRAPHIC: [ ] },
+ ] };
+ testAccessibleTree(this.container, accTree);
+ };
+
+ this.getID = function removeNameOnMap_getID() {
+ return "remove @name on map element";
+ };
+ }
+
+ function restoreNameOnMap(aImageMapContainerID, aImageMapID, aMapID) {
+ this.container = getAccessible(aImageMapContainerID);
+ this.containerNode = this.container.DOMNode;
+ this.imageMap = null;
+ this.imgNode = getNode(aImageMapID);
+ this.mapNode = getNode(aMapID);
+
+ function getImageMap(aThisObj) {
+ return aThisObj.imageMap;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getImageMap, this),
+ new invokerChecker(EVENT_SHOW, this.imgNode),
+ new invokerChecker(EVENT_REORDER, this.container),
+ ];
+
+ this.invoke = function restoreNameOnMap_invoke() {
+ this.imageMap = getAccessible(aImageMapID);
+ this.mapNode.setAttribute("name", "atoz_map");
+
+ // XXXhack: force repainting of the image (see bug 745788 for details).
+ waveOverImageMap(aImageMapID);
+ };
+
+ this.finalCheck = function removeNameOnMap_finalCheck() {
+ var accTree =
+ { SECTION: [
+ { IMAGE_MAP: [
+ { LINK: [ ] },
+ { LINK: [ ] },
+ ] },
+ ] };
+ testAccessibleTree(this.container, accTree);
+ };
+
+ this.getID = function removeNameOnMap_getID() {
+ return "restore @name on map element";
+ };
+ }
+
+ function removeMap(aImageMapContainerID, aImageMapID, aMapID) {
+ this.container = getAccessible(aImageMapContainerID);
+ this.containerNode = this.container.DOMNode;
+ this.imageMap = null;
+ this.imgNode = getNode(aImageMapID);
+ this.mapNode = getNode(aMapID);
+
+ function getImageMap(aThisObj) {
+ return aThisObj.imageMap;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getImageMap, this),
+ new invokerChecker(EVENT_SHOW, this.imgNode),
+ new invokerChecker(EVENT_REORDER, this.container),
+ ];
+
+ this.invoke = function removeMap_invoke() {
+ this.imageMap = getAccessible(aImageMapID);
+ this.mapNode.remove();
+ };
+
+ this.finalCheck = function removeMap_finalCheck() {
+ var accTree =
+ { SECTION: [
+ { GRAPHIC: [ ] },
+ ] };
+ testAccessibleTree(this.container, accTree);
+ };
+
+ this.getID = function removeMap_getID() {
+ return "remove map element";
+ };
+ }
+
+ function insertMap(aImageMapContainerID, aImageID) {
+ this.container = getAccessible(aImageMapContainerID);
+ this.containerNode = this.container.DOMNode;
+ this.image = null;
+ this.imgMapNode = getNode(aImageID);
+
+ function getImage(aThisObj) {
+ return aThisObj.image;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getImage, this),
+ new invokerChecker(EVENT_SHOW, this.imgMapNode),
+ new invokerChecker(EVENT_REORDER, this.container),
+ ];
+
+ this.invoke = function insertMap_invoke() {
+ this.image = getAccessible(aImageID);
+
+ var map = document.createElement("map");
+ map.setAttribute("name", "atoz_map");
+ map.setAttribute("id", "map");
+
+ var area = document.createElement("area");
+ area.setAttribute("href",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#b");
+ area.setAttribute("coords", "17,0,30,14");
+ area.setAttribute("alt", "b");
+ area.setAttribute("shape", "rect");
+
+ map.appendChild(area);
+
+ this.containerNode.appendChild(map);
+ };
+
+ this.finalCheck = function insertMap_finalCheck() {
+ var accTree =
+ { SECTION: [
+ { IMAGE_MAP: [
+ { LINK: [ ] },
+ ] },
+ ] };
+ testAccessibleTree(this.container, accTree);
+ };
+
+ this.getID = function insertMap_getID() {
+ return "insert map element";
+ };
+ }
+
+ function hideImageMap(aContainerID, aImageID) {
+ this.container = getAccessible(aContainerID);
+ this.imageMap = null;
+ this.imageMapNode = getNode(aImageID);
+
+ function getImageMap(aThisObj) {
+ return aThisObj.imageMap;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getImageMap, this),
+ new invokerChecker(EVENT_REORDER, aContainerID),
+ ];
+
+ this.invoke = function hideImageMap_invoke() {
+ this.imageMap = getAccessible(this.imageMapNode);
+ this.imageMapNode.style.display = "none";
+ };
+
+ this.finalCheck = function hideImageMap_finalCheck() {
+ var accTree =
+ { SECTION: [ ] };
+ testAccessibleTree(this.container, accTree);
+ };
+
+ this.getID = function hideImageMap_getID() {
+ return "display:none image";
+ };
+ }
+
+ // gA11yEventDumpToConsole = true; // debug stuff
+ function doPreTest() {
+ waitForImageMap("imgmap", doTest);
+ }
+
+ var gQueue = null;
+ function doTest() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new insertArea("imgmap", "map"));
+ gQueue.push(new appendArea("imgmap", "map"));
+ gQueue.push(new removeArea("imgmap", "map"));
+ gQueue.push(new removeNameOnMap("container", "imgmap", "map"));
+ gQueue.push(new restoreNameOnMap("container", "imgmap", "map"));
+ gQueue.push(new removeMap("container", "imgmap", "map"));
+ gQueue.push(new insertMap("container", "imgmap"));
+ gQueue.push(new hideImageMap("container", "imgmap"));
+
+ // enableLogging("tree"); // debug stuff
+ // gQueue.onFinish = function() { disableLogging("tree"); }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ title="Image map accessible tree is not updated when image map is changed"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=732389">
+ Mozilla Bug 732389
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <map name="atoz_map" id="map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ </map>
+
+ <div id="container">
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="../letters.gif"><!--
+ Important: no whitespace between the <img> and the </div>, so we
+ don't end up with textframes there, because those would be reflected
+ in our accessible tree in some cases.
+ --></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_inert.html b/accessible/tests/mochitest/treeupdate/test_inert.html
new file mode 100644
index 0000000000..0364f6612b
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_inert.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Inert tree update test</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script src="../common.js"></script>
+ <script src="../promisified-events.js"></script>
+ <script src="../role.js"></script>
+
+ <script>
+ async function doTest() {
+ if (SpecialPowers.getBoolPref("html5.inert.enabled")) {
+ const inertContainer = getAccessible("inertContainer");
+ const inertTree = { SECTION: [ // inertContainer
+ { PARAGRAPH: [{ TEXT_LEAF: [] }] }, // before
+ { PARAGRAPH: [{ TEXT_LEAF: [] }] }, // after
+ ]};
+ testAccessibleTree(inertContainer, inertTree);
+
+ info("Unsetting inert");
+ let reordered = waitForEvent(EVENT_REORDER, inertContainer);
+ const inertNode = getNode("inert");
+ inertNode.inert = false;
+ await reordered;
+ testAccessibleTree(inertContainer, { SECTION: [ // inertContainer
+ { PARAGRAPH: [{ TEXT_LEAF: [] }] }, // before
+ { SECTION: [ // inert
+ { TEXT_LEAF: [] },
+ { PUSHBUTTON: [] },
+ ] },
+ { PARAGRAPH: [{ TEXT_LEAF: [] }] }, // after
+ ]});
+
+ info("Setting inert");
+ reordered = waitForEvent(EVENT_REORDER, inertContainer);
+ inertNode.inert = true;
+ await reordered;
+ testAccessibleTree(inertContainer, inertTree);
+ } else {
+ // It's too late to flip the pref now. We'd need to load a new document
+ // to pick up the change.
+ todo(false, "Inert not enabled, skipping inert tests");
+ }
+
+ const noDialogTree = { SECTION: [ // dialogContainer
+ { TEXT_LEAF: [] }
+ ] };
+ testAccessibleTree("dialogContainer", noDialogTree);
+
+ info("Showing modal dialog");
+ let reordered = waitForEvent(EVENT_REORDER, document);
+ const dialogNode = getNode("dialog");
+ dialogNode.showModal();
+ await reordered;
+ // The dialog makes everything else in the document inert.
+ testAccessibleTree(document, { DOCUMENT: [
+ { DIALOG: [
+ { PUSHBUTTON: [] }
+ ] }
+ ] });
+
+ info("Closing dialog");
+ reordered = waitForEvent(EVENT_REORDER, document);
+ dialogNode.close();
+ await reordered;
+ testAccessibleTree("dialogContainer", noDialogTree);
+
+ const fullscreenTree = { SECTION: [ // fullscreen
+ { PUSHBUTTON: [] }
+ ] };
+ const notFullscreenTree = { SECTION: [ // fullscreenContainer
+ { SECTION: [
+ { PUSHBUTTON: [] } // requestFullscreen
+ ] },
+ fullscreenTree,
+ ] };
+ testAccessibleTree("fullscreenContainer", notFullscreenTree);
+
+ info("Requesting fullscreen");
+ reordered = waitForEvent(EVENT_REORDER, document);
+ // Fullscreen must be requested by a user input event.
+ synthesizeMouseAtCenter(getNode("requestFullscreen"), {});
+ await reordered;
+ // Fullscreen makes everything else in the document inert.
+ testAccessibleTree(document, { DOCUMENT: [
+ fullscreenTree
+ ] });
+
+ info("Exiting fullscreen");
+ reordered = waitForEvent(EVENT_REORDER, document);
+ document.exitFullscreen();
+ await reordered;
+ testAccessibleTree("fullscreenContainer", notFullscreenTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="inertContainer">
+ <p>before</p>
+ <div id="inert" inert>inert <button></button></div>
+ <p>after</p>
+ </div>
+
+ <div id="dialogContainer">
+ dialogContainer
+ <dialog id="dialog"><button></button></dialog>
+ </div>
+
+ <div id="fullscreenContainer">
+ <div>
+ <button id="requestFullscreen"
+ onclick="document.getElementById('fullscreen').requestFullscreen();">
+ </button>
+ </div>
+ <div id="fullscreen"><button></button></div>
+ </div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_inner_reorder.html b/accessible/tests/mochitest/treeupdate/test_inner_reorder.html
new file mode 100644
index 0000000000..b4411833d7
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_inner_reorder.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test accessible delayed removal</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+
+ async function testInnerReorder() {
+ window.windowUtils.advanceTimeAndRefresh(100);
+
+ let events = waitForOrderedEvents([
+ [EVENT_HIDE, "c1.1.1"],
+ [EVENT_SHOW, "c1.1.1"],
+ [EVENT_INNER_REORDER, "c1.1"],
+ [EVENT_REORDER, "c1"],
+ ], "events yay");
+
+ let child = getNode("c1.1.1");
+ child.remove();
+ getNode("c1").appendChild(child);
+
+ window.windowUtils.restoreNormalRefresh();
+
+ await events;
+ }
+
+ async function testInnerReorderEntry() {
+ window.windowUtils.advanceTimeAndRefresh(100);
+
+ let events = waitForOrderedEvents([
+ [EVENT_HIDE, e => e.accessible.name == "hello"],
+ [EVENT_HIDE, "c2.2"],
+ [EVENT_INNER_REORDER, "c2.1"],
+ [EVENT_REORDER, "c2"],
+ [EVENT_TEXT_VALUE_CHANGE, "c2.1"],
+ ], "events yay");
+
+ getNode("c2.1.1").remove();
+ getNode("c2.2").remove();
+
+ window.windowUtils.restoreNormalRefresh();
+
+ await events;
+ }
+
+ async function testInnerReorderAriaOwns() {
+ let events = waitForOrderedEvents([
+ [EVENT_HIDE, "c3.1.1"],
+ [EVENT_SHOW, "c3.1.1"],
+ [EVENT_INNER_REORDER, "c3.1"],
+ [EVENT_REORDER, "c3"],
+ ], "events yay");
+
+ getNode("c3").setAttribute("aria-owns", "c3.1.1");
+
+ await events;
+
+ events = waitForOrderedEvents([
+ [EVENT_HIDE, "c3.1.1"],
+ [EVENT_SHOW, "c3.1.1"],
+ [EVENT_INNER_REORDER, "c3.1"],
+ [EVENT_REORDER, "c3"],
+ ], "events yay");
+
+ getNode("c3").removeAttribute("aria-owns");
+
+ await events;
+ }
+
+ async function testInnerContainerRemoved() {
+ window.windowUtils.advanceTimeAndRefresh(100);
+
+ let events = waitForOrderedEvents([
+ [EVENT_HIDE, "c4.1"],
+ [EVENT_SHOW, "c4.1.1"],
+ [EVENT_REORDER, "c4"],
+ ], "events yay");
+
+ let child = getNode("c4.1.1");
+ child.remove();
+ getNode("c1").appendChild(child);
+ getNode("c4.1").remove();
+
+ window.windowUtils.restoreNormalRefresh();
+
+ await events;
+ }
+
+
+ async function doTest() {
+ await testInnerReorder();
+
+ await testInnerReorderEntry();
+
+ await testInnerReorderAriaOwns();
+
+ await testInnerContainerRemoved();
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="c1">
+ <div id="c1.1"><div id="c1.1.1">hello</div></div>
+ </div>
+
+ <div id="c2">
+ <div role="textbox" contenteditable="true" id="c2.1">
+ <span id="c2.1.1">hello</span>
+ </div>
+ <input type="submit" id="c2.2">
+ </div>
+
+ <div id="c3">
+ <div id="c3.1"><div id="c3.1.1"></div></div>
+ </div>
+
+ <div id="c4">
+ <div id="c4.1"><div id="c4.1.1">hello</div></div>
+ </div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_list.html b/accessible/tests/mochitest/treeupdate/test_list.html
new file mode 100644
index 0000000000..06c308e422
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_list.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test HTML li and listitem bullet accessible cache</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Helpers
+
+ function testLiAccessibleTree() {
+ // Test accessible tree.
+ var accTree = {
+ role: ROLE_LISTITEM,
+ children: [
+ {
+ role: ROLE_LISTITEM_MARKER,
+ children: [],
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ children: [],
+ },
+ ],
+ };
+
+ testAccessibleTree("li", accTree);
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Sequence item processors
+
+ function hideProcessor() {
+ this.liNode = getNode("li");
+ this.li = getAccessible(this.liNode);
+ this.bullet = this.li.firstChild;
+
+ this.process = function hideProcessor_process() {
+ this.liNode.style.display = "none";
+ };
+
+ this.onProcessed = function hideProcessor_onProcessed() {
+ window.setTimeout(
+ function(aLiAcc, aLiNode, aBulletAcc) {
+ testDefunctAccessible(aLiAcc, aLiNode);
+ testDefunctAccessible(aBulletAcc);
+
+ gSequence.processNext();
+ },
+ 0, this.li, this.liNode, this.bullet
+ );
+ };
+ }
+
+ function showProcessor() {
+ this.liNode = getNode("li");
+
+ this.process = function showProcessor_process() {
+ this.liNode.style.display = "list-item";
+ };
+
+ this.onProcessed = function showProcessor_onProcessed() {
+ testLiAccessibleTree();
+ gSequence.processNext();
+ };
+ }
+
+ function textReplaceProcessor() {
+ this.liNode = getNode("li");
+
+ this.process = function textReplaceProcessor_process() {
+ this.liNode.textContent = "hey";
+ };
+
+ this.onProcessed = function textReplaceProcessor_onProcessed() {
+ var tree = {
+ LISTITEM: [
+ { LISTITEM_MARKER: [] },
+ { TEXT_LEAF: [] },
+ ],
+ };
+ testAccessibleTree(this.liNode, tree);
+ SimpleTest.finish();
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpToConsole = true;
+
+ var gSequence = null;
+ function doTest() {
+ testLiAccessibleTree();
+
+ gSequence = new sequence();
+
+ gSequence.append(new hideProcessor(), EVENT_HIDE, getAccessible("li"),
+ "hide HTML li");
+ gSequence.append(new showProcessor(), EVENT_SHOW, getNode("li"),
+ "show HTML li");
+ gSequence.append(new textReplaceProcessor(), EVENT_REORDER, getNode("li"),
+ "change text of HTML li");
+
+ gSequence.processNext(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="setParent shouldn't be virtual"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=496783">Mozilla Bug 496783</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ul>
+ <li id="li">item1</li>
+ </ul>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html b/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html
new file mode 100644
index 0000000000..59b9dc9c53
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test HTML li and listitem bullet accessible insertion into editable document</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function addLi(aID) {
+ this.listNode = getNode(aID);
+ this.liNode = document.createElement("li");
+ this.liNode.textContent = "item";
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getAccessible, this.liNode),
+ new invokerChecker(EVENT_REORDER, this.listNode),
+ ];
+
+ this.invoke = function addLi_invoke() {
+ this.listNode.appendChild(this.liNode);
+ };
+
+ this.finalCheck = function addLi_finalCheck() {
+ var tree = {
+ role: ROLE_LIST,
+ children: [
+ {
+ role: ROLE_LISTITEM,
+ children: [
+ {
+ role: ROLE_LISTITEM_MARKER,
+ name: "1. ",
+ children: [],
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ children: [],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(aID, tree);
+ };
+
+ this.getID = function addLi_getID() {
+ return "add li";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+
+ var gQueue = null;
+
+ function doTest() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new addLi("list"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body contentEditable="true">
+
+ <a target="_blank"
+ title="Wrong list bullet text of accessible for the first numbered HTML:li in CKEditor"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=557795">Mozilla Bug 557795</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ol id="list">
+ </ol>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_list_style.html b/accessible/tests/mochitest/treeupdate/test_list_style.html
new file mode 100644
index 0000000000..1d9e1c00ca
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_list_style.html
@@ -0,0 +1,181 @@
+<html>
+
+<head>
+ <title>Test hide/show events for HTMLListBulletAccessibles on list restyle</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Change list style type to none.
+ */
+ async function hideBullet() {
+ info("Hide bullet by setting style to none");
+
+ let liAcc = getAccessible("list_element");
+ let bullet = liAcc.firstChild;
+
+ let events = waitForEvents([
+ [EVENT_HIDE, bullet],
+ [EVENT_REORDER, liAcc]
+ ]);
+
+ getNode("list").style.setProperty("list-style-type", "none");
+
+ await events;
+
+ is(liAcc.name, "list element",
+ "Check that first child of LI is not a bullet.");
+
+ dumpTree("list");
+ }
+
+ /**
+ * Change list style type to circles.
+ */
+ async function showBullet() {
+ info("Show bullet by setting style to circle");
+ let liAcc = getAccessible("list_element");
+
+ let events = waitForEvents([
+ [EVENT_SHOW, evt => evt.accessible.parent == liAcc],
+ [EVENT_REORDER, liAcc]
+ ]);
+
+ getNode("list").style.setProperty("list-style-type", "circle");
+
+ await events;
+
+ is(liAcc.name, "◦ list element",
+ "Check that first child of LI is a circle bullet.");
+
+ dumpTree("list");
+ }
+
+ /**
+ * Change list style position.
+ */
+ async function changeBulletPosition() {
+ info("Change list style position");
+ let liAcc = getAccessible("list_element");
+
+ let events = waitForEvents([
+ [EVENT_HIDE, evt => evt.accessible.role == ROLE_LISTITEM_MARKER],
+ [EVENT_SHOW, evt => evt.accessible.role == ROLE_LISTITEM_MARKER],
+ [EVENT_REORDER, liAcc]
+ ]);
+
+ getNode("list").style.setProperty("list-style-position", "inside");
+
+ await events;
+
+ is(liAcc.name, "◦ list element",
+ "Check that first child of LI is a circle bullet.");
+ }
+
+ async function changeBulletPositionAndType() {
+ let events = waitForEvents([
+ [EVENT_HIDE, evt => evt.accessible.role == ROLE_LISTITEM_MARKER],
+ [EVENT_REORDER, evt => evt.accessible.role == ROLE_LISTITEM]
+ ]);
+
+ let list = getNode("inside-marker-list");
+
+ // Bug 1513447 - This changes the list type to "none" and the
+ // position implicitly to "outside".
+ list.style.setProperty("list-style", "none");
+ list.offsetLeft
+ list.style.setProperty("list-style-type", "telugu");
+
+ await events;
+ }
+
+ async function doTest() {
+
+ testAccessibleTree("list", { LIST: [ // ol
+ { LISTITEM: [ // li
+ { LISTITEM_MARKER: [ ] },
+ { TEXT_LEAF: [] },
+ ] },
+ ] } );
+
+ await hideBullet();
+
+ testAccessibleTree("list", { LIST: [ // ol
+ { LISTITEM: [ // li
+ { TEXT_LEAF: [] },
+ ] },
+ ] } );
+
+ await showBullet();
+
+ testAccessibleTree("list", { LIST: [ // ol
+ { LISTITEM: [ // li
+ { LISTITEM_MARKER: [ ] },
+ { TEXT_LEAF: [] },
+ ] },
+ ] } );
+
+ await changeBulletPosition();
+
+ testAccessibleTree("list", { LIST: [ // ol
+ { LISTITEM: [ // li
+ { LISTITEM_MARKER: [ ] },
+ { TEXT_LEAF: [] },
+ ] },
+ ] } );
+
+ testAccessibleTree("unmarked-list", { LIST: [ // ol
+ { LISTITEM: [ // li
+ { TEXT_LEAF: [] },
+ ] },
+ ] } );
+
+ await changeBulletPositionAndType();
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1100602"
+ title="[e10s] crash in mozilla::a11y::ProxyAccessible::Shutdown()">
+ Mozilla Bug 1100602
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ol id="list" style="list-style-type: circle;">
+ <li id="list_element">list element</li>
+ </ol>
+
+ <ol id="unmarked-list" style="list-style: none;">
+ <li>list element</li>
+ </ol>
+
+ <ol id="inside-marker-list" style="list-style-position: inside;">
+ <li>list element</li>
+ </ol>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_listbox.xhtml b/accessible/tests/mochitest/treeupdate/test_listbox.xhtml
new file mode 100644
index 0000000000..629c4b0915
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_listbox.xhtml
@@ -0,0 +1,181 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL listbox hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function insertListitem(aListboxID)
+ {
+ this.listboxNode = getNode(aListboxID);
+
+ this.listitemNode = document.createXULElement("richlistitem");
+ var label = document.createXULElement("label");
+ label.setAttribute("value", "item1");
+ this.listitemNode.appendChild(label);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.listitemNode),
+ new invokerChecker(EVENT_REORDER, this.listboxNode)
+ ];
+
+ this.invoke = function insertListitem_invoke()
+ {
+ this.listboxNode.insertBefore(this.listitemNode,
+ this.listboxNode.firstChild);
+ }
+
+ this.finalCheck = function insertListitem_finalCheck()
+ {
+ var tree =
+ { LISTBOX: [
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item1"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item2"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item3"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item4"
+ }
+ ] };
+ testAccessibleTree(this.listboxNode, tree);
+ }
+
+ this.getID = function insertListitem_getID()
+ {
+ return "insert listitem ";
+ }
+ }
+
+ function removeListitem(aListboxID)
+ {
+ this.listboxNode = getNode(aListboxID);
+ this.listitemNode = null;
+ this.listitem;
+
+ function getListitem(aThisObj)
+ {
+ return aThisObj.listitem;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getListitem, this),
+ new invokerChecker(EVENT_REORDER, this.listboxNode)
+ ];
+
+ this.invoke = function removeListitem_invoke()
+ {
+ this.listitemNode = this.listboxNode.firstChild;
+ this.listitem = getAccessible(this.listitemNode);
+
+ this.listboxNode.removeChild(this.listitemNode);
+ }
+
+ this.finalCheck = function removeListitem_finalCheck()
+ {
+ var tree =
+ { LISTBOX: [
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item2"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item3"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item4"
+ }
+ ] };
+ testAccessibleTree(this.listboxNode, tree);
+ }
+
+ this.getID = function removeListitem_getID()
+ {
+ return "remove listitem ";
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest()
+ {
+ var tree =
+ { LISTBOX: [
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item2"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item3"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item4"
+ }
+ ] };
+ testAccessibleTree("listbox", tree);
+
+ gQueue = new eventQueue();
+ gQueue.push(new insertListitem("listbox"));
+ gQueue.push(new removeListitem("listbox"));
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=656225"
+ title="XUL listbox accessible tree doesn't get updated">
+ Mozilla Bug 656225
+ </a>
+ <br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <richlistbox id="listbox">
+ <richlistitem><label value="item2"/></richlistitem>
+ <richlistitem><label value="item3"/></richlistitem>
+ <richlistitem><label value="item4"/></richlistitem>
+ </richlistbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/treeupdate/test_menu.xhtml b/accessible/tests/mochitest/treeupdate/test_menu.xhtml
new file mode 100644
index 0000000000..44042cc9e7
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_menu.xhtml
@@ -0,0 +1,127 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL menu hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function openMenu(aID)
+ {
+ this.menuNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.menuNode)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ var tree;
+ if (LINUX || SOLARIS) {
+ tree =
+ { PARENT_MENUITEM: [ ] };
+
+ } else {
+ tree =
+ { PARENT_MENUITEM: [
+ { MENUPOPUP: [ ] }
+ ] };
+ }
+ testAccessibleTree(aID, tree);
+
+ // Show menu.
+ this.menuNode.open = true;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ var tree;
+ if (LINUX || SOLARIS) {
+ tree =
+ { PARENT_MENUITEM: [
+ { MENUITEM: [ ] },
+ { MENUITEM: [ ] }
+ ] };
+
+ } else {
+ tree =
+ { PARENT_MENUITEM: [
+ { MENUPOPUP: [
+ { MENUITEM: [ ] },
+ { MENUITEM: [ ] }
+ ] }
+ ] };
+ }
+ testAccessibleTree(aID, tree);
+ }
+
+ this.getID = function openMenu_getID()
+ {
+ return "open menu " + prettyName(aID);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new openMenu("menu"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292"
+ title="Ensure accessible children for toolbarbutton types 'menu'">
+ Mozilla Bug 249292
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486"
+ title="Don't force accessible creation for popup children.">
+ Mozilla Bug 630486
+ </a>
+ <br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menubar>
+ <menu id="menu" label="menu">
+ <menupopup>
+ <menuitem label="menuitem"/>
+ <menuitem label="menuitem"/>
+ </menupopup>
+ </menu>
+ </menubar>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/treeupdate/test_menubutton.xhtml b/accessible/tests/mochitest/treeupdate/test_menubutton.xhtml
new file mode 100644
index 0000000000..d3eeb29f78
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_menubutton.xhtml
@@ -0,0 +1,141 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL button hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function openMenu(aButtonID, aIsTree)
+ {
+ var menuItemRole = aIsTree ? ROLE_CHECK_MENU_ITEM : ROLE_MENUITEM;
+ this.button = getAccessible(aButtonID);
+ this.menupopup = aIsTree ? this.button.nextSibling : this.button.firstChild;
+
+ var checker = new invokerChecker(EVENT_REORDER, this.menupopup);
+ this.__proto__ = new synthClick(aButtonID, checker);
+
+ let testButton = popup => {
+ var children = [];
+ if (!aIsTree) {
+ children.push(popup);
+ }
+ var tree = { PUSHBUTTON: children };
+ testAccessibleTree(this.button, tree);
+ testAccessibleTree(this.menupop, popup);
+ }
+
+ this.invoke = function openMenu_invoke()
+ {
+ testButton({ MENUPOPUP: [ ] });
+ this.__proto__.invoke();
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ testButton({ MENUPOPUP: [
+ { role: menuItemRole, children: [ ] },
+ { role: menuItemRole, children: [ ] }
+ ] });
+
+ synthesizeKey("KEY_Escape");
+ }
+
+ this.getID = function openMenu_getID()
+ {
+ return "open menu of the button " + prettyName(aButtonID);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do test
+
+ gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new openMenu("button1"));
+ gQueue.push(new openMenu("button3"));
+
+ var columnPickerBtn = getAccessible("tree").firstChild.lastChild.previousSibling;
+ gQueue.push(new openMenu(columnPickerBtn, true));
+ gQueue.invoke(); // SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292"
+ title="Ensure accessible children for toolbarbutton types 'menu'">
+ Bug 249292
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486"
+ title="Don't force accessible creation for popup children">
+ Bug 630486
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=722265"
+ title="Column header selection popup no longer exposed to accessibility APIs">
+ Bug 722265
+ </a>
+ <br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <button id="button1" type="menu" label="button">
+ <menupopup>
+ <menuitem label="menuitem"/>
+ <menuitem label="menuitem"/>
+ </menupopup>
+ </button>
+
+ <toolbarbutton id="button3" type="menu" label="toolbarbutton">
+ <menupopup>
+ <menuitem label="menuitem"/>
+ <menuitem label="menuitem"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="another column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/treeupdate/test_optgroup.html b/accessible/tests/mochitest/treeupdate/test_optgroup.html
new file mode 100644
index 0000000000..943b73be43
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_optgroup.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Add and remove optgroup test</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ function addOptGroup(aID) {
+ this.selectNode = getNode(aID);
+ this.select = getAccessible(this.selectNode);
+ this.selectList = this.select.firstChild;
+
+ this.invoke = function addOptGroup_invoke() {
+ var optGroup = document.createElement("optgroup");
+ for (let i = 0; i < 2; i++) {
+ var opt = document.createElement("option");
+ opt.value = i;
+ opt.text = "Option: Value " + i;
+
+ optGroup.appendChild(opt);
+ }
+
+ this.selectNode.add(optGroup, null);
+ var option = document.createElement("option");
+ this.selectNode.add(option, null);
+ };
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.selectList),
+ ];
+
+ this.finalCheck = function addOptGroup_finalCheck() {
+ var tree =
+ { COMBOBOX: [
+ { COMBOBOX_LIST: [
+ { GROUPING: [
+ { COMBOBOX_OPTION: [ ] },
+ { COMBOBOX_OPTION: [ ] },
+ ]},
+ { COMBOBOX_OPTION: [] },
+ ] },
+ ] };
+ testAccessibleTree(this.select, tree);
+ };
+
+ this.getID = function addOptGroup_getID() {
+ return "test optgroup's insertion into a select";
+ };
+ }
+
+ function removeOptGroup(aID) {
+ this.selectNode = getNode(aID);
+ this.select = getAccessible(this.selectNode);
+ this.selectList = this.select.firstChild;
+
+ this.invoke = function removeOptGroup_invoke() {
+ this.option1Node = this.selectNode.firstChild.firstChild;
+ this.selectNode.firstChild.remove();
+ };
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.selectList),
+ ];
+
+ this.finalCheck = function removeOptGroup_finalCheck() {
+ var tree =
+ { COMBOBOX: [
+ { COMBOBOX_LIST: [
+ { COMBOBOX_OPTION: [] },
+ ] },
+ ] };
+ testAccessibleTree(this.select, tree);
+ is(isAccessible(this.option1Node), false, "removed option shouldn't be accessible anymore!");
+ };
+
+ this.getID = function removeOptGroup_getID() {
+ return "test optgroup's removal from a select";
+ };
+ }
+
+ // gA11yEventDumpToConsole = true;
+
+ function doTest() {
+ const gQueue = new eventQueue();
+
+ gQueue.push(new addOptGroup("select"));
+ gQueue.push(new removeOptGroup("select"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=616452"
+ title="Bug 616452 - Dynamically inserted select options aren't reflected in accessible tree">
+ Bug 616452</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="select"></select>
+
+ <div id="debug"/>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_recreation.html b/accessible/tests/mochitest/treeupdate/test_recreation.html
new file mode 100644
index 0000000000..d403b0890e
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_recreation.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test accessible recreation</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+
+ async function doTest() {
+ let events, msg;
+
+ msg = "Assign a 'button' role to a span";
+ events = waitForOrderedEvents(
+ [[EVENT_HIDE], [EVENT_SHOW, "span"], [EVENT_REORDER, "container"]], msg);
+ document.getElementById("span").setAttribute("role", "button");
+ await events;
+
+ msg = "Remove the 'button' role from a span";
+ events = waitForOrderedEvents(
+ [[EVENT_HIDE, "span"], [EVENT_SHOW], [EVENT_REORDER, "container"]], msg);
+ document.getElementById("span").removeAttribute("role");
+ await events;
+
+ msg = "Assign a 'button' role to a div";
+ events = waitForOrderedEvents(
+ [[EVENT_HIDE, "div1"], [EVENT_SHOW, "div1"], [EVENT_REORDER, "container"]], msg);
+ document.getElementById("div1").setAttribute("role", "button");
+ await events;
+
+ msg = "Change a password field to a text field";
+ events = waitForOrderedEvents(
+ [[EVENT_HIDE, "password"], [EVENT_SHOW, "password"], [EVENT_REORDER, "container"]], msg);
+ document.getElementById("password").setAttribute("type", "text");
+ await events;
+
+ msg = "Change a text field to a password field";
+ events = waitForOrderedEvents(
+ [[EVENT_HIDE, "text"], [EVENT_SHOW, "text"], [EVENT_REORDER, "container"]], msg);
+ document.getElementById("text").setAttribute("type", "password");
+ await events;
+
+ msg = "Add aria-label to a span";
+ ok(!isAccessible("span2"), "span2 has no accessible");
+ events = waitForOrderedEvents(
+ [[EVENT_SHOW, "span2"], [EVENT_REORDER, "container"]], msg);
+ document.getElementById("span2").setAttribute("aria-label", "label");
+ await events;
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Rework accessible tree update code"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
+ Mozilla Bug 570275
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="container">
+ <span id="span">span</span>
+ <div id="div1">div</div>
+ <a id="anchor">anchor</a>
+ <div id="div3" role="listbox">list</div>
+ <input type="password" id="password"/>
+ <input type="text" id="text"/>
+ <span id="span2"></span>
+ </div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_select.html b/accessible/tests/mochitest/treeupdate/test_select.html
new file mode 100644
index 0000000000..eabb64f80f
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_select.html
@@ -0,0 +1,191 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML select options test</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ function addOptions(aID) {
+ this.selectNode = getNode(aID);
+ this.select = getAccessible(this.selectNode);
+ this.selectList = this.select.firstChild;
+
+ this.invoke = function addOptions_invoke() {
+ for (let i = 0; i < 2; i++) {
+ var opt = document.createElement("option");
+ opt.value = i;
+ opt.text = "Option: Value " + i;
+
+ this.selectNode.add(opt, null);
+ }
+ };
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.selectList),
+ ];
+
+ this.finalCheck = function addOptions_finalCheck() {
+ var tree =
+ { COMBOBOX: [
+ { COMBOBOX_LIST: [
+ { COMBOBOX_OPTION: [ ] },
+ { COMBOBOX_OPTION: [ ] },
+ ] },
+ ] };
+ testAccessibleTree(this.select, tree);
+ };
+
+ this.getID = function addOptions_getID() {
+ return "test elements insertion into a select";
+ };
+ }
+
+ function removeOptions(aID) {
+ this.selectNode = getNode(aID);
+ this.select = getAccessible(this.selectNode);
+ this.selectList = this.select.firstChild;
+
+ this.invoke = function removeOptions_invoke() {
+ while (this.selectNode.length)
+ this.selectNode.remove(0);
+ };
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.selectList),
+ ];
+
+ this.finalCheck = function removeOptions_finalCheck() {
+ var tree =
+ { COMBOBOX: [
+ { COMBOBOX_LIST: [] },
+ ] };
+ testAccessibleTree(this.select, tree);
+ };
+
+ this.getID = function removeptions_getID() {
+ return "test elements removal from a select";
+ };
+ }
+
+ /**
+ * Setting role=option on option makes the accessible recreate.
+ */
+ function setRoleOnOption() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, "s2_o"),
+ new invokerChecker(EVENT_SHOW, "s2_o"),
+ ];
+
+ this.invoke = function setRoleOnOption_setRole() {
+ getNode("s2_o").setAttribute("role", "option");
+ };
+
+ this.finalCheck = function() {
+ var tree =
+ { COMBOBOX: [
+ { COMBOBOX_LIST: [
+ { COMBOBOX_OPTION: [ ] },
+ ] },
+ ] };
+ testAccessibleTree("s2", tree);
+ };
+
+ this.getID = function removeptions_getID() {
+ return "setting role=option on select option";
+ };
+ }
+
+ /**
+ * Setting multiple on select makes the accessible recreate.
+ */
+ function setMultipleOnSelect() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, "s3"),
+ new invokerChecker(EVENT_SHOW, "s3"),
+ ];
+
+ this.invoke = function setRoleOnOption_setRole() {
+ getNode("s3").multiple = true;
+ };
+
+ this.finalCheck = function() {
+ var tree =
+ { LISTBOX: [
+ { OPTION: [ ] },
+ ] };
+ testAccessibleTree("s3", tree);
+ };
+
+ this.getID = function removeptions_getID() {
+ return "setting multiple on select element";
+ };
+ }
+
+
+ /**
+ * Removing size on select makes the accessible recreate.
+ */
+ function removeSizeFromSelect() {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, "s4"),
+ new invokerChecker(EVENT_SHOW, "s4"),
+ ];
+
+ this.invoke = function setRoleOnOption_setRole() {
+ getNode("s4").removeAttribute("size");
+ };
+
+ this.finalCheck = function() {
+ var tree =
+ { COMBOBOX: [
+ { COMBOBOX_LIST: [
+ { COMBOBOX_OPTION: [ ] },
+ ] },
+ ] };
+ testAccessibleTree("s4", tree);
+ };
+
+ this.getID = function removeptions_getID() {
+ return "removing size from select element";
+ };
+ }
+
+ function doTest() {
+ const gQueue = new eventQueue();
+
+ gQueue.push(new addOptions("select"));
+ gQueue.push(new removeOptions("select"));
+ gQueue.push(new setRoleOnOption());
+ gQueue.push(new setMultipleOnSelect());
+ gQueue.push(new removeSizeFromSelect());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="select"></select>
+ <select id="s2"><option id="s2_o"></option></select>
+ <select id="s3"><option></option></select>
+ <select id="s4" size="3"><option></option></select>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_shadow_slots.html b/accessible/tests/mochitest/treeupdate/test_shadow_slots.html
new file mode 100644
index 0000000000..27baef0e4a
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_shadow_slots.html
@@ -0,0 +1,554 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test shadow roots with slots</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../promisified-events.js"></script>
+
+ <script type="application/javascript">
+ async function _dynamicShadowTest(name, mutationFunc, expectedTree, reorder_targets) {
+ info(name);
+
+ let container = getNode(name);
+ let host = container.querySelector('.host');
+
+ document.body.offsetTop;
+ let event = reorder_targets ?
+ waitForEvents(reorder_targets.map(target => [EVENT_REORDER, target, name])) :
+ waitForEvent(EVENT_REORDER, host, name);
+
+ mutationFunc(container, host);
+
+ await event;
+
+ testAccessibleTree(container, expectedTree);
+
+ return true;
+ }
+
+ async function attachFlatShadow() {
+ await _dynamicShadowTest("attachFlatShadow",
+ (container, host) => {
+ host.attachShadow({ mode: "open" })
+ .appendChild(container.querySelector('.shadowtree').content.cloneNode(true));
+ }, { SECTION: [{ SECTION: [{ name: "red"} ] }] });
+ }
+
+ async function attachOneDeepShadow() {
+ await _dynamicShadowTest("attachOneDeepShadow",
+ (container, host) => {
+ host.attachShadow({ mode: "open" })
+ .appendChild(container.querySelector('.shadowtree').content.cloneNode(true));
+ }, { SECTION: [{ SECTION: [{ SECTION: [{ name: "red"} ] }] }] });
+ }
+
+ async function changeSlotFlat() {
+ await _dynamicShadowTest("changeSlotFlat",
+ (container, host) => {
+ container.querySelector('.red').removeAttribute('slot');
+ container.querySelector('.green').setAttribute('slot', 'myslot');
+ }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+ }
+
+ async function changeSlotOneDeep() {
+ await _dynamicShadowTest("changeSlotOneDeep",
+ (container, host) => {
+ container.querySelector('.red').removeAttribute('slot');
+ container.querySelector('.green').setAttribute('slot', 'myslot');
+ }, { SECTION: [{ SECTION: [{ SECTION: [{ name: "green"} ] }] }] }, ["shadowdiv"]);
+ }
+
+ // Nested roots and slots
+ async function changeSlotNested() {
+ await _dynamicShadowTest("changeSlotNested",
+ (container, host) => {
+ testAccessibleTree(getNode("changeSlotNested"),
+ { SECTION: [{ SECTION: [{ SECTION: [{ name: "red"} ] }] }] });
+ container.querySelector('.red').removeAttribute('slot');
+ container.querySelector('.green').setAttribute('slot', 'myslot');
+ }, { SECTION: [{ SECTION: [{ SECTION: [{ name: "green"} ] }] }] }, ["shadowdiv"]);
+ }
+
+ async function changeSlotSingleChild() {
+ await _dynamicShadowTest("changeSlotSingleChild",
+ (container, host) => {
+ container.querySelector('.red').setAttribute('slot', 'invalid');
+ }, { SECTION: [{ SECTION: [] }] });
+ }
+
+ async function changeSlotNoShadow() {
+ await _dynamicShadowTest("changeSlotNoShadow",
+ (container, host) => {
+ // Make sure changing the slot attribute doesn't remove an element
+ // even when it remains in the flat tree.
+ container.querySelector('.red').setAttribute('slot', 'invalid');
+ // We need a reorder to know when we're done here, so remove another
+ // child.
+ container.querySelector('.green').remove();
+ }, { SECTION: [{ SECTION: [{ name: "red"} ] }] });
+ }
+
+ // Dynamic mutations to both shadow root and shadow host subtrees
+ // testing/web-platform/tests/css/css-scoping/shadow-assign-dynamic-001.html
+ async function assignSlotDynamic() {
+ await _dynamicShadowTest("assignSlotDynamic",
+ (container, host) => {
+ host.shadowRoot.appendChild(container.querySelector('.shadowtree').content.cloneNode(true));
+ host.appendChild(container.querySelector('.lighttree').content.cloneNode(true));
+ }, { SECTION: [{ SECTION: [{ name: "slot1"}, { name: "slot2" } ] }] });
+ }
+
+ // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-001.html
+ async function shadowFallbackDynamic_1() {
+ await _dynamicShadowTest("shadowFallbackDynamic_1",
+ (container, host) => {
+ host.firstElementChild.remove();
+ }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+ }
+
+ // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-002.html
+ async function shadowFallbackDynamic_2() {
+ await _dynamicShadowTest("shadowFallbackDynamic_2",
+ (container, host) => {
+ host.firstElementChild.removeAttribute("slot");
+ }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+ }
+
+ // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-003.html
+ async function shadowFallbackDynamic_3() {
+ await _dynamicShadowTest("shadowFallbackDynamic_3",
+ (container, host) => {
+ host.appendChild(container.querySelector(".lighttree").content.cloneNode(true));
+ }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+ }
+
+ // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-004.html
+ async function shadowFallbackDynamic_4() {
+ await _dynamicShadowTest("shadowFallbackDynamic_4",
+ (container, host) => {
+ host.shadowRoot.insertBefore(
+ container.querySelector(".moreshadowtree").
+ content.cloneNode(true), host.shadowRoot.firstChild);
+ }, { SECTION: [{ SECTION: [{ name: "slotparent2", children: [{ name: "green"} ] }, { name: "slotparent1" } ] }] });
+ }
+
+ // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-004.html
+ // This tests a case when the the slotted element would remain in the same accessible container
+ async function shadowFallbackDynamic_4_1() {
+ await _dynamicShadowTest("shadowFallbackDynamic_4_1",
+ (container, host) => {
+ host.shadowRoot.insertBefore(
+ container.querySelector(".moreshadowtree").
+ content.cloneNode(true), host.shadowRoot.firstChild);
+ }, { SECTION: [{ SECTION: [ { name: "green"}, { SEPARATOR: [] } ] }] });
+ }
+
+ // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-005.html
+ async function shadowFallbackDynamic_5() {
+ await _dynamicShadowTest("shadowFallbackDynamic_5",
+ (container, host) => {
+ host.firstElementChild.setAttribute("slot", "myotherslot");
+ }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+ }
+
+ // testing/web-platform/tests/css/css-scoping/shadow-reassign-dynamic-002.html
+ async function shadowReassignDynamic_2() {
+ await _dynamicShadowTest("shadowReassignDynamic_2",
+ (container, host) => {
+ host.shadowRoot.querySelector("slot").setAttribute("name", "myslot");
+ }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+ }
+
+ // testing/web-platform/tests/css/css-scoping/shadow-reassign-dynamic-003.html
+ async function shadowReassignDynamic_3() {
+ await _dynamicShadowTest("shadowReassignDynamic_3",
+ (container, host) => {
+ testAccessibleTree(container, { SECTION: [{ SECTION: [{ name: "green"}, { name: "red", children: [ { PUSHBUTTON: [] }]} ] }] });
+ host.shadowRoot.querySelector("slot[name]").removeAttribute("name");
+
+ }, { SECTION: [{ SECTION: [{ name: "green", children: [ { PUSHBUTTON: [] }]}, { name: "red"} ] }] },
+ [evt => evt.accessible.name == "green", evt => evt.accessible.name == "red"]);
+ }
+
+ // testing/web-platform/tests/css/css-scoping/shadow-reassign-dynamic-004.html
+ async function shadowReassignDynamic_4() {
+ await _dynamicShadowTest("shadowReassignDynamic_4",
+ (container, host) => {
+ host.shadowRoot.getElementById("slot").remove();
+ }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+ }
+
+ function shadowProcessInvalidation() {
+ testAccessibleTree("shadowProcessInvalidation",
+ { SECTION: [{
+ SECTION: [{
+ SECTION: [{ TEXT_LEAF: { name: "Hello "} },
+ { TEXT: [{ TEXT_LEAF: { name: "World"} }] },
+ { PUSHBUTTON: { name: "World"} }]
+ }]
+ }]
+ });
+ }
+
+ async function justAttachShadow() {
+ await _dynamicShadowTest("justAttachShadow",
+ (container, host) => {
+ host.attachShadow({ mode: "open" });
+ }, { SECTION: [{ SECTION: [] }] });
+ }
+
+ async function doTest() {
+ await attachFlatShadow();
+
+ await attachOneDeepShadow();
+
+ await changeSlotFlat();
+
+ await changeSlotOneDeep();
+
+ await changeSlotNested();
+
+ await changeSlotSingleChild();
+
+ await changeSlotNoShadow();
+
+ await assignSlotDynamic();
+
+ await shadowFallbackDynamic_1();
+
+ await shadowFallbackDynamic_2();
+
+ await shadowFallbackDynamic_3();
+
+ await shadowFallbackDynamic_4();
+
+ await shadowFallbackDynamic_4_1();
+
+ await shadowFallbackDynamic_5();
+
+ await shadowReassignDynamic_2();
+
+ await shadowReassignDynamic_3();
+
+ await shadowReassignDynamic_4();
+
+ shadowProcessInvalidation();
+
+ await justAttachShadow();
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="attachFlatShadow">
+ <template class="shadowtree">
+ <style>::slotted(div) { width: 100px; height: 100px }</style>
+ <slot name="myslot">FAIL</slot>
+ </template>
+ <section class="host">
+ <div style="background: green" aria-label="green"></div>
+ <div style="background: red" aria-label="red" slot="myslot"></div>
+ </section>
+ </div>
+
+ <div id="attachOneDeepShadow">
+ <template class="shadowtree">
+ <style>::slotted(div) { width: 100px; height: 100px }</style>
+ <div id="shadowdiv">
+ <slot name="myslot">FAIL</slot>
+ </div>
+ </template>
+ <section class="host">
+ <div style="background: green" aria-label="green"></div>
+ <div style="background: red" aria-label="red" slot="myslot"></div>
+ </section>
+ </div>
+
+ <div id="changeSlotFlat">
+ <template class="shadowtree">
+ <style>::slotted(div) { width: 100px; height: 100px }</style>
+ <slot name="myslot">FAIL</slot>
+ </template>
+ <section class="host">
+ <div class="green" style="background: green" aria-label="green"></div>
+ <div class="red" style="background: red" aria-label="red" slot="myslot"></div>
+ </section>
+ <script>
+ document.querySelector("#changeSlotFlat > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#changeSlotFlat > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="changeSlotOneDeep">
+ <template class="shadowtree">
+ <style>::slotted(div) { width: 100px; height: 100px }</style>
+ <div id="shadowdiv">
+ <slot name="myslot">FAIL</slot>
+ </div>
+ </template>
+ <section class="host">
+ <div class="green" style="background: green" aria-label="green"></div>
+ <div class="red" style="background: red" aria-label="red" slot="myslot"></div>
+ </section>
+ <script>
+ document.querySelector("#changeSlotOneDeep > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#changeSlotOneDeep > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="changeSlotNested">
+ <template class="shadowtree outer">
+ <div id="shadowdiv">
+ <slot name="myslot">FAIL</slot>
+ </div>
+ </template>
+ <template class="shadowtree inner">
+ <style>::slotted(div) { width: 100px; height: 100px }</style>
+ <slot>FAIL</slot>
+ </template>
+ <section class="host">
+ <div class="green" style="background: green" aria-label="green"></div>
+ <div class="red" style="background: red" aria-label="red" slot="myslot"></div>
+ </section>
+ <script>
+ (function foo() {
+ let outerShadow =
+ document.querySelector("#changeSlotNested > .host").
+ attachShadow({ mode: "open" });
+ outerShadow.appendChild(
+ document.querySelector("#changeSlotNested > .shadowtree.outer").
+ content.cloneNode(true));
+ let innerShadow =
+ outerShadow.querySelector("#shadowdiv").
+ attachShadow({ mode: "open" });
+ innerShadow.appendChild(
+ document.querySelector("#changeSlotNested > .shadowtree.inner").
+ content.cloneNode(true));
+ })();
+ </script>
+ </div>
+
+ <div id="changeSlotSingleChild">
+ <template class="shadowtree">
+ <style>::slotted(div) { width: 100px; height: 100px }</style>
+ <slot></slot>
+ </template>
+ <section class="host">
+ <div class="red" style="background: red" aria-label="red"></div>
+ </section>
+ <script>
+ document.querySelector("#changeSlotSingleChild > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#changeSlotSingleChild > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="changeSlotNoShadow">
+ <section class="host">
+ <div class="red" style="background: red; width: 100px; height: 100px;" aria-label="red"></div>
+ <div class="green" style="background: green; width: 100px; height: 100px;" aria-label="green"></div>
+ </section>
+ </div>
+
+ <div id="assignSlotDynamic">
+ <template class="shadowtree">
+ <style>::slotted(div) { width: 50px; height: 100px }</style>
+ <slot name="slot1">FAIL</slot>
+ <slot name="slot2">FAIL</slot>
+ </template>
+ <template class="lighttree">
+ <div aria-label="slot1" slot="slot1"></div>
+ <div aria-label="slot2" slot="slot2"></div>
+ </template>
+ <section class="host"></section>
+ <script>
+ document.querySelector("#assignSlotDynamic > .host").attachShadow({ mode: "open" });
+ </script>
+ </div>
+
+ <div id="shadowFallbackDynamic_1">
+ <template class="shadowtree">
+ <slot name="myslot">
+ <div aria-label="green" style="width: 100px; height: 100px; background: green"></div>
+ </slot>
+ </template>
+ <section class="host"><span slot="myslot">FAIL</span></section>
+ <script>
+ document.querySelector("#shadowFallbackDynamic_1 > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#shadowFallbackDynamic_1 > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="shadowFallbackDynamic_2">
+ <template class="shadowtree">
+ <slot name="myslot">
+ <div aria-label="green" style="width: 100px; height: 100px; background: green"></div>
+ </slot>
+ </template>
+ <section class="host"><span slot="myslot">FAIL</span></section>
+ <script>
+ document.querySelector("#shadowFallbackDynamic_2 > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#shadowFallbackDynamic_2 > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="shadowFallbackDynamic_3">
+ <template class="shadowtree">
+ <slot name="myslot">FAIL</slot>
+ </template>
+ <template class="lighttree">
+ <div aria-label="green" slot="myslot" style="width: 100px; height: 100px; background: green"></div>
+ </template>
+ <section class="host"></section>
+ <script>
+ document.querySelector("#shadowFallbackDynamic_3 > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#shadowFallbackDynamic_3 > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="shadowFallbackDynamic_4">
+ <template class="shadowtree">
+ <div aria-label="slotparent1"><slot name="myslot"></slot></div>
+ </template>
+ <template class="moreshadowtree">
+ <div aria-label="slotparent2"><slot name="myslot">FAIL</slot></div>
+ </template>
+ <section class="host">
+ <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div>
+ </section>
+ <script>
+ document.querySelector("#shadowFallbackDynamic_4 > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#shadowFallbackDynamic_4 > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="shadowFallbackDynamic_4_1">
+ <template class="shadowtree">
+ <hr>
+ <slot name="myslot"></slot>
+ </template>
+ <template class="moreshadowtree">
+ <slot name="myslot">FAIL</slot>
+ </template>
+ <section class="host">
+ <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div>
+ </section>
+ <script>
+ document.querySelector("#shadowFallbackDynamic_4_1 > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#shadowFallbackDynamic_4_1 > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="shadowFallbackDynamic_5">
+ <template class="shadowtree">
+ <slot name="myslot"></slot>
+ <slot name="myotherslot">FAIL</slot>
+ </template>
+ <section class="host">
+ <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div>
+ </section>
+ <script>
+ document.querySelector("#shadowFallbackDynamic_5 > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#shadowFallbackDynamic_5 > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="shadowReassignDynamic_2">
+ <template class="shadowtree">
+ <style>::slotted(div) { width: 100px; height: 100px }</style>
+ <slot>FAIL</slot>
+ </template>
+ <section class="host">
+ <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div>
+ </section>
+ <script>
+ document.querySelector("#shadowReassignDynamic_2 > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#shadowReassignDynamic_2 > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="shadowReassignDynamic_3">
+ <template class="shadowtree">
+ <div aria-label="green"><slot name="nomatch"></slot></div>
+ <div aria-label="red"><slot></slot></div>
+ </template>
+ <section class="host">
+ <div role="button"></div>
+ </section>
+ <script>
+ document.querySelector("#shadowReassignDynamic_3 > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#shadowReassignDynamic_3 > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="shadowReassignDynamic_4">
+ <template class="shadowtree">
+ <style>::slotted(div),div { width: 100px; height: 100px }</style>
+ <slot id="slot"></slot>
+ <slot>
+ <div aria-label="red" style="background: red"></div>
+ </slot>
+ </template>
+ <section class="host">
+ <div aria-label="green" style="background: green"></div>
+ </section>
+ <script>
+ document.querySelector("#shadowReassignDynamic_4 > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#shadowReassignDynamic_4 > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="shadowProcessInvalidation">
+ <template class="shadowtree">
+ <div id="shadowdiv">
+ <slot></slot>
+ </div>
+ </template>
+ <section class="host">Hello <span id="c">World</span><button aria-labelledby="c"></button></section>
+ <script>
+ document.querySelector("#shadowProcessInvalidation > .host")
+ .attachShadow({ mode: "open" })
+ .appendChild(document.querySelector("#shadowProcessInvalidation > .shadowtree").content.cloneNode(true));
+ </script>
+ </div>
+
+ <div id="justAttachShadow">
+ <section class="host">
+ <button></button>
+ </section>
+ </div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml b/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml
new file mode 100644
index 0000000000..ad8aebf812
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml
@@ -0,0 +1,131 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree hierarchy tests">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function setXULTreeView(aTreeID, aTreeView)
+ {
+ this.treeNode = getNode(aTreeID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.treeNode)
+ ];
+
+ this.invoke = function loadXULTree_invoke()
+ {
+ this.treeNode.view = aTreeView;
+ };
+
+ this.getID = function loadXULTree_getID()
+ {
+ return "Load XUL tree " + prettyName(aTreeID);
+ };
+ }
+
+ function removeTree(aID)
+ {
+ this.tree = getAccessible(aID);
+ this.lastItem = null;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, document)
+ ];
+
+ this.invoke = function invoke()
+ {
+ this.lastItem = getAccessible(aID).lastChild;
+ this.lastCell = this.lastItem.lastChild;
+ getNode(aID).remove();
+ };
+
+ this.check = function check(aEvent)
+ {
+ testIsDefunct(this.tree, aID);
+ testIsDefunct(this.lastItem, "last item of " + aID);
+ if (this.lastCell) {
+ testIsDefunct(this.lastCell, "last item cell of " + aID);
+ }
+ };
+
+ this.getID = function getID()
+ {
+ return "Remove tree from DOM";
+ };
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new setXULTreeView("tree", new nsTreeTreeView()));
+ gQueue.push(new removeTree("tree"));
+
+ gQueue.push(new setXULTreeView("treetable", new nsTreeTreeView()));
+ gQueue.push(new removeTree("treetable"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="treetable" flex="1">
+ <treecols>
+ <treecol id="col1" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/treeupdate/test_table.html b/accessible/tests/mochitest/treeupdate/test_table.html
new file mode 100644
index 0000000000..50fac91757
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_table.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Table update tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ function appendCaption(aTableID) {
+ this.invoke = function appendCaption_invoke() {
+ // append a caption, it should appear as a first element in the
+ // accessible tree.
+ var caption = document.createElement("caption");
+ caption.textContent = "table caption";
+ getNode(aTableID).appendChild(caption);
+ };
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aTableID),
+ ];
+
+ this.finalCheck = function appendCaption_finalCheck() {
+ var tree =
+ { TABLE: [
+ { CAPTION: [
+ { TEXT_LEAF: [] },
+ ] },
+ { ROW: [
+ { CELL: [ {TEXT_LEAF: [] }]},
+ { CELL: [ {TEXT_LEAF: [] }]},
+ ] },
+ ] };
+ testAccessibleTree(aTableID, tree);
+ };
+
+ this.getID = function appendCaption_getID() {
+ return "append caption";
+ };
+ }
+
+ function doTest() {
+ const gQueue = new eventQueue();
+ gQueue.push(new appendCaption("table"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table id="table">
+ <tr>
+ <td>cell1</td>
+ <td>cell2</td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_textleaf.html b/accessible/tests/mochitest/treeupdate/test_textleaf.html
new file mode 100644
index 0000000000..f0181dd754
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_textleaf.html
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test accessible recreation</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function textLeafUpdate(aID, aIsTextLeafLinkable) {
+ this.node = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.node.parentNode),
+ ];
+
+ this.finalCheck = function textLeafUpdate_finalCheck() {
+ var textLeaf = getAccessible(this.node).firstChild;
+ is(textLeaf.actionCount, (aIsTextLeafLinkable ? 1 : 0),
+ "Wrong action numbers!");
+ };
+ }
+
+ function setOnClickAttr(aID) {
+ var node = getNode(aID);
+ node.setAttribute("onclick", "alert(3);");
+ var textLeaf = getAccessible(node).firstChild;
+ is(textLeaf.actionCount, 1, "setOnClickAttr: wrong action numbers!");
+ }
+
+ function removeOnClickAttr(aID) {
+ var node = getNode(aID);
+ node.removeAttribute("onclick");
+ var textLeaf = getAccessible(node).firstChild;
+ is(textLeaf.actionCount, 0,
+ "removeOnClickAttr: wrong action numbers!");
+ }
+
+ function setOnClickNRoleAttrs(aID) {
+ this.__proto__ = new textLeafUpdate(aID, true);
+
+ this.invoke = function setOnClickAttr_invoke() {
+ this.node.setAttribute("role", "link");
+ this.node.setAttribute("onclick", "alert(3);");
+ };
+
+ this.getID = function setOnClickAttr_getID() {
+ return "make " + prettyName(aID) + " linkable";
+ };
+ }
+
+ function removeTextData(aID, aRole) {
+ this.containerNode = getNode(aID);
+ this.textNode = this.containerNode.firstChild;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.containerNode),
+ ];
+
+ this.invoke = function removeTextData_invoke() {
+ var tree = {
+ role: aRole,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "text",
+ },
+ ],
+ };
+ testAccessibleTree(this.containerNode, tree);
+
+ this.textNode.data = "";
+ };
+
+ this.finalCheck = function removeTextData_finalCheck() {
+ var tree = {
+ role: aRole,
+ children: [],
+ };
+ testAccessibleTree(this.containerNode, tree);
+ };
+
+ this.getID = function removeTextData_finalCheck() {
+ return "remove text data of text node inside '" + aID + "'.";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest() {
+ // adds onclick on element, text leaf should inherit its action
+ setOnClickAttr("div");
+ // remove onclick attribute, text leaf shouldn't have any action
+ removeOnClickAttr("div");
+
+ // Call rest of event tests.
+ gQueue = new eventQueue();
+
+ // set onclick attribute making span accessible, it's inserted into tree
+ // and adopts text leaf accessible, text leaf should have an action
+ gQueue.push(new setOnClickNRoleAttrs("span"));
+
+ // text data removal of text node should remove its text accessible
+ gQueue.push(new removeTextData("p", ROLE_PARAGRAPH));
+ gQueue.push(new removeTextData("pre", ROLE_TEXT_CONTAINER));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Clean up the code of accessible initialization and binding to the tree"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=545465">
+ Mozilla Bug 545465
+ </a>
+ <a target="_blank"
+ title="Make sure accessible tree is correct when rendered text is changed"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652">
+ Mozilla Bug 625652
+ </a>
+ <a target="_blank"
+ title="Remove text accesible getting no text inside a preformatted area"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=706335">
+ Mozilla Bug 706335
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="container">
+ <div id="div">div</div>
+ <span id="span">span</span>
+ </div>
+
+ <p id="p">text</p>
+ <pre id="pre">text</pre>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_tooltip.xhtml b/accessible/tests/mochitest/treeupdate/test_tooltip.xhtml
new file mode 100644
index 0000000000..dba83b2267
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_tooltip.xhtml
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tooltip test">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../promisified-events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ async function doTest() {
+ let tooltip = document.getElementById("tooltip");
+
+ testAccessibleTree("tooltip-container", { GROUPING: [
+ ] });
+
+ let shown = waitForEvent(EVENT_SHOW, tooltip);
+ tooltip.openPopup();
+ await shown;
+
+ testAccessibleTree("tooltip-container",
+ { GROUPING: [
+ { TOOLTIP: [] },
+ { STATICTEXT: [] },
+ ] });
+
+ let hidden = waitForEvent(EVENT_HIDE, tooltip);
+ tooltip.hidePopup();
+ await hidden;
+
+ testAccessibleTree("tooltip-container", { GROUPING: [] });
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1652211"
+ title="Added anonymous tooltip to mochitest docs messes with text">
+ Bug 1652211
+ </a>
+ <br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1" role="group" id="tooltip-container">
+ <tooltip id="tooltip">
+ <description class="tooltip-label" value="hello world"/>
+ </tooltip>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/treeupdate/test_visibility.html b/accessible/tests/mochitest/treeupdate/test_visibility.html
new file mode 100644
index 0000000000..4107832b3e
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_visibility.html
@@ -0,0 +1,411 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Style visibility tree update test</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ /**
+ * Hide parent while child stays visible.
+ */
+ function test1(aContainerID, aParentID, aChildID) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode(aParentID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID)),
+ ];
+
+ this.invoke = function invoke() {
+ var tree =
+ { SECTION: [
+ { SECTION: [
+ { SECTION: [
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aParentID).style.visibility = "hidden";
+ };
+
+ this.finalCheck = function finalCheck() {
+ var tree =
+ { SECTION: [
+ { SECTION: [
+ { TEXT_LEAF: [] },
+ ] },
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ };
+
+ this.getID = function getID() {
+ return "hide parent while child stays visible";
+ };
+ }
+
+ /**
+ * Hide grand parent while its children stay visible.
+ */
+ function test2(aContainerID, aGrandParentID, aChildID, aChild2ID) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode(aGrandParentID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChild2ID)),
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID)),
+ ];
+
+ this.invoke = function invoke() {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // grand parent
+ { SECTION: [
+ { SECTION: [ // child
+ { TEXT_LEAF: [] },
+ ] },
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aGrandParentID).style.visibility = "hidden";
+ };
+
+ this.finalCheck = function finalCheck() {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // child
+ { TEXT_LEAF: [] },
+ ] },
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] },
+ ] },
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ };
+
+ this.getID = function getID() {
+ return "hide grand parent while its children stay visible";
+ };
+ }
+
+ /**
+ * Change container style, hide parents while their children stay visible.
+ */
+ function test3(aContainerID, aParentID, aParent2ID, aChildID, aChild2ID) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode(aParentID)),
+ new invokerChecker(EVENT_HIDE, getNode(aParent2ID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChild2ID)),
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID)),
+ ];
+
+ this.invoke = function invoke() {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // parent
+ { SECTION: [ // child
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ { SECTION: [ // parent2
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aContainerID).style.color = "red";
+ getNode(aParentID).style.visibility = "hidden";
+ getNode(aParent2ID).style.visibility = "hidden";
+ };
+
+ this.finalCheck = function finalCheck() {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // child
+ { TEXT_LEAF: [] },
+ ] },
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] },
+ ] },
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ };
+
+ this.getID = function getID() {
+ return "change container style, hide parents while their children stay visible";
+ };
+ }
+
+ /**
+ * Change container style and make visible child inside the table.
+ */
+ function test4(aContainerID, aChildID) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+ new invokerChecker(EVENT_REORDER, getNode(aChildID).parentNode),
+ ];
+
+ this.invoke = function invoke() {
+ var tree =
+ { SECTION: [
+ { TABLE: [
+ { ROW: [
+ { CELL: [ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aContainerID).style.color = "red";
+ getNode(aChildID).style.visibility = "visible";
+ };
+
+ this.finalCheck = function finalCheck() {
+ var tree =
+ { SECTION: [
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { SECTION: [
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ };
+
+ this.getID = function getID() {
+ return "change container style, make visible child insdie the table";
+ };
+ }
+
+ /**
+ * Hide subcontainer while child inside the table stays visible.
+ */
+ function test5(aContainerID, aSubContainerID, aChildID) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode(aSubContainerID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID)),
+ ];
+
+ this.invoke = function invoke() {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // subcontainer
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { SECTION: [ // child
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] },
+ ] },
+ ] },
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aSubContainerID).style.visibility = "hidden";
+ };
+
+ this.finalCheck = function finalCheck() {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // child
+ { TEXT_LEAF: [] },
+ ] },
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ };
+
+ this.getID = function getID() {
+ return "hide subcontainer while child inside the table stays visible";
+ };
+ }
+
+ /**
+ * Hide subcontainer while its child and child inside the nested table stays visible.
+ */
+ function test6(aContainerID, aSubContainerID, aChildID, aChild2ID) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode(aSubContainerID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChild2ID)),
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID)),
+ ];
+
+ this.invoke = function invoke() {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // subcontainer
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TABLE: [ // nested table
+ { ROW: [
+ { CELL: [
+ { SECTION: [ // child
+ { TEXT_LEAF: [] } ]} ]} ]} ]} ]} ]} ]},
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] } ]} ]} ]};
+
+ testAccessibleTree(aContainerID, tree);
+
+ // invoke
+ getNode(aSubContainerID).style.visibility = "hidden";
+ };
+
+ this.finalCheck = function finalCheck() {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // child
+ { TEXT_LEAF: [] } ]},
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] } ]} ]};
+
+ testAccessibleTree(aContainerID, tree);
+ };
+
+ this.getID = function getID() {
+ return "hide subcontainer while its child and child inside the nested table stays visible";
+ };
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new test1("t1_container", "t1_parent", "t1_child"));
+ gQueue.push(new test2("t2_container", "t2_grandparent", "t2_child", "t2_child2"));
+ gQueue.push(new test3("t3_container", "t3_parent", "t3_parent2", "t3_child", "t3_child2"));
+ gQueue.push(new test4("t4_container", "t4_child"));
+ gQueue.push(new test5("t5_container", "t5_subcontainer", "t5_child"));
+ gQueue.push(new test6("t6_container", "t6_subcontainer", "t6_child", "t6_child2"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Develop a way to handle visibility style"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=606125">
+ Mozilla Bug 606125
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- hide parent while child stays visible -->
+ <div id="t1_container">
+ <div id="t1_parent">
+ <div id="t1_child" style="visibility: visible">text</div>
+ </div>
+ </div>
+
+ <!-- hide grandparent while its children stay visible -->
+ <div id="t2_container">
+ <div id="t2_grandparent">
+ <div id="t2_parent">
+ <div id="t2_child" style="visibility: visible">text</div>
+ <div id="t2_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- change container style, hide parents while their children stay visible -->
+ <div id="t3_container">
+ <div id="t3_parent">
+ <div id="t3_child" style="visibility: visible">text</div>
+ </div>
+ <div id="t3_parent2">
+ <div id="t3_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+
+ <!-- change container style, show child inside the table -->
+ <div id="t4_container">
+ <table>
+ <tr>
+ <td>
+ <div id="t4_child" style="visibility: hidden;">text</div>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ <!-- hide subcontainer while child inside the table stays visible -->
+ <div id="t5_container">
+ <div id="t5_subcontainer">
+ <table>
+ <tr>
+ <td>
+ <div id="t5_child" style="visibility: visible;">text</div>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <!-- hide subcontainer while its child and child inside the nested table stays visible -->
+ <div id="t6_container">
+ <div id="t6_subcontainer">
+ <table>
+ <tr>
+ <td>
+ <table>
+ <tr>
+ <td>
+ <div id="t6_child" style="visibility: visible;">text</div>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <div id="t6_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_whitespace.html b/accessible/tests/mochitest/treeupdate/test_whitespace.html
new file mode 100644
index 0000000000..ebb199cfbe
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_whitespace.html
@@ -0,0 +1,200 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Whitespace text accessible creation/destruction</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ /**
+ * Middle image accessible removal results in text accessible removal.
+ *
+ * Before:
+ * DOM: whitespace img1 whitespace img2 whitespace img3 whitespace,
+ * a11y: img1 whitespace img2 whitespace img3
+ * After:
+ * DOM: whitespace img1 whitespace whitespace img3 whitespace,
+ * a11y: img1 whitespace img3
+ */
+ function removeImg() {
+ this.containerNode = getNode("container1");
+ this.imgNode = getNode("img1");
+ this.img = getAccessible(this.imgNode);
+ this.text = this.img.nextSibling;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.img),
+ new invokerChecker(EVENT_HIDE, this.text),
+ new invokerChecker(EVENT_REORDER, this.containerNode),
+ ];
+
+ this.finalCheck = function textLeafUpdate_finalCheck() {
+ var tree =
+ { SECTION: [
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] },
+ ] };
+
+ testAccessibleTree(this.containerNode, tree);
+ };
+
+ this.invoke = function setOnClickAttr_invoke() {
+ var tree =
+ { SECTION: [
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] },
+ ] };
+
+ testAccessibleTree(this.containerNode, tree);
+
+ this.containerNode.removeChild(this.imgNode);
+ };
+
+ this.getID = function setOnClickAttr_getID() {
+ return "remove middle img";
+ };
+ }
+
+ /**
+ * Append image making the whitespace visible and thus accessible.
+ * Note: images and whitespaces are on different leves of accessible trees,
+ * so that image container accessible update doesn't update the tree
+ * of whitespace container.
+ *
+ * Before:
+ * DOM: whitespace emptylink whitespace linkwithimg whitespace
+ * a11y: emptylink linkwithimg
+ * After:
+ * DOM: whitespace linkwithimg whitespace linkwithimg whitespace
+ * a11y: linkwithimg whitespace linkwithimg
+ */
+ function insertImg() {
+ this.containerNode = getNode("container2");
+ this.topNode = this.containerNode.parentNode;
+ this.textNode = this.containerNode.nextSibling;
+ this.imgNode = document.createElement("img");
+ this.imgNode.setAttribute("src", "../moz.png");
+
+ this.eventSeq = [
+ new asyncInvokerChecker(EVENT_SHOW, getAccessible, this.textNode),
+ new asyncInvokerChecker(EVENT_SHOW, getAccessible, this.imgNode),
+ new orderChecker(),
+ new invokerChecker(EVENT_REORDER, this.topNode),
+ ];
+
+ this.invoke = function insertImg_invoke() {
+ var tree =
+ { SECTION: [
+ { LINK: [] },
+ { LINK: [
+ { GRAPHIC: [] },
+ ] },
+ ] };
+
+ testAccessibleTree(this.topNode, tree);
+
+ this.containerNode.appendChild(this.imgNode);
+ };
+
+ this.finalCheck = function insertImg_finalCheck() {
+ var tree =
+ { SECTION: [
+ { LINK: [
+ { GRAPHIC: [ ] },
+ ] },
+ { TEXT_LEAF: [ ] },
+ { LINK: [
+ { GRAPHIC: [ ] },
+ ] },
+ ] };
+
+ testAccessibleTree(this.topNode, tree);
+ };
+
+ this.getID = function appendImg_getID() {
+ return "insert img into internal container";
+ };
+ }
+
+ function dontCreateMapWhiteSpace() {
+ const tree = { SECTION: [ { role: ROLE_TEXT_LEAF, name: "x" } ] };
+ testAccessibleTree("container3", tree);
+
+ getNode("c3_inner").style.textAlign = "center";
+ document.body.offsetTop; // Flush layout.
+ window.windowUtils.advanceTimeAndRefresh(100);
+
+ testAccessibleTree("container3", tree);
+ window.windowUtils.restoreNormalRefresh();
+ }
+
+ // //////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest() {
+ dontCreateMapWhiteSpace();
+
+ gQueue = new eventQueue();
+
+ gQueue.push(new removeImg());
+ gQueue.push(new insertImg());
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Make sure accessible tree is correct when rendered text is changed"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652">
+ Mozilla Bug 625652
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Whitespace between the div and img tags will be inconsistent depending
+ on the image cache state and what optimizations layout was able to
+ apply. -->
+ <div id="container1"><img src="../moz.png"> <img id="img1" src="../moz.png"> <img src="../moz.png"></div>
+ <div><a id="container2"></a> <a><img src="../moz.png"></a></div>
+
+ <div id="container3">
+ <div id="c3_inner" role="presentation">
+ x<map> </map>
+ </div>
+ </div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeview.css b/accessible/tests/mochitest/treeview.css
new file mode 100644
index 0000000000..462f560d6f
--- /dev/null
+++ b/accessible/tests/mochitest/treeview.css
@@ -0,0 +1,15 @@
+treechildren::-moz-tree-checkbox(checked) {
+ list-style-image: url("chrome://global/skin/icons/check.svg");
+}
+
+treechildren::-moz-tree-image(cyclerState1) {
+ list-style-type: disc;
+}
+
+treechildren::-moz-tree-image(cyclerState2) {
+ list-style-type: circle;
+}
+
+treechildren::-moz-tree-image(cyclerState3) {
+ list-style-type: square;
+}
diff --git a/accessible/tests/mochitest/treeview.js b/accessible/tests/mochitest/treeview.js
new file mode 100644
index 0000000000..5dc3b595d8
--- /dev/null
+++ b/accessible/tests/mochitest/treeview.js
@@ -0,0 +1,273 @@
+/* import-globals-from common.js */
+/* import-globals-from events.js */
+
+/**
+ * Helper method to start a single XUL tree test.
+ */
+function loadXULTreeAndDoTest(aDoTestFunc, aTreeID, aTreeView) {
+ var doTestFunc = aDoTestFunc ? aDoTestFunc : gXULTreeLoadContext.doTestFunc;
+ var treeID = aTreeID ? aTreeID : gXULTreeLoadContext.treeID;
+ var treeView = aTreeView ? aTreeView : gXULTreeLoadContext.treeView;
+
+ let treeNode = getNode(treeID);
+
+ gXULTreeLoadContext.queue = new eventQueue();
+ gXULTreeLoadContext.queue.push({
+ treeNode,
+
+ eventSeq: [new invokerChecker(EVENT_REORDER, treeNode)],
+
+ invoke() {
+ this.treeNode.view = treeView;
+ },
+
+ getID() {
+ return "Load XUL tree " + prettyName(treeID);
+ },
+ });
+ gXULTreeLoadContext.queue.onFinish = function () {
+ SimpleTest.executeSoon(doTestFunc);
+ return DO_NOT_FINISH_TEST;
+ };
+ gXULTreeLoadContext.queue.invoke();
+}
+
+/**
+ * Analogy of addA11yLoadEvent, nice helper to load XUL tree and start the test.
+ */
+function addA11yXULTreeLoadEvent(aDoTestFunc, aTreeID, aTreeView) {
+ gXULTreeLoadContext.doTestFunc = aDoTestFunc;
+ gXULTreeLoadContext.treeID = aTreeID;
+ gXULTreeLoadContext.treeView = aTreeView;
+
+ addA11yLoadEvent(loadXULTreeAndDoTest);
+}
+
+function nsTableTreeView(aRowCount) {
+ this.__proto__ = new nsTreeView();
+
+ for (var idx = 0; idx < aRowCount; idx++) {
+ this.mData.push(new treeItem("row" + String(idx) + "_"));
+ }
+}
+
+function nsTreeTreeView() {
+ this.__proto__ = new nsTreeView();
+
+ this.mData = [
+ new treeItem("row1"),
+ new treeItem("row2_", true, [
+ new treeItem("row2.1_"),
+ new treeItem("row2.2_"),
+ ]),
+ new treeItem("row3_", false, [
+ new treeItem("row3.1_"),
+ new treeItem("row3.2_"),
+ ]),
+ new treeItem("row4"),
+ ];
+}
+
+function nsTreeView() {
+ this.mTree = null;
+ this.mData = [];
+}
+
+nsTreeView.prototype = {
+ // ////////////////////////////////////////////////////////////////////////////
+ // nsITreeView implementation
+
+ get rowCount() {
+ return this.getRowCountIntl(this.mData);
+ },
+ setTree: function setTree(aTree) {
+ this.mTree = aTree;
+ },
+ getCellText: function getCellText(aRow, aCol) {
+ var data = this.getDataForIndex(aRow);
+ if (aCol.id in data.colsText) {
+ return data.colsText[aCol.id];
+ }
+
+ return data.text + aCol.id;
+ },
+ getCellValue: function getCellValue(aRow, aCol) {
+ var data = this.getDataForIndex(aRow);
+ return data.value;
+ },
+ getRowProperties: function getRowProperties(aIndex) {
+ return "";
+ },
+ getCellProperties: function getCellProperties(aIndex, aCol) {
+ if (!aCol.cycler) {
+ return "";
+ }
+
+ var data = this.getDataForIndex(aIndex);
+ return this.mCyclerStates[data.cyclerState];
+ },
+ getColumnProperties: function getColumnProperties(aCol) {
+ return "";
+ },
+ getParentIndex: function getParentIndex(aRowIndex) {
+ var info = this.getInfoByIndex(aRowIndex);
+ return info.parentIndex;
+ },
+ hasNextSibling: function hasNextSibling(aRowIndex, aAfterIndex) {},
+ getLevel: function getLevel(aIndex) {
+ var info = this.getInfoByIndex(aIndex);
+ return info.level;
+ },
+ getImageSrc: function getImageSrc(aRow, aCol) {},
+ isContainer: function isContainer(aIndex) {
+ var data = this.getDataForIndex(aIndex);
+ return data.open != undefined;
+ },
+ isContainerOpen: function isContainerOpen(aIndex) {
+ var data = this.getDataForIndex(aIndex);
+ return data.open;
+ },
+ isContainerEmpty: function isContainerEmpty(aIndex) {
+ var data = this.getDataForIndex(aIndex);
+ return data.open == undefined;
+ },
+ isSeparator: function isSeparator(aIndex) {},
+ isSorted: function isSorted() {},
+ toggleOpenState: function toggleOpenState(aIndex) {
+ var data = this.getDataForIndex(aIndex);
+
+ data.open = !data.open;
+ var rowCount = this.getRowCountIntl(data.children);
+
+ if (data.open) {
+ this.mTree.rowCountChanged(aIndex + 1, rowCount);
+ } else {
+ this.mTree.rowCountChanged(aIndex + 1, -rowCount);
+ }
+ },
+ selectionChanged: function selectionChanged() {},
+ cycleHeader: function cycleHeader(aCol) {},
+ cycleCell: function cycleCell(aRow, aCol) {
+ var data = this.getDataForIndex(aRow);
+ data.cyclerState = (data.cyclerState + 1) % 3;
+
+ this.mTree.invalidateCell(aRow, aCol);
+ },
+ isEditable: function isEditable(aRow, aCol) {
+ return true;
+ },
+ setCellText: function setCellText(aRow, aCol, aValue) {
+ var data = this.getDataForIndex(aRow);
+ data.colsText[aCol.id] = aValue;
+ },
+ setCellValue: function setCellValue(aRow, aCol, aValue) {
+ var data = this.getDataForIndex(aRow);
+ data.value = aValue;
+
+ this.mTree.invalidateCell(aRow, aCol);
+ },
+
+ // ////////////////////////////////////////////////////////////////////////////
+ // public implementation
+
+ appendItem: function appendItem(aText) {
+ this.mData.push(new treeItem(aText));
+ },
+
+ // ////////////////////////////////////////////////////////////////////////////
+ // private implementation
+
+ getDataForIndex: function getDataForIndex(aRowIndex) {
+ return this.getInfoByIndex(aRowIndex).data;
+ },
+
+ getInfoByIndex: function getInfoByIndex(aRowIndex) {
+ var info = {
+ data: null,
+ parentIndex: -1,
+ level: 0,
+ index: -1,
+ };
+
+ this.getInfoByIndexIntl(aRowIndex, info, this.mData, 0);
+ return info;
+ },
+
+ getRowCountIntl: function getRowCountIntl(aChildren) {
+ var rowCount = 0;
+ for (var childIdx = 0; childIdx < aChildren.length; childIdx++) {
+ rowCount++;
+
+ var data = aChildren[childIdx];
+ if (data.open) {
+ rowCount += this.getRowCountIntl(data.children);
+ }
+ }
+
+ return rowCount;
+ },
+
+ getInfoByIndexIntl: function getInfoByIndexIntl(
+ aRowIdx,
+ aInfo,
+ aChildren,
+ aLevel
+ ) {
+ var rowIdx = aRowIdx;
+ for (var childIdx = 0; childIdx < aChildren.length; childIdx++) {
+ var data = aChildren[childIdx];
+
+ aInfo.index++;
+
+ if (rowIdx == 0) {
+ aInfo.data = data;
+ aInfo.level = aLevel;
+ return -1;
+ }
+
+ if (data.open) {
+ var parentIdx = aInfo.index;
+ rowIdx = this.getInfoByIndexIntl(
+ rowIdx - 1,
+ aInfo,
+ data.children,
+ aLevel + 1
+ );
+
+ if (rowIdx == -1) {
+ if (aInfo.parentIndex == -1) {
+ aInfo.parentIndex = parentIdx;
+ }
+ return 0;
+ }
+ } else {
+ rowIdx--;
+ }
+ }
+
+ return rowIdx;
+ },
+
+ mCyclerStates: ["cyclerState1", "cyclerState2", "cyclerState3"],
+};
+
+function treeItem(aText, aOpen, aChildren) {
+ this.text = aText;
+ this.colsText = {};
+ this.open = aOpen;
+ this.value = "true";
+ this.cyclerState = 0;
+ if (aChildren) {
+ this.children = aChildren;
+ }
+}
+
+/**
+ * Used in conjunction with loadXULTreeAndDoTest and addA11yXULTreeLoadEvent.
+ */
+var gXULTreeLoadContext = {
+ doTestFunc: null,
+ treeID: null,
+ treeView: null,
+ queue: null,
+};
diff --git a/accessible/tests/mochitest/value.js b/accessible/tests/mochitest/value.js
new file mode 100644
index 0000000000..379403ecaf
--- /dev/null
+++ b/accessible/tests/mochitest/value.js
@@ -0,0 +1,52 @@
+/* import-globals-from common.js */
+
+// //////////////////////////////////////////////////////////////////////////////
+// Public methods
+
+/**
+ * Tests nsIAccessibleValue interface.
+ *
+ * @param aAccOrElmOrId [in] identifier of accessible
+ * @param aValue [in] accessible value (nsIAccessible::value)
+ * @param aCurrValue [in] current value (nsIAccessibleValue::currentValue)
+ * @param aMinValue [in] minimum value (nsIAccessibleValue::minimumValue)
+ * @param aMaxValue [in] maximumn value (nsIAccessibleValue::maximumValue)
+ * @param aMinIncr [in] minimum increment value
+ * (nsIAccessibleValue::minimumIncrement)
+ */
+function testValue(
+ aAccOrElmOrId,
+ aValue,
+ aCurrValue,
+ aMinValue,
+ aMaxValue,
+ aMinIncr
+) {
+ var acc = getAccessible(aAccOrElmOrId, [nsIAccessibleValue]);
+ if (!acc) {
+ return;
+ }
+
+ is(acc.value, aValue, "Wrong value of " + prettyName(aAccOrElmOrId));
+
+ is(
+ acc.currentValue,
+ aCurrValue,
+ "Wrong current value of " + prettyName(aAccOrElmOrId)
+ );
+ is(
+ acc.minimumValue,
+ aMinValue,
+ "Wrong minimum value of " + prettyName(aAccOrElmOrId)
+ );
+ is(
+ acc.maximumValue,
+ aMaxValue,
+ "Wrong maximum value of " + prettyName(aAccOrElmOrId)
+ );
+ is(
+ acc.minimumIncrement,
+ aMinIncr,
+ "Wrong minimum increment value of " + prettyName(aAccOrElmOrId)
+ );
+}
diff --git a/accessible/tests/mochitest/value/a11y.ini b/accessible/tests/mochitest/value/a11y.ini
new file mode 100644
index 0000000000..8bd39474a8
--- /dev/null
+++ b/accessible/tests/mochitest/value/a11y.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_ariavalue.html]
+[test_datetime.html]
+[test_general.html]
+[test_number.html]
+[test_progress.html]
+[test_range.html]
+[test_meter.html]
diff --git a/accessible/tests/mochitest/value/test_ariavalue.html b/accessible/tests/mochitest/value/test_ariavalue.html
new file mode 100644
index 0000000000..bdb62a866d
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_ariavalue.html
@@ -0,0 +1,85 @@
+<html>
+
+<head>
+ <title>nsIAccessible value testing for implicit aria-value* attributes</title>
+
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../value.js"></script>
+
+ <script src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ for (const role of ["slider", "scrollbar"]) {
+ testValue(`${role}_default`, "50", 50, 0, 100, 0);
+ testValue(`${role}_min1max50`, "25.5", 25.5, 1, 50, 0);
+ testValue(`${role}_max200`, "100", 100, 0, 200, 0);
+ testValue(`${role}_min10`, "55", 55, 10, 100, 0);
+ testValue(`${role}_vt`, "juice", 50, 0, 100, 0);
+ testValue(`${role}_vn`, "6", 6, 0, 100, 0);
+ testValue(`${role}_vtvn`, "juice", 6, 0, 100, 0);
+ }
+
+ testValue("spinbutton_default", "", 0, 0, 0, 0);
+ testValue("spinbutton_min1max50", "", 0, 1, 50, 0);
+ testValue("spinbutton_max200", "", 0, 0, 200, 0);
+ testValue("spinbutton_min10", "", 0, 10, 0, 0);
+ testValue("spinbutton_vt", "juice", 0, 0, 0, 0);
+ testValue("spinbutton_vn", "6", 6, 0, 0, 0);
+ testValue("spinbutton_vtvn", "juice", 6, 0, 0, 0);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1357071"
+ title="Add support for implicit values for aria-value* attributes for scrollbar and slider roles">
+ Bug 1357071
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+
+ <!-- ARIA sliders -->
+ <div id="slider_default" role="slider">vanilla slider</div>
+ <div id="slider_min1max50" role="slider" aria-valuemin="1" aria-valuemax="50">banana slider</div>
+ <div id="slider_max200" role="slider" aria-valuemax="200">cherry slider</div>
+ <div id="slider_min10" role="slider" aria-valuemin="10">strawberry slider</div>
+ <div id="slider_vt" role="slider" aria-valuetext="juice">orange slider</div>
+ <div id="slider_vn" role="slider" aria-valuenow="6">chocolate slider</div>
+ <div id="slider_vtvn" role="slider" aria-valuetext="juice" aria-valuenow="6">apple slider</div>
+
+ <!-- ARIA scrollbars -->
+ <div id="scrollbar_default" role="scrollbar">vanilla scrollbar</div>
+ <div id="scrollbar_min1max50" role="scrollbar" aria-valuemin="1" aria-valuemax="50">banana scrollbar</div>
+ <div id="scrollbar_max200" role="scrollbar" aria-valuemax="200">cherry scrollbar</div>
+ <div id="scrollbar_min10" role="scrollbar" aria-valuemin="10">strawberry scrollbar</div>
+ <div id="scrollbar_vt" role="scrollbar" aria-valuetext="juice">orange scrollbar</div>
+ <div id="scrollbar_vn" role="scrollbar" aria-valuenow="6">chocolate scrollbar</div>
+ <div id="scrollbar_vtvn" role="scrollbar" aria-valuetext="juice" aria-valuenow="6">apple scrollbar</div>
+
+ <!-- ARIA spinbuttons -->
+ <div id="spinbutton_default" role="spinbutton">vanilla spinbutton</div>
+ <div id="spinbutton_min1max50" role="spinbutton" aria-valuemin="1" aria-valuemax="50">banana spinbutton</div>
+ <div id="spinbutton_max200" role="spinbutton" aria-valuemax="200">cherry spinbutton</div>
+ <div id="spinbutton_min10" role="spinbutton" aria-valuemin="10">strawberry spinbutton</div>
+ <div id="spinbutton_vt" role="spinbutton" aria-valuetext="juice">orange spinbutton</div>
+ <div id="spinbutton_vn" role="spinbutton" aria-valuenow="6">chocolate spinbutton</div>
+ <div id="spinbutton_vtvn" role="spinbutton" aria-valuetext="juice" aria-valuenow="6">apple spinbutton</div>
+</body>
+
+</html>
diff --git a/accessible/tests/mochitest/value/test_datetime.html b/accessible/tests/mochitest/value/test_datetime.html
new file mode 100644
index 0000000000..e03356ac1a
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_datetime.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+<head>
+ <title>nsIAccessible value testing for datetime-local input element</title>
+ <link rel="stylesheet"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script src="../common.js"></script>
+ <script src="../promisified-events.js"></script>
+
+ <script>
+ async function doTest() {
+ const dateTimeNode = getNode("datetime");
+ const dateTime = getAccessible(dateTimeNode);
+ // We assume en-US for testing. However, the OS date format might
+ // override the en-US date format.
+ const monthFirst = dateTime.getChildAt(0).name == "Month";
+ const month = dateTime.getChildAt(monthFirst ? 0 : 2);
+ const day = dateTime.getChildAt(monthFirst ? 2 : 0);
+ const year = dateTime.getChildAt(4);
+ const hour = dateTime.getChildAt(6);
+ const minute = dateTime.getChildAt(8);
+ const amPm = dateTime.getChildAt(10);
+
+ // We don't use testValue() because it also checks numeric value, but
+ // we don't support numeric value here because it isn't useful.
+ function assertIsClear() {
+ is(year.value, "");
+ is(month.value, "");
+ is(day.value, "");
+ is(hour.value, "");
+ is(minute.value, "");
+ // Unlike the numeric fields, amPm is a textbox. Since textboxes take
+ // their a11y value from their text content and "--" is set as the text
+ // content, the a11y value is "--".
+ is(amPm.value, "--");
+ }
+
+ info("Checking that input is initially clear");
+ assertIsClear();
+
+ // The container doesn't notify of value changes, so we wait for a value
+ // change on one of the fields to know when it's updated.
+ info("Setting value");
+ let changed = waitForEvent(EVENT_TEXT_VALUE_CHANGE, month);
+ dateTimeNode.value = "2000-01-02T03:04";
+ await changed;
+ is(year.value, "2000");
+ is(month.value, "01");
+ is(day.value, "02");
+ is(hour.value, "03");
+ is(minute.value, "04");
+ // Again, the OS date format might override, so we might get "am" instead
+ // of "AM".
+ is(amPm.value.toLowerCase(), "am");
+
+ info("Clearing value");
+ changed = waitForEvent(EVENT_TEXT_VALUE_CHANGE, month);
+ dateTimeNode.value = "";
+ await changed;
+ assertIsClear();
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <input type="datetime-local" id="datetime">
+</body>
+</html>
diff --git a/accessible/tests/mochitest/value/test_general.html b/accessible/tests/mochitest/value/test_general.html
new file mode 100644
index 0000000000..862c5254c0
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_general.html
@@ -0,0 +1,159 @@
+<html>
+
+<head>
+ <title>nsIAccessible value testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style type="text/css">
+ .offscreen {
+ position: absolute;
+ left: -5000px;
+ top: -5000px;
+ height: 100px;
+ width: 100px;
+ }
+ </style>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+
+ <script src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ function testValue(aID, aValue) {
+ var acc = getAccessible(aID);
+ if (!acc)
+ return;
+ is(acc.value, aValue, "Wrong value for " + aID + "!");
+ }
+
+ var href = getRootDirectory(window.location.href) + "foo";
+
+ // roles that can't live as HTMLLinkAccessibles
+ testValue("aria_menuitem_link", "");
+ testValue("aria_button_link", "");
+ testValue("aria_checkbox_link", "");
+ testValue("aria_application_link", "");
+ testValue("aria_main_link", "");
+ testValue("aria_navigation_link", "");
+
+ // roles that can live as HTMLLinkAccessibles
+ testValue("aria_link_link", href);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA textboxes
+
+ testValue("aria_textbox1", "helo");
+ // Textbox containing list.
+ testValue("aria_textbox2", "1. test");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA comboboxes
+
+ // aria-activedescendant defines a current item the value is computed from
+ testValue("aria_combobox1", kDiscBulletText + "Zoom");
+
+ // aria-selected defines a selected item the value is computed from,
+ // list control is pointed by aria-owns relation.
+ testValue("aria_combobox2", kDiscBulletText + "Zoom");
+
+ // aria-selected defines a selected item the value is computed from,
+ // list control is a child of combobox.
+ testValue("aria_combobox3", kDiscBulletText + "2");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML controls
+ testValue("combobox1", "item1");
+ testValue("combobox2", "item2");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=494807"
+ title="Do not expose a11y info specific to hyperlinks when role is overridden using ARIA">
+ Bug 494807
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=819273"
+ title="ARIA combobox should have accessible value">
+ Bug 819273
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=887250"
+ title="ARIA textbox role doesn't expose value">
+ Bug 887250
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+
+ <a id="aria_menuitem_link" role="menuitem" href="foo">menuitem</a>
+ <a id="aria_button_link" role="button" href="foo">button</a>
+ <a id="aria_checkbox_link" role="checkbox" href="foo">checkbox</a>
+
+ <!-- landmark links -->
+ <a id="aria_application_link" role="application" href="foo">app</a>
+ <a id="aria_main_link" role="main" href="foo">main</a>
+ <a id="aria_navigation_link" role="navigation" href="foo">nav</a>
+
+ <!-- strange edge case: please don't do this in the wild -->
+ <a id="aria_link_link" role="link" href="foo">link</a>
+
+ <div id="aria_textbox1" role="textbox">helo</div>
+ <div id="aria_textbox2" contenteditable role="textbox">
+ <ol><li>test</li></ol>
+ </div>
+
+ <div id="aria_combobox1" role="combobox"
+ aria-owns="aria_combobox1_owned_listbox"
+ aria-activedescendant="aria_combobox1_selected_option">
+ </div>
+ <ul role="listbox" id="aria_combobox1_owned_listbox">
+ <li role="option">Zebra</li>
+ <li role="option" id="aria_combobox1_selected_option">Zoom</li>
+ </ul>
+
+ <div id="aria_combobox2" role="combobox"
+ aria-owns="aria_combobox2_owned_listbox">
+ </div>
+ <ul role="listbox" id="aria_combobox2_owned_listbox">
+ <li role="option">Zebra</li>
+ <li role="option" aria-selected="true">Zoom</li>
+ </ul>
+
+ <div id="aria_combobox3" role="combobox">
+ <div role="textbox"></div>
+ <ul role="listbox">
+ <li role="option">1</li>
+ <li role="option" aria-selected="true">2</li>
+ <li role="option">3</li>
+ </ul>
+ </div>
+
+ <select id="combobox1">
+ <option id="cb1_item1">item1</option>
+ <option id="cb1_item2">item2</option>
+ </select>
+ <select id="combobox2">
+ <option id="cb2_item1">item1</option>
+ <option id="cb2_item2" selected="true">item2</option>
+ </select>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/value/test_meter.html b/accessible/tests/mochitest/value/test_meter.html
new file mode 100644
index 0000000000..7ae1ed3543
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_meter.html
@@ -0,0 +1,82 @@
+<html>
+
+<head>
+ <title>nsIAccessible value testing for meter element</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../value.js"></script>
+
+ <script src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // HTML5 meter element tests
+ testValue("nothing", "0", 0, 0, 1, 0);
+ testValue("minOnly", "20", 20, 20, 20, 0);
+ testValue("maxOnly", "0", 0, 0, 20, 0);
+ testValue("valOnly", "1", 1, 0, 1, 0);
+ testValue("regular", "15", 15, 10, 30, 0);
+ testValue("noMin", "10", 10, 0, 100, 0);
+ testValue("noMax", "5", 5, 5, 5, 0);
+ testValue("noVal", "10", 10, 10, 20, 0);
+ testValue("invalidValue", "20", 20, 10, 20, 0);
+ testValue("invalidMax", "10", 10, 10, 10, 0);
+ testValue("invalidValueMax", "20", 20, 20, 20, 0);
+
+ testValue("plainText", "Hello world", 0, 0, 1, 0);
+ testValue("regularText", "You scored 15 out of 30", 15, 10, 30, 0);
+ testValue("invalidText", "Something isnt right here", 20, 20, 20, 0);
+
+ testValue("valueText", "value", 0, 0, 1, 0);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1460378"
+ title="HTML <meter> not spoken by screen readers">
+ Mozilla Bug 559773
+ </a><br />
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+
+ <meter id="nothing"></meter>
+
+ <meter id="minOnly" min="20"></meter>
+ <meter id="maxOnly" max="20"></meter>
+ <meter id="valOnly" value="20"></meter>
+
+ <meter id="regular" min="10" value="15" max="30"></meter>
+
+ <meter id="noMin" value="10" max="100"></meter>
+ <meter id="noMax" min="5" value="10"></meter>
+ <meter id="noVal" min="10" max="20"></meter>
+
+ <meter id="invalidValue" min="10" value="30" max="20"></meter>
+ <meter id="invalidMax" min="10" value="15" max="2"></meter>
+ <meter id="invalidValueMax" min="20" value="17" max="10"></meter>
+
+ <meter id="plainText">Hello world</meter>
+ <meter id="regularText" min="10" value="15" max="30">You scored 15 out of 30</meter>
+ <meter id="invalidText" min="20" value="17" max="10">Something isnt right here</meter>
+
+ <meter id="valueText" aria-valuetext="value">valuetext should take precedence over internal text</meter>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/value/test_number.html b/accessible/tests/mochitest/value/test_number.html
new file mode 100644
index 0000000000..59024c3ef3
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_number.html
@@ -0,0 +1,56 @@
+<html>
+
+<head>
+ <title>nsIAccessible value testing for input@type=range element</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../value.js"></script>
+
+ <script src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // HTML5 number element tests
+ testValue("number", "", 0, 0, 0, 1);
+ testValue("number_value", "1", 1, 0, 0, 1);
+ testValue("number_step", "", 0, 0, 0, 1);
+ testValue("number_min42", "", 0, 42, 0, 1);
+ testValue("number_max42", "", 0, 0, 42, 1);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559761"
+ title="make HTML5 input@type=number element accessible">
+ Bug 559761
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+
+ <!-- HTML5 input@type=number element -->
+ <input type="number" id="number">
+ <input type="number" id="number_value" value="1">
+ <input type="number" id="number_step" step="1">
+ <input type="number" id="number_min42" min="42">
+ <input type="number" id="number_max42" max="42">
+</body>
+</html>
diff --git a/accessible/tests/mochitest/value/test_progress.html b/accessible/tests/mochitest/value/test_progress.html
new file mode 100644
index 0000000000..03ddb02196
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_progress.html
@@ -0,0 +1,61 @@
+<html>
+
+<head>
+ <title>nsIAccessible value testing for progress element</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../value.js"></script>
+
+ <script src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // HTML5 progress element tests
+ testValue("pr_indeterminate", "", 0, 0, 1, 0);
+ testValue("pr_zero", "0%", 0, 0, 1, 0);
+ testValue("pr_zeropointfive", "50%", 0.5, 0, 1, 0);
+ testValue("pr_one", "100%", 1, 0, 1, 0);
+ testValue("pr_42", "100%", 42, 0, 1, 0);
+ testValue("pr_21", "50%", 21, 0, 42, 0);
+ testValue("pr_valuetext", "value", 0, 0, 1, 0);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559773"
+ title="make HTML5 progress element accessible">
+ Mozilla Bug 559773
+ </a><br />
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+
+ <!-- HTML5 progress element -->
+ <progress id="pr_indeterminate">this will be read by legacy browsers</progress>
+ <progress id="pr_zero" value="0">this will be read by legacy browsers</progress>
+ <progress id="pr_zeropointfive" value="0.5">this will be read by legacy browsers</progress>
+ <progress id="pr_one" value="1">this will be read by legacy browsers</progress>
+ <progress id="pr_42" value="42">this will be read by legacy browsers</progress>
+ <progress id="pr_21" value="21" max="42">this will be read by legacy browsers</progress>
+ <!-- aria-valuetext should work due to implicit progressbar role (bug 1475376) -->
+ <progress id="pr_valuetext" aria-valuetext="value">this will be read by legacy browsers</progress>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/value/test_range.html b/accessible/tests/mochitest/value/test_range.html
new file mode 100644
index 0000000000..55cb0b1767
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_range.html
@@ -0,0 +1,59 @@
+<html>
+
+<head>
+ <title>nsIAccessible value testing for input@type=range element</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../value.js"></script>
+
+ <script src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ // HTML5 progress element tests
+ testValue("range", "50", 50, 0, 100, 1);
+ testValue("range_value", "1", 1, 0, 100, 1);
+ testValue("range_step", "50", 50, 0, 100, 1);
+ testValue("range_min42", "71", 71, 42, 100, 1);
+ testValue("range_max42", "21", 21, 0, 42, 1);
+ testValue("range_valuetext", "value", 50, 0, 100, 1);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764"
+ title="make HTML5 input@type=range element accessible">
+ Bug 559764
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+
+ <!-- HTML5 input@type=range element -->
+ <input type="range" id="range">
+ <input type="range" id="range_value" value="1">
+ <input type="range" id="range_step" step="1">
+ <input type="range" id="range_min42" min="42">
+ <input type="range" id="range_max42" max="42">
+ <!-- aria-valuetext should work due to implicit slider role (bug 1475376) -->
+ <input type="range" id="range_valuetext" aria-valuetext="value">
+</body>
+</html>