summaryrefslogtreecommitdiffstats
path: root/accessible/tests
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/tests')
-rw-r--r--accessible/tests/browser/.eslintrc.js28
-rw-r--r--accessible/tests/browser/Common.jsm456
-rw-r--r--accessible/tests/browser/Layout.jsm181
-rw-r--r--accessible/tests/browser/bounds/browser.ini13
-rw-r--r--accessible/tests/browser/bounds/browser_test_resolution.js72
-rw-r--r--accessible/tests/browser/bounds/browser_test_zoom.js69
-rw-r--r--accessible/tests/browser/bounds/browser_test_zoom_text.js76
-rw-r--r--accessible/tests/browser/bounds/head.js21
-rw-r--r--accessible/tests/browser/browser.ini34
-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.js104
-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.js151
-rw-r--r--accessible/tests/browser/browser_shutdown_remote_only.js56
-rw-r--r--accessible/tests/browser/browser_shutdown_remote_own_reference.js191
-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.ini60
-rw-r--r--accessible/tests/browser/e10s/browser_caching_attributes.js134
-rw-r--r--accessible/tests/browser/e10s/browser_caching_description.js214
-rw-r--r--accessible/tests/browser/e10s/browser_caching_name.js539
-rw-r--r--accessible/tests/browser/e10s/browser_caching_relations.js96
-rw-r--r--accessible/tests/browser/e10s/browser_caching_states.js154
-rw-r--r--accessible/tests/browser/e10s/browser_caching_uniqueid.js30
-rw-r--r--accessible/tests/browser/e10s/browser_caching_value.js195
-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.js114
-rw-r--r--accessible/tests/browser/e10s/browser_events_vcchange.js87
-rw-r--r--accessible/tests/browser/e10s/browser_text_paragraph_boundary.js22
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js45
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js294
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_canvas.js28
-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_imagemap.js187
-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_optgroup.js103
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_removal.js58
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js77
-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.js68
-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/head.js19
-rw-r--r--accessible/tests/browser/events/browser.ini17
-rw-r--r--accessible/tests/browser/events/browser_test_A11yUtils_announce.js57
-rw-r--r--accessible/tests/browser/events/browser_test_docload.js120
-rw-r--r--accessible/tests/browser/events/browser_test_focus_browserui.js52
-rw-r--r--accessible/tests/browser/events/browser_test_focus_dialog.js76
-rw-r--r--accessible/tests/browser/events/browser_test_focus_urlbar.js410
-rw-r--r--accessible/tests/browser/events/browser_test_scrolling.js96
-rw-r--r--accessible/tests/browser/events/browser_test_selection_urlbar.js58
-rw-r--r--accessible/tests/browser/events/browser_test_textcaret.js58
-rw-r--r--accessible/tests/browser/events/head.js19
-rw-r--r--accessible/tests/browser/fission/browser.ini14
-rw-r--r--accessible/tests/browser/fission/browser_content_tree.js75
-rw-r--r--accessible/tests/browser/fission/browser_hidden_iframe.js81
-rw-r--r--accessible/tests/browser/fission/browser_nested_iframe.js153
-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.js61
-rw-r--r--accessible/tests/browser/fission/browser_take_focus.js26
-rw-r--r--accessible/tests/browser/fission/head.js19
-rw-r--r--accessible/tests/browser/general/browser.ini9
-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.js72
-rw-r--r--accessible/tests/browser/head.js147
-rw-r--r--accessible/tests/browser/hittest/browser.ini15
-rw-r--r--accessible/tests/browser/hittest/browser_test_browser.js68
-rw-r--r--accessible/tests/browser/hittest/browser_test_canvas_hitregion.js92
-rw-r--r--accessible/tests/browser/hittest/browser_test_general.js123
-rw-r--r--accessible/tests/browser/hittest/browser_test_shadowroot.js61
-rw-r--r--accessible/tests/browser/hittest/browser_test_zoom.js38
-rw-r--r--accessible/tests/browser/hittest/browser_test_zoom_text.js76
-rw-r--r--accessible/tests/browser/hittest/head.js89
-rw-r--r--accessible/tests/browser/mac/browser.ini50
-rw-r--r--accessible/tests/browser/mac/browser_app.js292
-rw-r--r--accessible/tests/browser/mac/browser_aria_busy.js44
-rw-r--r--accessible/tests/browser/mac/browser_aria_controls_flowto.js84
-rw-r--r--accessible/tests/browser/mac/browser_aria_current.js60
-rw-r--r--accessible/tests/browser/mac/browser_aria_haspopup.js330
-rw-r--r--accessible/tests/browser/mac/browser_details_summary.js60
-rw-r--r--accessible/tests/browser/mac/browser_focus.js44
-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.js79
-rw-r--r--accessible/tests/browser/mac/browser_link.js125
-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.js52
-rw-r--r--accessible/tests/browser/mac/browser_navigate.js394
-rw-r--r--accessible/tests/browser/mac/browser_outline.js459
-rw-r--r--accessible/tests/browser/mac/browser_outline_xul.js274
-rw-r--r--accessible/tests/browser/mac/browser_popupbutton.js153
-rw-r--r--accessible/tests/browser/mac/browser_radio_position.js321
-rw-r--r--accessible/tests/browser/mac/browser_range.js92
-rw-r--r--accessible/tests/browser/mac/browser_required.js145
-rw-r--r--accessible/tests/browser/mac/browser_rich_listbox.js73
-rw-r--r--accessible/tests/browser/mac/browser_roles_elements.js309
-rw-r--r--accessible/tests/browser/mac/browser_rootgroup.js196
-rw-r--r--accessible/tests/browser/mac/browser_rotor.js1697
-rw-r--r--accessible/tests/browser/mac/browser_selectables.js336
-rw-r--r--accessible/tests/browser/mac/browser_table.js281
-rw-r--r--accessible/tests/browser/mac/browser_text_basics.js282
-rw-r--r--accessible/tests/browser/mac/browser_text_input.js412
-rw-r--r--accessible/tests/browser/mac/browser_text_leaf.js75
-rw-r--r--accessible/tests/browser/mac/browser_text_selection.js187
-rw-r--r--accessible/tests/browser/mac/browser_toggle_radio_check.js140
-rw-r--r--accessible/tests/browser/mac/browser_webarea.js76
-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.js114
-rw-r--r--accessible/tests/browser/scroll/browser.ini9
-rw-r--r--accessible/tests/browser/scroll/browser_test_zoom_text.js150
-rw-r--r--accessible/tests/browser/scroll/head.js19
-rw-r--r--accessible/tests/browser/shared-head.js779
-rw-r--r--accessible/tests/browser/states/browser.ini17
-rw-r--r--accessible/tests/browser/states/browser_deck_has_out_of_process_iframe.js133
-rw-r--r--accessible/tests/browser/states/browser_offscreen_element_in_out_of_process_iframe.js98
-rw-r--r--accessible/tests/browser/states/browser_test_link.js47
-rw-r--r--accessible/tests/browser/states/browser_test_visibility.js52
-rw-r--r--accessible/tests/browser/states/head.js81
-rw-r--r--accessible/tests/browser/states/target.html3
-rw-r--r--accessible/tests/browser/states/test_deck_has_out_of_process_iframe.xhtml10
-rw-r--r--accessible/tests/browser/telemetry/browser.ini3
-rw-r--r--accessible/tests/browser/telemetry/browser_HCM_telemetry.js177
-rw-r--r--accessible/tests/browser/tree/browser.ini11
-rw-r--r--accessible/tests/browser/tree/browser_aria_owns.js278
-rw-r--r--accessible/tests/browser/tree/browser_searchbar.js52
-rw-r--r--accessible/tests/browser/tree/browser_shadowdom.js64
-rw-r--r--accessible/tests/browser/tree/head.js34
-rw-r--r--accessible/tests/crashtests/.eslintrc.js7
-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/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/471493.xhtml55
-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.list21
-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.ini18
-rw-r--r--accessible/tests/mochitest/actions.js230
-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.xhtml172
-rw-r--r--accessible/tests/mochitest/actions/test_keys.html57
-rw-r--r--accessible/tests/mochitest/actions/test_keys.xhtml107
-rw-r--r--accessible/tests/mochitest/actions/test_link.html141
-rw-r--r--accessible/tests/mochitest/actions/test_media.html130
-rw-r--r--accessible/tests/mochitest/actions/test_select.html103
-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.js490
-rw-r--r--accessible/tests/mochitest/attributes/a11y.ini15
-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.html300
-rw-r--r--accessible/tests/mochitest/attributes/test_obj_css.html225
-rw-r--r--accessible/tests/mochitest/attributes/test_obj_css.xhtml57
-rw-r--r--accessible/tests/mochitest/attributes/test_obj_group.html566
-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.js195
-rw-r--r--accessible/tests/mochitest/bounds/a11y.ini6
-rw-r--r--accessible/tests/mochitest/bounds/test_list.html78
-rw-r--r--accessible/tests/mochitest/bounds/test_select.html78
-rw-r--r--accessible/tests/mochitest/browser.js157
-rw-r--r--accessible/tests/mochitest/common.js1076
-rw-r--r--accessible/tests/mochitest/dumbfile.zipbin0 -> 22 bytes
-rw-r--r--accessible/tests/mochitest/editabletext/a11y.ini7
-rw-r--r--accessible/tests/mochitest/editabletext/editabletext.js409
-rw-r--r--accessible/tests/mochitest/editabletext/test_1.html140
-rw-r--r--accessible/tests/mochitest/editabletext/test_2.html61
-rw-r--r--accessible/tests/mochitest/elm/a11y.ini15
-rw-r--r--accessible/tests/mochitest/elm/test_HTMLSpec.html1972
-rw-r--r--accessible/tests/mochitest/elm/test_MathMLSpec.html617
-rw-r--r--accessible/tests/mochitest/elm/test_canvas.html55
-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.js2631
-rw-r--r--accessible/tests/mochitest/events/a11y.ini63
-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.html126
-rw-r--r--accessible/tests/mochitest/events/docload/test_docload_shutdown.html143
-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/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.html224
-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.html125
-rw-r--r--accessible/tests/mochitest/events/test_descrchange.html79
-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.html260
-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.xhtml95
-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.html164
-rw-r--r--accessible/tests/mochitest/events/test_focus_general.xhtml118
-rw-r--r--accessible/tests/mochitest/events/test_focus_listcontrols.xhtml152
-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.html115
-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.html96
-rw-r--r--accessible/tests/mochitest/events/test_fromUserInput.html112
-rw-r--r--accessible/tests/mochitest/events/test_label.xhtml176
-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.html119
-rw-r--r--accessible/tests/mochitest/events/test_namechange.xhtml90
-rw-r--r--accessible/tests/mochitest/events/test_scroll.xhtml141
-rw-r--r--accessible/tests/mochitest/events/test_scroll_caret.xhtml91
-rw-r--r--accessible/tests/mochitest/events/test_selection.html115
-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.html317
-rw-r--r--accessible/tests/mochitest/events/test_statechange_tabpanels.xhtml98
-rw-r--r--accessible/tests/mochitest/events/test_text.html310
-rw-r--r--accessible/tests/mochitest/events/test_text_alg.html246
-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.html279
-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.ini15
-rw-r--r--accessible/tests/mochitest/hittest/test_browser.html61
-rw-r--r--accessible/tests/mochitest/hittest/test_canvas_hitregion.html85
-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.html273
-rw-r--r--accessible/tests/mochitest/hyperlink/test_general.xhtml96
-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.js385
-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.build37
-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.js438
-rw-r--r--accessible/tests/mochitest/name/markuprules.xml385
-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.html706
-rw-r--r--accessible/tests/mochitest/name/test_general.xhtml339
-rw-r--r--accessible/tests/mochitest/name/test_link.html87
-rw-r--r--accessible/tests/mochitest/name/test_list.html89
-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.js665
-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.html33
-rw-r--r--accessible/tests/mochitest/pivot/test_virtualcursor.html119
-rw-r--r--accessible/tests/mochitest/pivot/test_virtualcursor_text.html267
-rw-r--r--accessible/tests/mochitest/promisified-events.js262
-rw-r--r--accessible/tests/mochitest/relations.js203
-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.html408
-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.js197
-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.html439
-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.html88
-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.js123
-rw-r--r--accessible/tests/mochitest/selectable/a11y.ini12
-rw-r--r--accessible/tests/mochitest/selectable/test_aria.html246
-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_select.html241
-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.js363
-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.html113
-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.html196
-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.xhtml151
-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.js1033
-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.html126
-rw-r--r--accessible/tests/mochitest/table/test_sels_ariagrid.html159
-rw-r--r--accessible/tests/mochitest/table/test_sels_table.html178
-rw-r--r--accessible/tests/mochitest/table/test_sels_tree.xhtml78
-rw-r--r--accessible/tests/mochitest/table/test_struct_ariagrid.html147
-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_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.html423
-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.html149
-rw-r--r--accessible/tests/mochitest/text/test_lineboundary.html341
-rw-r--r--accessible/tests/mochitest/text/test_paragraphboundary.html140
-rw-r--r--accessible/tests/mochitest/text/test_passwords.html72
-rw-r--r--accessible/tests/mochitest/text/test_selection.html96
-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.ini8
-rw-r--r--accessible/tests/mochitest/textattrs/test_general.html737
-rw-r--r--accessible/tests/mochitest/textattrs/test_invalid.html59
-rw-r--r--accessible/tests/mochitest/textattrs/test_spelling.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.html129
-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.ini56
-rw-r--r--accessible/tests/mochitest/tree/dockids.html30
-rw-r--r--accessible/tests/mochitest/tree/test_applicationacc.xhtml73
-rw-r--r--accessible/tests/mochitest/tree/test_aria_globals.html124
-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.xhtml72
-rw-r--r--accessible/tests/mochitest/tree/test_canvas.html53
-rw-r--r--accessible/tests/mochitest/tree/test_combobox.xhtml117
-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.html49
-rw-r--r--accessible/tests/mochitest/tree/test_divs.html333
-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_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.html354
-rw-r--r--accessible/tests/mochitest/tree/test_map.html81
-rw-r--r--accessible/tests/mochitest/tree/test_media.html121
-rw-r--r--accessible/tests/mochitest/tree/test_select.html137
-rw-r--r--accessible/tests/mochitest/tree/test_svg.html127
-rw-r--r--accessible/tests/mochitest/tree/test_tabbox.xhtml98
-rw-r--r--accessible/tests/mochitest/tree/test_tabbrowser.xhtml261
-rw-r--r--accessible/tests/mochitest/tree/test_table.html458
-rw-r--r--accessible/tests/mochitest/tree/test_table_2.html240
-rw-r--r--accessible/tests/mochitest/tree/test_table_3.html242
-rw-r--r--accessible/tests/mochitest/tree/test_tree.xhtml180
-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.ini44
-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.html797
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1040735.html40
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1100602.html103
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1175913.html95
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1189277.html81
-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.html150
-rw-r--r--accessible/tests/mochitest/treeupdate/test_deck.xhtml154
-rw-r--r--accessible/tests/mochitest/treeupdate/test_delayed_removal.html453
-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.html399
-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_listbox.xhtml181
-rw-r--r--accessible/tests/mochitest/treeupdate/test_menu.xhtml127
-rw-r--r--accessible/tests/mochitest/treeupdate/test_menubutton.xhtml140
-rw-r--r--accessible/tests/mochitest/treeupdate/test_optgroup.html126
-rw-r--r--accessible/tests/mochitest/treeupdate/test_recreation.html91
-rw-r--r--accessible/tests/mochitest/treeupdate/test_select.html136
-rw-r--r--accessible/tests/mochitest/treeupdate/test_shadow_slots.html467
-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.xhtml71
-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.ini9
-rw-r--r--accessible/tests/mochitest/value/test_ariavalue.html68
-rw-r--r--accessible/tests/mochitest/value/test_general.html159
-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
555 files changed, 81920 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.jsm b/accessible/tests/browser/Common.jsm
new file mode 100644
index 0000000000..527e301be9
--- /dev/null
+++ b/accessible/tests/browser/Common.jsm
@@ -0,0 +1,456 @@
+/* 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 EXPORTED_SYMBOLS = ["CommonUtils"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
+
+const MAX_TRIM_LENGTH = 100;
+
+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.jsm b/accessible/tests/browser/Layout.jsm
new file mode 100644
index 0000000000..d51644ec57
--- /dev/null
+++ b/accessible/tests/browser/Layout.jsm
@@ -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";
+
+const EXPORTED_SYMBOLS = ["Layout"];
+
+const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
+const { CommonUtils } = ChromeUtils.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.jsm"
+);
+
+const Layout = {
+ /**
+ * Zoom the given document.
+ */
+ zoomDocument(doc, zoom) {
+ const bc = BrowsingContext.getFromWindow(doc.defaultView);
+ bc.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 winUtil = win.windowUtils;
+ const ratio = winUtil.screenPixelsPerCSSPixel;
+
+ // 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..1997e92ff1
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+
+[browser_test_resolution.js]
+skip-if = e10s && os == 'win' # bug 1372296
+[browser_test_zoom.js]
+[browser_test_zoom_text.js]
+skip-if = e10s && os == 'win' # bug 1372296
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..bdcb3e1792
--- /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.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
+ );
+ 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.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
+ );
+ 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_zoom.js b/accessible/tests/browser/bounds/browser_test_zoom.js
new file mode 100644
index 0000000000..6600a17130
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_test_zoom.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/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 invokeContentTask(browser, [], () => {
+ const { Layout } = ChromeUtils.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
+ );
+ 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..8abbff025d
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_test_zoom_text.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/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
+ );
+ }
+
+ 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
+ );
+ testTextBounds(
+ inputNode,
+ 0,
+ 0,
+ [x, y, width, height],
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ }
+
+ await testTextNode("p1");
+ await testTextNode("p2");
+ await testEmptyInputNode("i1");
+
+ await invokeContentTask(browser, [], () => {
+ const { Layout } = ChromeUtils.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
+ );
+ Layout.zoomDocument(content.document, 2.0);
+ });
+
+ await testTextNode("p1");
+
+ await invokeContentTask(browser, [], () => {
+ const { Layout } = ChromeUtils.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
+ );
+ 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/head.js b/accessible/tests/browser/bounds/head.js
new file mode 100644
index 0000000000..f4d20e636c
--- /dev/null
+++ b/accessible/tests/browser/bounds/head.js
@@ -0,0 +1,21 @@
+/* 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.
+/* import-globals-from ../shared-head.js */
+
+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..99acf3c0b0
--- /dev/null
+++ b/accessible/tests/browser/browser.ini
@@ -0,0 +1,34 @@
+[DEFAULT]
+skip-if = a11y_checks || (os == 'win' && processor == 'aarch64') # 1534855
+support-files =
+ !/accessible/tests/mochitest/*.js
+ *.jsm
+ head.js
+ shared-head.js
+
+[browser_shutdown_acc_reference.js]
+skip-if = (os == 'linux' && debug && bits == 64) #Bug 1421307
+[browser_shutdown_doc_acc_reference.js]
+[browser_shutdown_multi_acc_reference_obj.js]
+[browser_shutdown_multi_acc_reference_doc.js]
+[browser_shutdown_multi_reference.js]
+[browser_shutdown_parent_own_reference.js]
+skip-if = !e10s || (verify && debug && (os == 'win')) # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_pref.js]
+[browser_shutdown_proxy_acc_reference.js]
+skip-if = !e10s || (os == 'win') # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_proxy_doc_acc_reference.js]
+skip-if = !e10s || (os == 'win') || (verify && debug) # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_multi_proxy_acc_reference_doc.js]
+skip-if = !e10s || (os == 'win') || (verify && debug && (os == 'linux')) # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_multi_proxy_acc_reference_obj.js]
+skip-if = !e10s || (os == 'win') || (verify && debug && (os == 'linux')) # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_remote_no_reference.js]
+skip-if = !e10s || (verify && debug && (os == 'win')) # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_remote_only.js]
+skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_remote_own_reference.js]
+skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content.
+[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..68c07ba2b6
--- /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..baf2b898e5
--- /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..b67b2f46f7
--- /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..18160a8db7
--- /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..8763327bae
--- /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..5134901355
--- /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..cd0bc0d103
--- /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..86115fdaad
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_parent_own_reference.js
@@ -0,0 +1,104 @@
+/* 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, "Common.jsm");
+ // 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..d6fa715cf3
--- /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..1dc2344acb
--- /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..cce695579d
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_remote_no_reference.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";
+
+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, "Common.jsm");
+ // 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..8798170c05
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_remote_only.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() {
+ // 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, "Common.jsm");
+ // 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..2d88e5ed85
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_remote_own_reference.js
@@ -0,0 +1,191 @@
+/* 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, "Common.jsm");
+ // 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..b4dad44de8
--- /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..bac7a61da7
--- /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..a582b9b8bb
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser.ini
@@ -0,0 +1,60 @@
+[DEFAULT]
+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
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+ !/accessible/tests/mochitest/moz.png
+
+# Caching tests
+[browser_caching_attributes.js]
+[browser_caching_description.js]
+[browser_caching_name.js]
+skip-if = (os == "linux" && bits == 64) || (debug && os == "mac") || (debug && os == "win") #Bug 1388256
+[browser_caching_relations.js]
+[browser_caching_states.js]
+[browser_caching_value.js]
+[browser_caching_uniqueid.js]
+
+# Events tests
+[browser_events_announcement.js]
+skip-if = e10s && 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]
+
+# Text tests
+[browser_text_paragraph_boundary.js]
+
+# Tree update tests
+[browser_treeupdate_ariadialog.js]
+[browser_treeupdate_ariaowns.js]
+[browser_treeupdate_canvas.js]
+skip-if = (os == 'win' && os_version == '10.0' && bits == 64 && !debug) #Bug 1462638 - Disabled on Win10 opt/pgo for frequent failures
+[browser_treeupdate_cssoverflow.js]
+[browser_treeupdate_doc.js]
+skip-if = e10s && os == 'win' # Bug 1288839
+[browser_treeupdate_gencontent.js]
+[browser_treeupdate_hidden.js]
+[browser_treeupdate_imagemap.js]
+[browser_treeupdate_list.js]
+[browser_treeupdate_list_editabledoc.js]
+[browser_treeupdate_listener.js]
+[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]
+skip-if = true # Failing due to incorrect index of test container children on document load.
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..b8e157b068
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_attributes.js
@@ -0,0 +1,134 @@
+/* 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);
+ }
+ },
+ { iframe: true, remoteIframe: 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..5b9e70ce07
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_description.js
@@ -0,0 +1,214 @@
+/* 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_REORDER, matchContentDoc]],
+ 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:
+ "Description from @aria-describedby attribute when @title (used for " +
+ "name) and @aria-describedby are not the same",
+ attrs: [
+ {
+ attr: "alt",
+ },
+ {
+ attr: "title",
+ value: "title",
+ },
+ ],
+ waitFor: [[EVENT_REORDER, matchContentDoc]],
+ 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_REORDER, matchContentDoc]],
+ 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" />`,
+ 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 }
+);
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..cac05a04de
--- /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
+ *
+ *
+ * Options include:
+ * * waitFor - changes in the subtree will result in an accessible event
+ * being fired, the test must only continue after the event
+ * is receieved.
+ */
+const ARIARule = [
+ { attr: "aria-labelledby" },
+ { attr: "aria-label", waitFor: EVENT_NAME_CHANGE },
+];
+const HTMLControlHeadRule = [...ARIARule, { elm: "label", isSibling: true }];
+const rules = {
+ CSSContent: [{ elm: "style", isSibling: true }, { fromsubtree: true }],
+ HTMLARIAGridCell: [...ARIARule, { fromsubtree: true }, { attr: "title" }],
+ HTMLControl: [
+ ...HTMLControlHeadRule,
+ { fromsubtree: true },
+ { attr: "title" },
+ ],
+ HTMLElm: [...ARIARule, { attr: "title" }],
+ HTMLImg: [
+ ...ARIARule,
+ { attr: "alt", waitFor: EVENT_NAME_CHANGE },
+ { attr: "title" },
+ ],
+ HTMLImgEmptyAlt: [...ARIARule, { attr: "title" }, { attr: "alt" }],
+ HTMLInputButton: [
+ ...HTMLControlHeadRule,
+ { attr: "value" },
+ { attr: "title" },
+ ],
+ HTMLInputImage: [
+ ...HTMLControlHeadRule,
+ { attr: "alt", waitFor: EVENT_NAME_CHANGE },
+ { attr: "value" },
+ { attr: "title" },
+ ],
+ HTMLInputImageNoValidSrc: [
+ ...HTMLControlHeadRule,
+ { attr: "alt", waitFor: EVENT_NAME_CHANGE },
+ { attr: "value" },
+ ],
+ HTMLInputReset: [
+ ...HTMLControlHeadRule,
+ { attr: "value", waitFor: EVENT_TEXT_INSERTED },
+ ],
+ HTMLInputSubmit: [
+ ...HTMLControlHeadRule,
+ { attr: "value", waitFor: EVENT_TEXT_INSERTED },
+ ],
+ HTMLLink: [...ARIARule, { fromsubtree: true }, { attr: "title" }],
+ HTMLLinkImage: [...ARIARule, { elm: "img" }, { 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: "imgemptyalt",
+ ruleset: "HTMLImgEmptyAlt",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <img id="imgemptyalt"
+ aria-label="Logo of Mozilla"
+ aria-labelledby="l1 l2"
+ title="This is a logo"
+ alt=""
+ src="http://example.com/a11y/accessible/tests/mochitest/moz.png"/>`,
+ expected: ["test2 test3", "Logo of Mozilla", "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, parent, id } structure that contains an
+ * accessible, its parent 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, parent, acc } = target;
+ let { waitFor, attr } = rule;
+
+ testName(acc, expected);
+
+ if (waitFor) {
+ let [event] = await contentSpawnMutation(
+ browser,
+ {
+ expected: [[waitFor, waitFor === EVENT_REORDER ? parent : id]],
+ },
+ (contentId, contentAttr) =>
+ content.document.getElementById(contentId).removeAttribute(contentAttr),
+ [id, attr]
+ );
+
+ // Update accessible just in case it is now defunct.
+ target.acc = findAccessibleChildByID(event.accessible, id);
+ } else {
+ await invokeSetAttribute(browser, id, attr);
+ }
+}
+
+/**
+ * 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, parent, id } structure that contains an
+ * accessible, its parent 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, parent, acc } = target;
+ let { isSibling, elm } = rule;
+
+ testName(acc, expected);
+ let [event] = await contentSpawnMutation(
+ browser,
+ {
+ expected: [[EVENT_REORDER, isSibling ? parent : id]],
+ },
+ contentElm => content.document.querySelector(`${contentElm}`).remove(),
+ [elm]
+ );
+
+ // 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, parent, id } structure that contains an
+ * accessible, its parent 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 [event] = await contentSpawnMutation(
+ browser,
+ {
+ expected: [[EVENT_REORDER, id]],
+ },
+ contentId => {
+ let elm = content.document.getElementById(contentId);
+ while (elm.firstChild) {
+ elm.firstChild.remove();
+ }
+ },
+ [id]
+ );
+
+ // 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, parent, id } structure that contains an
+ * accessible, its parent 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);
+ // Find target's parent accessible from an accessible subtree.
+ let parent = getAccessibleDOMNodeID(acc.parent);
+ let target = { id, parent, acc };
+ await testNameRule(browser, target, rules[ruleset], expected);
+ Services.obs.removeObserver(observer, "accessible-event");
+ },
+ { iframe: true, remoteIframe: 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..38d3a0c63b
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_relations.js
@@ -0,0 +1,96 @@
+/* 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/relations.js */
+loadScripts({ name: "relations.js", dir: MOCHITESTS_DIR });
+
+/**
+ * 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],
+];
+
+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);
+ }
+ }
+
+ testRelation(dependant1, dependantRelation, expected[0]);
+ testRelation(dependant2, dependantRelation, expected[1]);
+ testRelation(host, hostRelation, expected[2]);
+ }
+}
+
+/**
+ * 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 }
+);
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..e6dbc33d65
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_states.js
@@ -0,0 +1,154 @@
+/* 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) {
+ info(desc);
+ let onUpdate = waitForEvent(EVENT_STATE_CHANGE, id);
+ 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 }
+);
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..287f896c36
--- /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..c727fc5252
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_value.js
@@ -0,0 +1,195 @@
+/* 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/value.js */
+loadScripts({ 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.import(
+ "resource://testing-common/ContentTaskUtils.jsm"
+ );
+ 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 @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: "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.import(
+ "resource://testing-common/ContentTaskUtils.jsm"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.synthesizeKey("VK_LEFT", {}, content);
+ });
+ },
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: "5",
+ },
+];
+
+/**
+ * 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">`,
+ 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);
+ } 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 }
+);
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..2de6d4b005
--- /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..a39d16e710
--- /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..d46921d051
--- /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..d464d8fb9d
--- /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..a027a974e4
--- /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..1e822dc65a
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_textchange.js
@@ -0,0 +1,114 @@
+/* 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.import(
+ "resource://testing-common/ContentTaskUtils.jsm"
+ );
+ 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);
+
+ events = [{ isInserted: true, str: "DEF", offset: 2 }];
+ 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..bf649fe045
--- /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.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.jsm"
+ );
+ 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.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.jsm"
+ );
+ 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.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.jsm"
+ );
+ 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_text_paragraph_boundary.js b/accessible/tests/browser/e10s/browser_text_paragraph_boundary.js
new file mode 100644
index 0000000000..04e64520e8
--- /dev/null
+++ b/accessible/tests/browser/e10s/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/e10s/browser_treeupdate_ariadialog.js b/accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js
new file mode 100644
index 0000000000..8b4a575d75
--- /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..dfd6401c48
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js
@@ -0,0 +1,294 @@
+/* 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 }
+);
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..5fcd1eb773
--- /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_cssoverflow.js b/accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js
new file mode 100644
index 0000000000..629f9fb89f
--- /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..98f399695c
--- /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..ca1150f9dd
--- /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..725999db36
--- /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_imagemap.js b/accessible/tests/browser/e10s/browser_treeupdate_imagemap.js
new file mode 100644
index 0000000000..e206b83276
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_imagemap.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";
+
+/* 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",
+ "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",
+ "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.import(
+ "resource://testing-common/ContentTaskUtils.jsm"
+ );
+ 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");
+
+ 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..d14b983c10
--- /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..9c672f3c7c
--- /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..35baf28667
--- /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_optgroup.js b/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js
new file mode 100644
index 0000000000..15fed8112d
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_optgroup.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 });
+
+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: [{ TEXT_LEAF: [] }] },
+ { COMBOBOX_OPTION: [{ TEXT_LEAF: [] }] },
+ ],
+ },
+ {
+ 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..eb791525b3
--- /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..add454ceac
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.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 });
+
+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.import(
+ "resource://testing-common/ContentTaskUtils.jsm"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.synthesizeKey("VK_DOWN", { altKey: true }, content);
+ });
+ 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"
+ );
+ await invokeContentTask(browser, [], () => {
+ const { ContentTaskUtils } = ChromeUtils.import(
+ "resource://testing-common/ContentTaskUtils.jsm"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, content);
+ });
+ 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..5c2903225a
--- /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..6f89105b86
--- /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..4583056586
--- /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..7f7e4ba97d
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_whitespace.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";
+
+/* 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",
+ "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..9d08854b9a
--- /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: fixed; 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/head.js b/accessible/tests/browser/e10s/head.js
new file mode 100644
index 0000000000..672aa46171
--- /dev/null
+++ b/accessible/tests/browser/e10s/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.
+/* import-globals-from ../shared-head.js */
+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/events/browser.ini b/accessible/tests/browser/events/browser.ini
new file mode 100644
index 0000000000..fe1d15917d
--- /dev/null
+++ b/accessible/tests/browser/events/browser.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/browser/*.jsm
+
+[browser_test_docload.js]
+skip-if = e10s
+[browser_test_scrolling.js]
+[browser_test_textcaret.js]
+[browser_test_focus_browserui.js]
+skip-if = sessionHistoryInParent
+[browser_test_focus_dialog.js]
+[browser_test_focus_urlbar.js]
+[browser_test_A11yUtils_announce.js]
+[browser_test_selection_urlbar.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_docload.js b/accessible/tests/browser/events/browser_test_docload.js
new file mode 100644
index 0000000000..53722a582a
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_docload.js
@@ -0,0 +1,120 @@
+/* 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.loadURI(
+ 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.loadURI(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.loadURI(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([
+ [EVENT_DOCUMENT_LOAD_COMPLETE, urlChecker("http://www.wronguri.wronguri/")],
+ [EVENT_STATE_CHANGE, busyChecker(false)],
+ [EVENT_REORDER, getAccessible(browser)],
+ ]);
+
+ BrowserTestUtils.loadURI(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.loadURI(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..98fc735269
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_focus_browserui.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/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, "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(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..874a0e239b
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_focus_urlbar.js
@@ -0,0 +1,410 @@
+/* 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 }
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm",
+ PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
+ UrlbarProvider: "resource:///modules/UrlbarUtils.jsm",
+ UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
+ UrlbarResult: "resource:///modules/UrlbarResult.jsm",
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.jsm",
+ UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
+});
+
+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 isEventForTipButton(event) {
+ let parent = event.accessible.parent;
+ return (
+ event.accessible.role == ROLE_PUSHBUTTON &&
+ parent &&
+ parent.role == ROLE_GROUPING &&
+ parent.name
+ );
+}
+
+/**
+ * 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([
+ "http://example1.com/blah",
+ "http://example2.com/blah",
+ "http://example1.com/",
+ "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);
+ }
+
+ info(
+ "Ensuring context menu gets menu event on launch, item focus on down, and address bar focus on escape."
+ );
+ let menuEvent = waitForEvent(
+ nsIAccessibleEvent.EVENT_MENUPOPUP_START,
+ isEventForMenuPopup
+ );
+ await EventUtils.sendMouseEvent(
+ { type: "contextmenu" },
+ gURLBar.querySelector("moz-input-box")
+ );
+ 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;
+ 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,
+ { url: "http://mozilla.org/a" }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.TIP,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ icon: "",
+ text: "This is a test intervention.",
+ buttonText: "Done",
+ type: "test",
+ helpUrl: "about:blank",
+ buttonUrl: "about:mozilla",
+ }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: "http://mozilla.org/b" }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { 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, isEventForTipButton);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ event = await focused;
+ testStates(event.accessible, STATE_FOCUSED);
+
+ info("Ensuring the help button is focused on down arrow");
+ info("Also ensuring that the help button is a part of a labelled group");
+ focused = waitForEvent(EVENT_FOCUS, isEventForTipButton);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ 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 up arrow");
+ focused = waitForEvent(EVENT_FOCUS, isEventForTipButton);
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ 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_scrolling.js b/accessible/tests/browser/events/browser_test_scrolling.js
new file mode 100644
index 0000000000..d9a4a0a73f
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_scrolling.js
@@ -0,0 +1,96 @@
+/* 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>`,
+ 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"
+ );
+ }
+);
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..0a773ce896
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_selection_urlbar.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 });
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ BrowserTestUtils: "resource://testing-common/BrowserTestUtils.jsm",
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm",
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.jsm",
+});
+
+// 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();
+ await PlacesTestUtils.addVisits("http://addons.mozilla.org");
+
+ registerCleanupFunction(async function() {
+ await BrowserTestUtils.closeWindow(newWin);
+ await PlacesUtils.history.clear();
+ });
+ 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..d4e0f11a0f
--- /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.loadURI(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..672aa46171
--- /dev/null
+++ b/accessible/tests/browser/events/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.
+/* import-globals-from ../shared-head.js */
+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..a3fc7aa839
--- /dev/null
+++ b/accessible/tests/browser/fission/browser.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+
+[browser_content_tree.js]
+[browser_hidden_iframe.js]
+[browser_nested_iframe.js]
+[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..54df06c7f4
--- /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..94b3015424
--- /dev/null
+++ b/accessible/tests/browser/fission/browser_hidden_iframe.js
@@ -0,0 +1,81 @@
+/* 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 be accessible");
+
+ info(
+ "Show the IFRAME and check that it's now available in the accessibility tree."
+ );
+
+ const events = [[EVENT_REORDER, contentDocAcc]];
+ // Until this event is fired, IFRAME accessible has no children attached.
+ 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
+ );
+ },
+ ]);
+
+ 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);
+ iframeDocAcc = findAccessibleChildByID(
+ contentDocAcc,
+ DEFAULT_IFRAME_DOC_BODY_ID
+ );
+
+ ok(!isDefunct(iframeAcc), "IFRAME should be accessible");
+ 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..0e48d767f8
--- /dev/null
+++ b/accessible/tests/browser/fission/browser_nested_iframe.js
@@ -0,0 +1,153 @@
+/* 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";
+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) {
+ let nestedDocAcc = findAccessibleChildByID(
+ iframeDocAcc,
+ NESTED_IFRAME_DOC_BODY_ID
+ );
+
+ ok(iframeDocAcc, "IFRAME document accessible is present");
+ ok(nestedDocAcc, "Nested IFRAME document accessible is present");
+
+ const state = {};
+ nestedDocAcc.getState(state, {});
+ if (state.value & STATE_BUSY) {
+ 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..66dcf249bf
--- /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..bddb651f91
--- /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..c490468b15
--- /dev/null
+++ b/accessible/tests/browser/fission/browser_src_change.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";
+
+/* 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,
+ "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..6e6eba662e
--- /dev/null
+++ b/accessible/tests/browser/fission/browser_take_focus.js
@@ -0,0 +1,26 @@
+/* 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(
+ `<input id="textbox" value="hello"/>`,
+ async function(browser, iframeDocAcc, contentDocAcc) {
+ const textbox = findAccessibleChildByID(iframeDocAcc, "textbox");
+ testStates(textbox, STATE_FOCUSABLE, 0, STATE_FOCUSED);
+
+ let onFocus = waitForEvent(EVENT_FOCUS, textbox);
+ textbox.takeFocus();
+ await onFocus;
+
+ testStates(textbox, STATE_FOCUSABLE | STATE_FOCUSED, 0);
+ },
+ { 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..672aa46171
--- /dev/null
+++ b/accessible/tests/browser/fission/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.
+/* import-globals-from ../shared-head.js */
+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..93ff094995
--- /dev/null
+++ b/accessible/tests/browser/general/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/browser/shared-head.js
+ head.js
+ !/accessible/tests/mochitest/*.js
+skip-if = a11y_checks || (os == 'win' && processor == 'aarch64') # windows-aarch64, 1534811
+
+[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..8c0c7797b5
--- /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.import(
+ "resource://testing-common/UrlbarTestUtils.jsm"
+);
+
+// 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..cd03a441f3
--- /dev/null
+++ b/accessible/tests/browser/general/head.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";
+
+/* exported initAccessibilityService, openNewTab, shutdownAccessibilityService */
+
+// Load the shared-head file first.
+/* import-globals-from ../shared-head.js */
+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..915d502905
--- /dev/null
+++ b/accessible/tests/browser/head.js
@@ -0,0 +1,147 @@
+/* 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.
+/* import-globals-from shared-head.js */
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+const { CommonUtils } = ChromeUtils.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.jsm"
+);
+
+/**
+ * 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..6b293b24bf
--- /dev/null
+++ b/accessible/tests/browser/hittest/browser.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+
+[browser_test_browser.js]
+[browser_test_canvas_hitregion.js]
+skip-if = os == "android"
+[browser_test_general.js]
+[browser_test_shadowroot.js]
+[browser_test_zoom_text.js]
+[browser_test_zoom.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_canvas_hitregion.js b/accessible/tests/browser/hittest/browser_test_canvas_hitregion.js
new file mode 100644
index 0000000000..dbd326f593
--- /dev/null
+++ b/accessible/tests/browser/hittest/browser_test_canvas_hitregion.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";
+
+function convertCSSToDevicePixels(browser, ...bounds) {
+ return invokeContentTask(browser, bounds, (x, y, width, height) => {
+ const { Layout: LayoutUtils } = ChromeUtils.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
+ );
+
+ return LayoutUtils.CSSToDevicePixels(content, x, y, width, height);
+ });
+}
+
+async function runTests(browser, accDoc) {
+ Services.prefs.setBoolPref("canvas.hitregions.enabled", true);
+
+ const offsetX = 20;
+ const offsetY = 40;
+ await invokeContentTask(browser, [], () =>
+ content.document.getElementById("hitcanvas").scrollIntoView(true)
+ );
+ const dpr = await getContentDPR(browser);
+
+ await invokeContentTask(browser, [offsetX, offsetY], (x, y) => {
+ const doc = content.document;
+ const element = doc.getElementById("hitcheck");
+ const context = doc.getElementById("hitcanvas").getContext("2d");
+ context.save();
+ context.font = "10px sans-serif";
+ context.textAlign = "left";
+ context.textBaseline = "middle";
+ const metrics = context.measureText(element.parentNode.textContent);
+ context.beginPath();
+ context.strokeStyle = "black";
+ context.rect(x - 5, y - 5, 10, 10);
+ context.stroke();
+ if (element.checked) {
+ context.fillStyle = "black";
+ context.fill();
+ }
+
+ context.fillText(element.parentNode.textContent, x + 5, y);
+ context.beginPath();
+ context.rect(x - 7, y - 7, 12 + metrics.width + 2, 14);
+
+ if (doc.activeElement == element) {
+ context.drawFocusIfNeeded(element);
+ }
+
+ context.addHitRegion({ control: element });
+ context.restore();
+ });
+
+ const hitcanvas = findAccessibleChildByID(accDoc, "hitcanvas");
+ const hitcheck = findAccessibleChildByID(accDoc, "hitcheck");
+ const [hitX, hitY /* hitWidth, hitHeight */] = Layout.getBounds(
+ hitcanvas,
+ dpr
+ );
+ const [deltaX, deltaY] = await convertCSSToDevicePixels(
+ browser,
+ offsetX,
+ offsetY
+ );
+
+ info("Test if we hit the region associated with the shadow dom checkbox.");
+ const tgtX = hitX + deltaX;
+ let tgtY = hitY + deltaY;
+ let hitAcc = accDoc.getDeepestChildAtPoint(tgtX, tgtY);
+ CommonUtils.isObject(hitAcc, hitcheck, `Hit match at (${tgtX}, ${tgtY}`);
+
+ info(
+ "Test that we don't hit the region associated with the shadow dom checkbox."
+ );
+ tgtY = hitY + deltaY * 2;
+ hitAcc = accDoc.getDeepestChildAtPoint(tgtX, tgtY);
+ CommonUtils.isObject(hitAcc, hitcanvas, `Hit match at (${tgtX}, ${tgtY}`);
+
+ Services.prefs.clearUserPref("canvas.hitregions.enabled");
+}
+
+addAccessibleTask(
+ `
+ <canvas id="hitcanvas">
+ <input id="hitcheck" type="checkbox"><label for="showA"> Show A </label>
+ </canvas>`,
+ 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..08596529ee
--- /dev/null
+++ b/accessible/tests/browser/hittest/browser_test_general.js
@@ -0,0 +1,123 @@
+/* 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);
+
+ info("Not specific case, child and deepchild testing.");
+ 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."
+ );
+
+ info(
+ "::MustPrune case (in this case childAtPoint doesn't look inside a " +
+ "textbox), point is inside of textbox."
+ );
+ const txt = findAccessibleChildByID(accDoc, "txt");
+ testChildAtPoint(dpr, 1, 1, txt, txt, txt);
+
+ info(
+ "::MustPrune case, point is outside of textbox accessible but is in document."
+ );
+ testChildAtPoint(dpr, -1, -1, txt, null, null);
+
+ info("::MustPrune case, point is outside of root accessible.");
+ testChildAtPoint(dpr, -10000, -10000, txt, null, null);
+
+ info("Not specific case, point is inside of btn accessible.");
+ const btn = findAccessibleChildByID(accDoc, "btn");
+ testChildAtPoint(dpr, 1, 1, btn, btn, btn);
+
+ info("Not specific case, point is outside of btn accessible.");
+ 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.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.jsm"
+ );
+
+ 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");
+ 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
+ );
+}
+
+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>
+ `,
+ runTests,
+ {
+ iframe: true,
+ remoteIframe: true,
+ // Ensure that all hittest elements are in view.
+ iframeAttrs: { style: "width: 600px; height: 600px;" },
+ }
+);
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..cb499a4fd9
--- /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");
+ testChildAtPoint(
+ dpr,
+ 1,
+ 1,
+ componentAcc,
+ componentAcc.firstChild,
+ componentAcc.firstChild
+ );
+
+ componentAcc = findAccessibleChildByID(accDoc, "component2");
+ 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_zoom.js b/accessible/tests/browser/hittest/browser_test_zoom.js
new file mode 100644
index 0000000000..2ea330fb74
--- /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.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
+ );
+
+ 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..0d7eb336ca
--- /dev/null
+++ b/accessible/tests/browser/hittest/browser_test_zoom_text.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 if getOffsetAtPoint returns the given text offset at given coordinates.
+ */
+function testOffsetAtPoint(hyperText, x, y, coordType, expectedOffset) {
+ is(
+ hyperText.getOffsetAtPoint(x, y, coordType),
+ expectedOffset,
+ `Wrong offset at given point (${x}, ${y}) for ${prettyName(hyperText)}`
+ );
+}
+
+async function runTests(browser, accDoc) {
+ const expectedLength = await invokeContentTask(browser, [], () => {
+ const { CommonUtils } = ChromeUtils.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.jsm"
+ );
+ const hyperText = CommonUtils.getNode("paragraph", content.document);
+
+ return 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)
+ );
+
+ testOffsetAtPoint(
+ hyperText,
+ x + width / 2,
+ y + height / 2,
+ COORDTYPE_SCREEN_RELATIVE,
+ expectedLength
+ );
+
+ await invokeContentTask(browser, [], () => {
+ const { Layout } = ChromeUtils.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
+ );
+
+ 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)
+ );
+
+ testOffsetAtPoint(
+ hyperText,
+ x + width / 2,
+ y + height / 2,
+ COORDTYPE_SCREEN_RELATIVE,
+ expectedLength
+ );
+}
+
+addAccessibleTask(
+ `<p id="paragraph" style="font-family: monospace;">Болтали две сороки</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..f06467da7c
--- /dev/null
+++ b/accessible/tests/browser/hittest/head.js
@@ -0,0 +1,89 @@
+/* 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.
+/* import-globals-from ../shared-head.js */
+
+/* exported CommonUtils, testChildAtPoint, Layout, hitTest */
+
+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.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.jsm"
+);
+
+const { Layout } = ChromeUtils.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
+);
+
+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.
+ }
+
+ return null;
+}
+
+function testChildAtPoint(dpr, x, y, container, child, grandChild) {
+ const [containerX, containerY] = Layout.getBounds(container, dpr);
+ x += containerX;
+ y += containerY;
+
+ CommonUtils.isObject(
+ getChildAtPoint(container, x, y, false),
+ child,
+ `Wrong direct child accessible at the point (${x}, ${y}) of ${CommonUtils.prettyName(
+ container
+ )}`
+ );
+
+ CommonUtils.isObject(
+ getChildAtPoint(container, x, y, true),
+ grandChild,
+ `Wrong deepest child accessible at the point (${x}, ${y}) of ${CommonUtils.prettyName(
+ container
+ )}`
+ );
+}
+
+/**
+ * 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;
+
+ CommonUtils.isObject(
+ getChildAtPoint(container, x, y, false),
+ child,
+ `Wrong direct child of ${prettyName(container)}`
+ );
+
+ CommonUtils.isObject(
+ getChildAtPoint(container, x, y, true),
+ grandChild,
+ `Wrong deepest child of ${prettyName(container)}`
+ );
+}
diff --git a/accessible/tests/browser/mac/browser.ini b/accessible/tests/browser/mac/browser.ini
new file mode 100644
index 0000000000..ddcfe0e081
--- /dev/null
+++ b/accessible/tests/browser/mac/browser.ini
@@ -0,0 +1,50 @@
+[DEFAULT]
+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
+
+[browser_app.js]
+[browser_aria_current.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]
+skip-if = os == 'mac' && !debug #1648813
+[browser_text_basics.js]
+[browser_text_input.js]
+skip-if = os == 'mac' && debug # Bug 1664577
+[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] \ No newline at end of file
diff --git a/accessible/tests/browser/mac/browser_app.js b/accessible/tests/browser/mac/browser_app.js
new file mode 100644
index 0000000000..e57555b556
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_app.js
@@ -0,0 +1,292 @@
+/* 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 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)
+ is(rootChildCount(), 5, "Root with no popups has 5 children");
+
+ // Open a context menu
+ const menu = document.getElementById("contentAreaContextMenu");
+ EventUtils.synthesizeMouseAtCenter(document.body, {
+ type: "contextmenu",
+ });
+ await waitForMacEvent("AXMenuOpened");
+
+ // Now root has 6 children
+ is(rootChildCount(), 6, "Root has 6 children");
+
+ // Close context menu
+ let closed = waitForMacEvent("AXMenuClosed", "contentAreaContextMenu");
+ EventUtils.synthesizeKey("KEY_Escape");
+ await BrowserTestUtils.waitForPopupEvent(menu, "hidden");
+ await closed;
+
+ // We're back to 5
+ is(rootChildCount(), 5, "Root has 5 children");
+
+ // Open site identity popup
+ document.getElementById("identity-box").click();
+ const identityPopup = document.getElementById("identity-popup");
+ await BrowserTestUtils.waitForPopupEvent(identityPopup, "shown");
+
+ // Now root has 6 children
+ is(rootChildCount(), 6, "Root has 6 children");
+
+ // Close popup
+ EventUtils.synthesizeKey("KEY_Escape");
+ await BrowserTestUtils.waitForPopupEvent(identityPopup, "hidden");
+
+ // We're back to 5
+ is(rootChildCount(), 5, "Root has 5 children");
+ }
+ );
+});
+
+/**
+ * Tests for location bar
+ */
+add_task(async () => {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ 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 () => {
+ 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;
+
+ // 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");
+ // menu contains 14 items when containers disabled, 15 items otherwise
+ const expectedChildCount = hasContainers ? 15 : 14;
+ is(
+ menuChildren.length,
+ expectedChildCount,
+ "Context menu on link contains 14 or 15 items depending on release"
+ );
+ // items at indicies 4, 10, and 12 are the splitters when containers exist
+ // everything else should be a menu item, otherwise indicies of splitters are
+ // 3, 9, and 11
+ const splitterIndicies = hasContainers ? [4, 10, 12] : [3, 9, 11];
+ 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..c1b75b8318
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_aria_controls_flowto.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";
+
+/**
+ * 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 isARIAControls = (id, expectedIds) =>
+ Assert.deepEqual(
+ getNativeInterface(accDoc, id)
+ .getAttributeValue("AXARIAControls")
+ .map(e => e.getAttributeValue("AXDOMIdentifier")),
+ expectedIds,
+ `"${id}" has correct AXARIAControls`
+ );
+
+ isARIAControls("info-button", ["info"]);
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("info-button")
+ .setAttribute("aria-controls", "info more-info");
+ });
+
+ isARIAControls("info-button", ["info", "more-info"]);
+ }
+);
+
+/**
+ * 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) => {
+ const isLinkedUIElements = (id, expectedIds) =>
+ Assert.deepEqual(
+ getNativeInterface(accDoc, id)
+ .getAttributeValue("AXLinkedUIElements")
+ .map(e => e.getAttributeValue("AXDOMIdentifier")),
+ expectedIds,
+ `"${id}" has correct AXARIAControls`
+ );
+
+ isLinkedUIElements("info-button", ["info"]);
+
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("info-button")
+ .setAttribute("aria-flowto", "info more-info");
+ });
+
+ isLinkedUIElements("info-button", ["info", "more-info"]);
+ }
+);
+
+/**
+ * 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) => {
+ const isLinkedUIElements = (id, expectedIds) =>
+ Assert.deepEqual(
+ getNativeInterface(accDoc, id)
+ .getAttributeValue("AXLinkedUIElements")
+ .map(e => e.getAttributeValue("AXDOMIdentifier")),
+ expectedIds,
+ `"${id}" has correct AXARIAControls`
+ );
+
+ isLinkedUIElements("dog-radio", ["cat-radio", "dog-radio", "info"]);
+ }
+);
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..0c5ff08a0a
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_aria_current.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 */
+/* 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"
+ );
+
+ let stateChanged = waitForEvent(EVENT_STATE_CHANGE, "one");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("one")
+ .setAttribute("aria-current", "step");
+ });
+ await stateChanged;
+
+ is(
+ one.getAttributeValue("AXARIACurrent"),
+ "step",
+ "Correct aria-current for #one"
+ );
+
+ 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_haspopup.js b/accessible/tests/browser/mac/browser_aria_haspopup.js
new file mode 100644
index 0000000000..36bff246db
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_aria_haspopup.js
@@ -0,0 +1,330 @@
+/* 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"
+ );
+
+ attrChanged = waitForEvent(EVENT_STATE_CHANGE, "menu");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("menu")
+ .setAttribute("aria-haspopup", "true");
+ });
+ await attrChanged;
+
+ is(
+ 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;
+
+ is(
+ 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"
+ );
+
+ attrChanged = waitForEvent(EVENT_STATE_CHANGE, "listbox");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("listbox")
+ .setAttribute("aria-haspopup", "true");
+ });
+ await attrChanged;
+
+ is(
+ 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"
+ );
+
+ attrChanged = waitForEvent(EVENT_STATE_CHANGE, "tree");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("tree")
+ .setAttribute("aria-haspopup", "true");
+ });
+ await attrChanged;
+
+ is(
+ 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"
+ );
+
+ attrChanged = waitForEvent(EVENT_STATE_CHANGE, "grid");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("grid")
+ .setAttribute("aria-haspopup", "true");
+ });
+ await attrChanged;
+
+ is(
+ 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"
+ );
+
+ attrChanged = waitForEvent(EVENT_STATE_CHANGE, "dialog");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("dialog")
+ .setAttribute("aria-haspopup", "true");
+ });
+ await attrChanged;
+
+ is(
+ 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_details_summary.js b/accessible/tests/browser/mac/browser_details_summary.js
new file mode 100644
index 0000000000..62183afef5
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_details_summary.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 */
+/* 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>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 reorder = waitForEvent(EVENT_REORDER, "details");
+ summary.performAction("AXPress");
+ is(summary.getAttributeValue("AXExpanded"), 1, "Summary is collapsed");
+ // The reorder gecko event notifies us of a tree change.
+ await reorder;
+
+ detailsChildren = details.getAttributeValue("AXChildren");
+ is(detailsChildren.length, 2, "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_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..0ed45391e9
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_label_title.js
@@ -0,0 +1,79 @@
+/* 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(
+ `<article id="article" aria-label="Hello world"></article>`,
+ async (browser, accDoc) => {
+ let article = getNativeInterface(accDoc, "article");
+ is(article.getAttributeValue("AXTitle"), "Hello world");
+ let evt = waitForMacEvent("AXTitleChanged", "article");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("article")
+ .setAttribute("aria-label", "Hello universe");
+ });
+ await evt;
+ is(article.getAttributeValue("AXTitle"), "Hello universe");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_link.js b/accessible/tests/browser/mac/browser_link.js
new file mode 100644
index 0000000000..4ae128fbf6
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_link.js
@@ -0,0 +1,125 @@
+/* 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.defineModuleGetter(
+ this,
+ "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm"
+);
+
+/**
+ * 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");
+
+ 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();
+ }
+);
+
+/**
+ * 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 = waitForEvent(EVENT_STATE_CHANGE, "link1");
+ 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 = waitForEvent(EVENT_STATE_CHANGE, "link2");
+ 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 = waitForEvent(EVENT_STATE_CHANGE, "link3");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document
+ .getElementById("link3")
+ .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");
+ }
+);
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..dd037c01ee
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_menulist.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/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 }
+);
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..1bb3c56bad
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_outline.js
@@ -0,0 +1,459 @@
+/* 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 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);
+ }
+);
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..9916a7cb12
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_popupbutton.js
@@ -0,0 +1,153 @@
+/* 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 three events happen in quick succession when select is pressed
+ let events = Promise.all([
+ waitForMacEvent("AXMenuOpened"),
+ waitForMacEvent("AXSelectedChildrenChanged"),
+ waitForMacEvent("AXFocusedUIElementChanged"),
+ ]);
+ 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"),
+ ]);
+ menuItem.performAction("AXPress");
+ let [, newFocus] = await events;
+ is(
+ newFocus.getAttributeValue("AXRole"),
+ "AXPopUpButton",
+ "Newly focused element is AXPopupButton"
+ );
+ 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..7158dfcb30
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_range.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";
+
+/* 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=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"
+ );
+ }
+);
+
+/**
+ * Test input[type=range]
+ */
+addAccessibleTask(
+ `<input type="number" value="11" id="number">`,
+ 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"), "12", "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"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_required.js b/accessible/tests/browser/mac/browser_required.js
new file mode 100644
index 0000000000..5f552d44c3
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_required.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/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"
+ );
+
+ // 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"
+ );
+
+ // 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..5f07961486
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_roles_elements.js
@@ -0,0 +1,309 @@
+/* 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>
+
+ <!-- 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>
+
+ <!-- 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");
+
+ // Text boxes
+ testRoleAndSubRole(accDoc, "textbox_multiline", "AXTextArea");
+ testRoleAndSubRole(accDoc, "textbox_singleline", "AXTextArea");
+ 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");
+
+ // 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..87f5e6c04f
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_rootgroup.js
@@ -0,0 +1,196 @@
+/* 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"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_rotor.js b/accessible/tests/browser/mac/browser_rotor.js
new file mode 100644
index 0000000000..8b0d12de9d
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_rotor.js
@@ -0,0 +1,1697 @@
+/* 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.defineModuleGetter(
+ this,
+ "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm"
+);
+
+/**
+ * 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 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>
+ `,
+ 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(3, controlsCount, "Found 3 controls");
+
+ const controls = webArea.getParameterizedAttributeValue(
+ "AXUIElementsForSearchPredicate",
+ NSDictionary(searchPred)
+ );
+
+ const spin = getNativeInterface(accDoc, "spinbutton");
+ const details = getNativeInterface(accDoc, "details");
+ const tree = getNativeInterface(accDoc, "tree");
+
+ 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"
+ );
+ }
+);
+
+/**
+ * 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");
+
+ 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..29cd326bdd
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_selectables.js
@@ -0,0 +1,336 @@
+/* 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);
+ one.setAttributeValue("AXSelected", false);
+ is(select.getAttributeValue("AXSelectedChildren").length, 0);
+
+ three.setAttributeValue("AXSelected", true);
+ is(select.getAttributeValue("AXSelectedChildren").length, 1);
+ ok(getSelectedIds(select).includes("three"), "'three' is selected");
+
+ select.setAttributeValue("AXSelectedChildren", [one, two]);
+ Assert.deepEqual(
+ getSelectedIds(select),
+ ["one", "two"],
+ "one and two are selected"
+ );
+
+ select.setAttributeValue("AXSelectedChildren", [three, two, four]);
+ Assert.deepEqual(
+ getSelectedIds(select),
+ ["two", "three"],
+ "two and three are selected, four is disabled so it's not"
+ );
+
+ 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);
+
+ three.setAttributeValue("AXSelected", true);
+ is(select.getAttributeValue("AXSelectedChildren").length, 1);
+ ok(getSelectedIds(select).includes("three"), "'three' is selected");
+
+ select.setAttributeValue("AXSelectedChildren", [one, two]);
+ Assert.deepEqual(
+ getSelectedIds(select),
+ ["one", "two"],
+ "one and two are selected"
+ );
+
+ select.setAttributeValue("AXSelectedChildren", [three, two, four]);
+ Assert.deepEqual(
+ getSelectedIds(select),
+ ["two", "three"],
+ "two and three are selected, four is disabled so it's not"
+ );
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_table.js b/accessible/tests/browser/mac/browser_table.js
new file mode 100644
index 0000000000..34aeb87a17
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_table.js
@@ -0,0 +1,281 @@
+/* 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"
+ );
+ }
+);
+
+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);
+ }
+);
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..de66ebf639
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_text_basics.js
@@ -0,0 +1,282 @@
+/* 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`
+ );
+}
+
+// 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
+ );
+
+ let count = 0;
+
+ // Iterate forward with "AXNextTextMarkerForTextMarker"
+ let marker = macDoc.getAttributeValue("AXStartTextMarker");
+ while (marker) {
+ let index = macDoc.getParameterizedAttributeValue(
+ "AXIndexForTextMarker",
+ marker
+ );
+ is(
+ index,
+ count,
+ `Correct index in "AXNextTextMarkerForTextMarker": ${count}`
+ );
+
+ testWords(
+ macDoc,
+ marker,
+ `At index ${count}`,
+ ...expectedMarkerValues[count].words
+ );
+ testLines(
+ macDoc,
+ marker,
+ `At index ${count}`,
+ ...expectedMarkerValues[count].lines
+ );
+ testUIElement(
+ macDoc,
+ marker,
+ `At index ${count}`,
+ ...expectedMarkerValues[count].element
+ );
+ testParagraph(
+ macDoc,
+ marker,
+ `At index ${count}`,
+ expectedMarkerValues[count].paragraph
+ );
+ testStyleRun(
+ macDoc,
+ marker,
+ `At index ${count}`,
+ expectedMarkerValues[count].style
+ );
+
+ let prevMarker = marker;
+ marker = macDoc.getParameterizedAttributeValue(
+ "AXNextTextMarkerForTextMarker",
+ marker
+ );
+
+ if (marker) {
+ let range = macDoc.getParameterizedAttributeValue(
+ "AXTextMarkerRangeForUnorderedTextMarkers",
+ [prevMarker, marker]
+ );
+ is(
+ macDoc.getParameterizedAttributeValue(
+ "AXLengthForTextMarkerRange",
+ range
+ ),
+ 1,
+ "marker moved one character"
+ );
+ }
+
+ count++;
+ }
+
+ // Use "AXTextMarkerForIndex" to retrieve all text markers
+ for (let i = 0; i < count; i++) {
+ marker = macDoc.getParameterizedAttributeValue("AXTextMarkerForIndex", i);
+ let index = macDoc.getParameterizedAttributeValue(
+ "AXIndexForTextMarker",
+ marker
+ );
+ is(index, i, `Correct index in "AXTextMarkerForIndex": ${i}`);
+ }
+
+ ok(
+ !macDoc.getParameterizedAttributeValue(
+ "AXNextTextMarkerForTextMarker",
+ marker
+ ),
+ "Iterated through all markers"
+ );
+
+ // Iterate backward with "AXPreviousTextMarkerForTextMarker"
+ marker = macDoc.getAttributeValue("AXEndTextMarker");
+ while (marker) {
+ count--;
+ let index = macDoc.getParameterizedAttributeValue(
+ "AXIndexForTextMarker",
+ marker
+ );
+ is(
+ index,
+ count,
+ `Correct index in "AXPreviousTextMarkerForTextMarker": ${count}`
+ );
+ marker = macDoc.getParameterizedAttributeValue(
+ "AXPreviousTextMarkerForTextMarker",
+ marker
+ );
+ }
+
+ is(count, 0, "Iterated backward through all text markers");
+}
+
+addAccessibleTask("mac/doc_textmarker_test.html", async (browser, accDoc) => {
+ const expectedMarkerValues = await SpecialPowers.spawn(
+ browser,
+ [],
+ async () => {
+ return content.wrappedJSObject.EXPECTED;
+ }
+ );
+
+ testMarkerIntegrity(accDoc, expectedMarkerValues);
+});
+
+// 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");
+ }
+);
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..b5aa9511a1
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_text_input.js
@@ -0,0 +1,412 @@
+/* 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`
+ );
+}
+
+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 focusIntoInputAndType(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 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_Home",
+ { shiftKey: true },
+ selectionId,
+ "hello ",
+ {
+ AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
+ AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
+ AXTextSelectionGranularity: AXTextSelectionGranularityWord,
+ }
+ );
+ 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");
+ }
+);
+
+// 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 text input in iframe
+addAccessibleTask(
+ `<a href="#">link</a> <input id="input">`,
+ async (browser, accDoc) => {
+ await focusIntoInputAndType(accDoc, "input");
+ },
+ { iframe: true }
+);
+
+// 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");
+});
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..c7c7a5c319
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_text_leaf.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 });
+
+/**
+ * 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"),
+ "world ",
+ "Second child is world text"
+ );
+ // we have a trailing space here due to bug 1577028
+});
+
+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, this ", "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");
+ }
+);
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..17c292fd87
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_toggle_radio_check.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";
+
+/* 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");
+ is(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;
+ is(checkbox.getAttributeValue("AXValue"), 1, "Correct checked value");
+
+ evt = waitForMacEvent("AXValueChanged", "vehicle");
+ checkbox.performAction("AXPress");
+ await evt;
+ is(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");
+ is(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;
+ is(toggle.getAttributeValue("AXValue"), 1, "Correct checked value");
+
+ evt = waitForMacEvent("AXValueChanged", "toggle");
+ toggle.performAction("AXPress");
+ await evt;
+ is(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");
+ is(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;
+ is(checkbox.getAttributeValue("AXValue"), 1, "Correct checked value");
+
+ evt = waitForMacEvent("AXValueChanged", "checkbox");
+ 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");
+ is(huey.getAttributeValue("AXValue"), 1, "Correct initial value for huey");
+
+ let dewey = getNativeInterface(accDoc, "dewey");
+ is(
+ 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;
+ is(
+ dewey.getAttributeValue("AXValue"),
+ 1,
+ "Correct checked value for dewey"
+ );
+ is(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..c1e68dac1b
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_webarea.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/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+// Test web area role and AXLoadComplete event
+addAccessibleTask(``, async (browser, accDoc) => {
+ let evt = waitForMacEvent("AXLoadComplete");
+ 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("AXDescription"),
+ "webarea test",
+ "test has correct label"
+ );
+
+ 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"),
+ waitForMacEvent("AXLoadComplete"),
+ ]);
+ await SpecialPowers.spawn(browser, [], () => {
+ const iframe = content.document.createElement("iframe");
+ iframe.src = "data:text/html,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"),
+ "data:text/html,hello world",
+ "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..9de6f8b81c
--- /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.setAttribute("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..8a73c95a35
--- /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: ["Bob Loblaw Lobs Law Bomb",
+ "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: ["I love all of my children equally",
+ "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: ["This is the best free scrapbooking class I have ever taken",
+ "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 Fried cheese 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 Popcorn shrimp with 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: ["\u2022 Chicken fingers with spicy club sauce",
+ "\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'", "'"],
+ 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: ["Do not order the Skip's Scramble",
+ "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 ", "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: ["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 ", "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: ["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, ", "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: ["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. ", "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: ["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 ", "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: ["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.", "Army.", "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: ["Army.", "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"],
+ 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: "deceived you",
+ lines: ["I deceived you, mom.", "I deceived you, mom.", "I deceived you, mom."],
+ words: ["you", ""],
+ 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..7f70bd4a4e
--- /dev/null
+++ b/accessible/tests/browser/mac/head.js
@@ -0,0 +1,114 @@
+/* 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,
+ NSRange, NSDictionary, stringForRange, AXTextStateChangeTypeEdit,
+ AXTextEditTypeDelete, AXTextEditTypeTyping, AXTextStateChangeTypeSelectionMove,
+ AXTextStateChangeTypeSelectionExtend, AXTextSelectionDirectionUnknown,
+ AXTextSelectionDirectionPrevious, AXTextSelectionDirectionNext,
+ AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown,
+ AXTextSelectionGranularityCharacter, AXTextSelectionGranularityWord */
+
+// Load the shared-head file first.
+/* import-globals-from ../shared-head.js */
+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 AXTextSelectionDirectionPrevious = 3;
+const AXTextSelectionDirectionNext = 4;
+const AXTextSelectionDirectionDiscontiguous = 5;
+
+// AXTextSelectionGranularity enum values
+const AXTextSelectionGranularityUnknown = 0;
+const AXTextSelectionGranularityCharacter = 1;
+const AXTextSelectionGranularityWord = 2;
+
+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 "";
+ }
+
+ return macDoc.getParameterizedAttributeValue(
+ "AXStringForTextMarkerRange",
+ range
+ );
+}
diff --git a/accessible/tests/browser/scroll/browser.ini b/accessible/tests/browser/scroll/browser.ini
new file mode 100644
index 0000000000..1e24426535
--- /dev/null
+++ b/accessible/tests/browser/scroll/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+
+[browser_test_zoom_text.js]
+skip-if = e10s && os == 'win' # bug 1372296
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..be58361c7b
--- /dev/null
+++ b/accessible/tests/browser/scroll/browser_test_zoom_text.js
@@ -0,0 +1,150 @@
+/* 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 waitForContentPaint(browser) {
+ await SpecialPowers.spawn(browser, [], () => {
+ return new Promise(function(r) {
+ content.requestAnimationFrame(() => content.setTimeout(r));
+ });
+ });
+}
+
+async function runTests(browser, accDoc) {
+ await loadContentScripts(browser, "Layout.jsm");
+
+ 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..672aa46171
--- /dev/null
+++ b/accessible/tests/browser/scroll/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.
+/* import-globals-from ../shared-head.js */
+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/shared-head.js b/accessible/tests/browser/shared-head.js
new file mode 100644
index 0000000000..b848e09932
--- /dev/null
+++ b/accessible/tests/browser/shared-head.js
@@ -0,0 +1,779 @@
+/* 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/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 */
+
+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.
+ */
+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() {
+ const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
+ );
+
+ 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 of scripts) {
+ let contentScript;
+ if (typeof script === "string") {
+ // If script string includes a .jsm extention, assume it is a module path.
+ contentScript = `${CURRENT_DIR}${script}`;
+ } else {
+ // Script is a object that has { dir, name } format.
+ contentScript = `${script.dir}${script.name}`;
+ }
+
+ 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], async _contentScript => {
+ ChromeUtils.import(_contentScript, content.window);
+ });
+ 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) {
+ 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",
+ `<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 = `<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(
+ `<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,
+ url,
+ },
+ async function(browser) {
+ registerCleanupFunction(() => {
+ if (browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ if (tab && !tab.closing && tab.linkedBrowser) {
+ gBrowser.removeTab(tab);
+ }
+ }
+ });
+
+ await SimpleTest.promiseFocus(browser);
+ await loadContentScripts(browser, "Common.jsm");
+
+ 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 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. This is only valid if url is a
+ * relative URL to a XUL document, not a markup snippet. topLevel
+ * should usually be set to false in this case, since XUL documents
+ * don't work in the content process.
+ * - {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") {
+ const acc = findAccessibleChildByID(accDoc, id);
+ if (acc.firstChild) {
+ return;
+ }
+
+ const onReorder = waitForEvent(EVENT_REORDER, id);
+ // Wave over image map
+ await invokeContentTask(browser, [id], contentId => {
+ const { ContentTaskUtils } = ChromeUtils.import(
+ "resource://testing-common/ContentTaskUtils.jsm"
+ );
+ 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.import(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
+ );
+
+ return LayoutUtils.getBoundsForDOMElm(contentId, content.document);
+ });
+}
diff --git a/accessible/tests/browser/states/browser.ini b/accessible/tests/browser/states/browser.ini
new file mode 100644
index 0000000000..726c7f4f03
--- /dev/null
+++ b/accessible/tests/browser/states/browser.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/browser/*.jsm
+
+[browser_test_link.js]
+skip-if = verify
+[browser_test_visibility.js]
+[browser_deck_has_out_of_process_iframe.js]
+skip-if = (!debug && webrender && (os == 'win')) # bug 1584037
+support-files =
+ target.html
+ test_deck_has_out_of_process_iframe.xhtml
+[browser_offscreen_element_in_out_of_process_iframe.js]
+skip-if = (webrender && os == 'win') # bug 1580706
diff --git a/accessible/tests/browser/states/browser_deck_has_out_of_process_iframe.js b/accessible/tests/browser/states/browser_deck_has_out_of_process_iframe.js
new file mode 100644
index 0000000000..155ac4c954
--- /dev/null
+++ b/accessible/tests/browser/states/browser_deck_has_out_of_process_iframe.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";
+
+const { Preferences } = ChromeUtils.import(
+ "resource://gre/modules/Preferences.jsm"
+);
+
+const DIRPATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ ""
+);
+const parentPATH = DIRPATH + "test_deck_has_out_of_process_iframe.xhtml";
+const iframePATH = DIRPATH + "target.html";
+
+// XXX: Using external files here since using data URL breaks something, e.g. it
+// makes querying the second iframe in a hidden deck failure for some reasons.
+const parentURL = `http://example.com/${parentPATH}`;
+const iframeURL = `http://example.org/${iframePATH}`;
+
+add_task(async function() {
+ if (Preferences.locked("fission.autostart")) {
+ ok(
+ true,
+ "fission.autostart pref is locked on this channel which means " +
+ "we don't need to run the following tests"
+ );
+ return;
+ }
+
+ const win = await BrowserTestUtils.openNewBrowserWindow({
+ fission: true,
+ });
+
+ try {
+ const browser = win.gBrowser.selectedTab.linkedBrowser;
+
+ BrowserTestUtils.loadURI(browser, parentURL);
+ await BrowserTestUtils.browserLoaded(browser, false, parentURL);
+
+ async function setupIFrame(id, url) {
+ const iframe = content.document.getElementById(id);
+
+ iframe.contentWindow.location = url;
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ });
+
+ return iframe.browsingContext;
+ }
+
+ async function spawnSelectDeck(index) {
+ async function selectDeck(i) {
+ const deck = content.document.getElementById("deck");
+
+ deck.setAttribute("selectedIndex", i);
+ await new Promise(resolve => {
+ content.window.addEventListener("MozAfterPaint", resolve, {
+ once: true,
+ });
+ });
+ return deck.selectedIndex;
+ }
+ await SpecialPowers.spawn(browser, [index], selectDeck);
+
+ await waitForIFrameUpdates();
+ }
+
+ const firstIFrame = await SpecialPowers.spawn(
+ browser,
+ ["first", iframeURL],
+ setupIFrame
+ );
+ const secondIFrame = await SpecialPowers.spawn(
+ browser,
+ ["second", iframeURL],
+ setupIFrame
+ );
+
+ await waitForIFrameUpdates();
+
+ await spawnTestStates(
+ firstIFrame,
+ "target",
+ 0,
+ nsIAccessibleStates.STATE_OFFSCREEN
+ );
+ // Disable the check for the target element in the unselected pane of the
+ // deck, this should be fixed by bug 1578932.
+ // Note: As of now we can't use todo in the script transfered into the
+ // out-of-process.
+ //await spawnTestStates(
+ // secondIFrame,
+ // "target",
+ // nsIAccessibleStates.STATE_OFFSCREEN,
+ // nsIAccessibleStates.STATE_INVISIBLE
+ //);
+
+ // Select the second panel.
+ await spawnSelectDeck(1);
+ await spawnTestStates(
+ firstIFrame,
+ "target",
+ nsIAccessibleStates.STATE_OFFSCREEN,
+ nsIAccessibleStates.STATE_INVISIBLE
+ );
+ await spawnTestStates(
+ secondIFrame,
+ "target",
+ 0,
+ nsIAccessibleStates.STATE_OFFSCREEN
+ );
+
+ // Select the first panel again.
+ await spawnSelectDeck(0);
+ await spawnTestStates(
+ firstIFrame,
+ "target",
+ 0,
+ nsIAccessibleStates.STATE_OFFSCREEN
+ );
+ await spawnTestStates(
+ secondIFrame,
+ "target",
+ nsIAccessibleStates.STATE_OFFSCREEN,
+ nsIAccessibleStates.STATE_INVISIBLE
+ );
+ } finally {
+ await BrowserTestUtils.closeWindow(win);
+ }
+});
diff --git a/accessible/tests/browser/states/browser_offscreen_element_in_out_of_process_iframe.js b/accessible/tests/browser/states/browser_offscreen_element_in_out_of_process_iframe.js
new file mode 100644
index 0000000000..289d3fec9a
--- /dev/null
+++ b/accessible/tests/browser/states/browser_offscreen_element_in_out_of_process_iframe.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 parentURL =
+ "data:text/html;charset=utf-8," +
+ '<div id="scroller" style="width: 300px; height: 300px; overflow-y: scroll; overflow-x: hidden;">' +
+ ' <div style="width: 100%; height: 1000px;"></div>' +
+ ' <iframe frameborder="0"/>' +
+ "</div>";
+const iframeURL =
+ "data:text/html;charset=utf-8," +
+ "<style>" +
+ " html,body {" +
+ " /* Convenient for calculation of element positions */" +
+ " margin: 0;" +
+ " padding: 0;" +
+ " }" +
+ "</style>" +
+ '<div id="target" style="width: 100px; height: 100px;">target</div>';
+
+add_task(async function() {
+ const win = await BrowserTestUtils.openNewBrowserWindow({
+ fission: true,
+ });
+
+ try {
+ const browser = win.gBrowser.selectedTab.linkedBrowser;
+
+ BrowserTestUtils.loadURI(browser, parentURL);
+ await BrowserTestUtils.browserLoaded(browser, false, parentURL);
+
+ async function setup(url) {
+ const iframe = content.document.querySelector("iframe");
+
+ iframe.contentWindow.location = url;
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ });
+
+ return iframe.browsingContext;
+ }
+
+ async function scrollTo(x, y) {
+ await SpecialPowers.spawn(browser, [x, y], async (scrollX, scrollY) => {
+ const scroller = content.document.getElementById("scroller");
+ scroller.scrollTo(scrollX, scrollY);
+ await new Promise(resolve => {
+ scroller.addEventListener("scroll", resolve, { once: true });
+ });
+ });
+ await waitForIFrameUpdates();
+ }
+
+ // Setup an out-of-process iframe which is initially scrolled out.
+ const iframe = await SpecialPowers.spawn(browser, [iframeURL], setup);
+
+ await waitForIFrameA11yReady(iframe);
+ await spawnTestStates(
+ iframe,
+ "target",
+ nsIAccessibleStates.STATE_OFFSCREEN,
+ nsIAccessibleStates.STATE_INVISIBLE
+ );
+
+ // Scroll the iframe into view and the target element is also visible but
+ // the visible area height is 11px.
+ await scrollTo(0, 711);
+ await spawnTestStates(
+ iframe,
+ "target",
+ nsIAccessibleStates.STATE_OFFSCREEN,
+ nsIAccessibleStates.STATE_INVISIBLE
+ );
+
+ // Scroll to a position where the visible height is 13px.
+ await scrollTo(0, 713);
+ await spawnTestStates(
+ iframe,
+ "target",
+ 0,
+ nsIAccessibleStates.STATE_OFFSCREEN
+ );
+
+ // Scroll the iframe out again.
+ await scrollTo(0, 0);
+ await spawnTestStates(
+ iframe,
+ "target",
+ nsIAccessibleStates.STATE_OFFSCREEN,
+ nsIAccessibleStates.STATE_INVISIBLE
+ );
+ } finally {
+ await BrowserTestUtils.closeWindow(win);
+ }
+});
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..f747a69971
--- /dev/null
+++ b/accessible/tests/browser/states/browser_test_link.js
@@ -0,0 +1,47 @@
+/* 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 }
+);
+
+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 newWinOpened = BrowserTestUtils.waitForNewWindow();
+
+ await BrowserTestUtils.synthesizeMouse(
+ "#link_traversed",
+ 1,
+ 1,
+ { shiftKey: true },
+ browser
+ );
+
+ await onStateChanged;
+ testStates(getAcc("link_traversed"), STATE_TRAVERSED);
+
+ let newWin = await newWinOpened;
+ await BrowserTestUtils.closeWindow(newWin);
+}
+
+/**
+ * Test caching of accessible object states
+ */
+addAccessibleTask(
+ `
+ <a id="link_traversed" href="http://www.example.com" target="_top">
+ example.com
+ </a>`,
+ runTests
+);
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..572c6f57d3
--- /dev/null
+++ b/accessible/tests/browser/states/browser_test_visibility.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 */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+async function runTest(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ testStates(getAcc("div"), 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+
+ let input = getAcc("input_scrolledoff");
+ testStates(input, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+
+ // scrolled off item (twice)
+ let lastLi = getAcc("li_last");
+ testStates(lastLi, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+
+ // scroll into view the item
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.getElementById("li_last").scrollIntoView(true);
+ });
+ testStates(lastLi, 0, 0, STATE_OFFSCREEN | STATE_INVISIBLE);
+
+ // first item is scrolled off now (testcase for bug 768786)
+ let firstLi = getAcc("li_first");
+ testStates(firstLi, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+
+ let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ // Accessibles in background tab should have offscreen state and no
+ // invisible state.
+ testStates(getAcc("div"), STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+ 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>`,
+ runTest
+);
diff --git a/accessible/tests/browser/states/head.js b/accessible/tests/browser/states/head.js
new file mode 100644
index 0000000000..f9d7393229
--- /dev/null
+++ b/accessible/tests/browser/states/head.js
@@ -0,0 +1,81 @@
+/* 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 */
+
+// Load the shared-head file first.
+/* import-globals-from ../shared-head.js */
+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 }
+);
+
+// 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
+ );
+}
diff --git a/accessible/tests/browser/states/target.html b/accessible/tests/browser/states/target.html
new file mode 100644
index 0000000000..0412c13d53
--- /dev/null
+++ b/accessible/tests/browser/states/target.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<div id="target">target</div>
diff --git a/accessible/tests/browser/states/test_deck_has_out_of_process_iframe.xhtml b/accessible/tests/browser/states/test_deck_has_out_of_process_iframe.xhtml
new file mode 100644
index 0000000000..72d93ca30a
--- /dev/null
+++ b/accessible/tests/browser/states/test_deck_has_out_of_process_iframe.xhtml
@@ -0,0 +1,10 @@
+<?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"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="XUL elements visibility states testing">
+<deck id="deck" selectedIndex="0">
+ <iframe id="first" flex="1"/>
+ <iframe id="second" flex="1"/>
+</deck>
+</window>
diff --git a/accessible/tests/browser/telemetry/browser.ini b/accessible/tests/browser/telemetry/browser.ini
new file mode 100644
index 0000000000..8a6381f19f
--- /dev/null
+++ b/accessible/tests/browser/telemetry/browser.ini
@@ -0,0 +1,3 @@
+[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..d9c48f5942
--- /dev/null
+++ b/accessible/tests/browser/telemetry/browser_HCM_telemetry.js
@@ -0,0 +1,177 @@
+/* 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-globals-from ../../../../browser/components/preferences/tests/head.js */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/components/preferences/tests/head.js",
+ this
+);
+
+ChromeUtils.import("resource://testing-common/TelemetryTestUtils.jsm", this);
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.display.document_color_use");
+ Services.prefs.clearUserPref("browser.display.permit_backplate");
+ Services.telemetry.clearEvents();
+});
+
+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");
+ dialogWin.document
+ .getElementById("ColorsDialog")
+ .getButton("accept")
+ .doCommand();
+ return dialogClosed;
+}
+
+function verifyBackplate(expectedValue) {
+ const snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ ok("a11y.backplate" in snapshot, "Backplate scalar must be present.");
+ is(
+ snapshot["a11y.backplate"],
+ expectedValue,
+ "Backplate scalar is logged as " + expectedValue
+ );
+}
+
+add_task(async function testInit() {
+ const dialogWin = await openColorsDialog();
+ const menulistHCM = dialogWin.document.getElementById("useDocumentColors");
+ if (AppConstants.platform == "win") {
+ is(
+ menulistHCM.value,
+ "0",
+ "HCM menulist should be set to only with HCM theme on startup for windows"
+ );
+
+ // Verify correct default value on windows
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ "a11y.theme",
+ "default",
+ false
+ );
+ } else {
+ is(
+ menulistHCM.value,
+ "1",
+ "HCM menulist should be set to never on startup for non-windows platforms"
+ );
+
+ // Verify correct default value on non-windows
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ "a11y.theme",
+ "always",
+ false
+ );
+ }
+
+ 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
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ "a11y.theme",
+ "never",
+ false
+ );
+
+ 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
+ );
+
+ 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
+ );
+
+ gBrowser.removeCurrentTab();
+});
+
+// XXX We're uable to assert scalars that are false using assertScalar util,
+// so we directly query the scalar in the scalar list below. (bug 1633883)
+
+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);
+});
diff --git a/accessible/tests/browser/tree/browser.ini b/accessible/tests/browser/tree/browser.ini
new file mode 100644
index 0000000000..8072f55c37
--- /dev/null
+++ b/accessible/tests/browser/tree/browser.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ head.js
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/browser/*.jsm
+
+[browser_aria_owns.js]
+skip-if = true || (verify && !debug && (os == 'linux')) #Bug 1445513
+[browser_searchbar.js]
+[browser_shadowdom.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..084ac83fea
--- /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_searchbar.js b/accessible/tests/browser/tree/browser_searchbar.js
new file mode 100644
index 0000000000..e41ba9819d
--- /dev/null
+++ b/accessible/tests/browser/tree/browser_searchbar.js
@@ -0,0 +1,52 @@
+"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]],
+ });
+
+ 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 = BrowserTestUtils.waitForEvent(popup, "popupshown", false);
+ searchbar.textbox.openPopup();
+ await promise;
+
+ promise = BrowserTestUtils.waitForEvent(popup, "popuphidden", false);
+ searchbar.textbox.closePopup();
+ await promise;
+
+ const 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);
+});
diff --git a/accessible/tests/browser/tree/browser_shadowdom.js b/accessible/tests/browser/tree/browser_shadowdom.js
new file mode 100644
index 0000000000..728284d3e2
--- /dev/null
+++ b/accessible/tests/browser/tree/browser_shadowdom.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";
+
+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"]);
+});
diff --git a/accessible/tests/browser/tree/head.js b/accessible/tests/browser/tree/head.js
new file mode 100644
index 0000000000..867a1b1417
--- /dev/null
+++ b/accessible/tests/browser/tree/head.js
@@ -0,0 +1,34 @@
+/* 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.
+/* import-globals-from ../shared-head.js */
+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/.eslintrc.js b/accessible/tests/crashtests/.eslintrc.js
new file mode 100644
index 0000000000..1a103a8d38
--- /dev/null
+++ b/accessible/tests/crashtests/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "plugin:mozilla/mochitest-test"
+ ],
+};
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/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/471493.xhtml b/accessible/tests/crashtests/471493.xhtml
new file mode 100644
index 0000000000..ca703f2aae
--- /dev/null
+++ b/accessible/tests/crashtests/471493.xhtml
@@ -0,0 +1,55 @@
+<?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="doTest();" class="reftest-wait">
+
+ <script type="application/javascript">
+ <![CDATA[
+ function addA11yLoadEvent(accService, func) {
+ function waitForDocLoad() {
+ setTimeout(() => {
+ let accDoc = accService.getAccessibleFor(document);
+ let state = {};
+ accDoc.getState(state, {});
+
+ if (state.value & SpecialPowers.Ci.nsIAccessibleStates.STATE_BUSY) {
+ return waitForDocLoad();
+ }
+ setTimeout(func, 0);
+ }, 0);
+ }
+
+ waitForDocLoad();
+ }
+
+ function doTest()
+ {
+ var accService = SpecialPowers.Cc["@mozilla.org/accessibilityService;1"].
+ getService(SpecialPowers.Ci.nsIAccessibilityService);
+
+ var treecol = document.getElementById("col");
+ var x = treecol.screenX;
+ var y = treecol.screenY;
+
+ var tree = document.getElementById("tree");
+ addA11yLoadEvent(accService, () => {
+ var treeAcc = accService.getAccessibleFor(tree);
+ treeAcc.getChildAtPoint(x + 1, y + 1);
+ document.documentElement.removeAttribute("class");
+ });
+ }
+ ]]>
+ </script>
+
+ <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>
+
+</window>
+
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..c29365cef3
--- /dev/null
+++ b/accessible/tests/crashtests/crashtests.list
@@ -0,0 +1,21 @@
+load 448064.xhtml # This test instantiates a11y, so be careful about adding tests before it
+load chrome://reftest/content/crashtests/accessible/tests/crashtests/471493.xhtml
+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 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..d9430e1f86
--- /dev/null
+++ b/accessible/tests/mochitest/a11y.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+support-files =
+ ../../../dom/media/test/bug461281.ogg
+ ../../../dom/security/test/csp/dummy.pdf
+ dumbfile.zip
+ formimage.png
+ letters.gif
+ moz.png
+ longdesc_src.html
+ *.js
+ treeview.css
+
+[test_aria_token_attrs.html]
+[test_bug420863.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..57fe09e1c4
--- /dev/null
+++ b/accessible/tests/mochitest/actions.js
@@ -0,0 +1,230 @@
+/* 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",
+};
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..956688afa9
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_general.xhtml
@@ -0,0 +1,172 @@
+<?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
+
+ if (navigator.platform.startsWith("Mac")) {
+ SimpleTest.expectAssertions(0, 1);
+ } else {
+ SimpleTest.expectAssertions(0, 1);
+ }
+
+ function doTest()
+ {
+ var actionsArray = [
+ {
+ ID: "menu",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ // Wait for focus event, it guarantees us the submenu tree is created,
+ // that's necessary for next test.
+ eventSeq: [
+ new invokerChecker(EVENT_FOCUS, getNode("menu"))
+ ]
+ },
+ {
+ 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..edba3eea76
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_keys.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"
+ 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");
+
+ 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"/>
+
+ <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..4dc0bab678
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_link.html
@@ -0,0 +1,141 @@
+<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: "jump",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new linkChecker("link1"),
+ ],
+ },
+ {
+ ID: "link2",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "img2",
+ targetID: "link2",
+ actionName: "jump",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "link3",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "img3",
+ targetID: "link3",
+ actionName: "jump",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "link4",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ },
+ {
+ ID: "img4",
+ targetID: "link4",
+ actionName: "jump",
+ 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=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..3cc7c71849
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_select.html
@@ -0,0 +1,103 @@
+<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"),
+ ],
+ },
+ {
+ ID: "combobox",
+ actionIndex: 0,
+ actionName: "open",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new focusChecker("cb_orange"),
+ ],
+ },
+ {
+ ID: "cb_apple",
+ actionIndex: 0,
+ actionName: "select",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new focusChecker("combobox"),
+ ],
+ },
+ {
+ ID: "combobox",
+ actionIndex: 0,
+ actionName: "open",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new focusChecker("cb_apple"),
+ ],
+ },
+ {
+ ID: "combobox",
+ actionIndex: 0,
+ actionName: "close",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new focusChecker("combobox"),
+ ],
+ },
+ ];
+
+ 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..d41a56f959
--- /dev/null
+++ b/accessible/tests/mochitest/attributes.js
@@ -0,0 +1,490 @@
+/* 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
+ */
+function testAttrs(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs) {
+ testAttrsInternal(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs);
+}
+
+/**
+ * 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)
+ */
+function testAbsentAttrs(aAccOrElmOrID, aAbsentAttrs) {
+ testAttrsInternal(aAccOrElmOrID, {}, true, aAbsentAttrs);
+}
+
+/**
+ * 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) {
+ var accessible = getAccessible(aAccOrElmOrID);
+ if (!accessible) {
+ return;
+ }
+
+ var attrs = null;
+ try {
+ attrs = accessible.attributes;
+ } catch (e) {}
+
+ todo_is(attrs.getStringProperty(aKey), aExpectedValue, "attributes match");
+}
+
+/**
+ * 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) {
+ var acc = getAccessible(aAccOrElmOrID);
+ var levelObj = {},
+ posInSetObj = {},
+ setSizeObj = {};
+ acc.groupPosition(levelObj, setSizeObj, posInSetObj);
+
+ if (aPosInSet && aSetSize) {
+ is(
+ posInSetObj.value,
+ aPosInSet,
+ "Wrong group position (posinset) for " + prettyName(aAccOrElmOrID)
+ );
+ is(
+ setSizeObj.value,
+ aSetSize,
+ "Wrong size of the group (setsize) for " + prettyName(aAccOrElmOrID)
+ );
+
+ let attrs = {
+ posinset: String(aPosInSet),
+ setsize: String(aSetSize),
+ };
+ testAttrs(aAccOrElmOrID, attrs, true);
+ }
+
+ if (aLevel) {
+ is(
+ levelObj.value,
+ aLevel,
+ "Wrong group level for " + prettyName(aAccOrElmOrID)
+ );
+
+ let attrs = { level: String(aLevel) };
+ testAttrs(aAccOrElmOrID, attrs, true);
+ }
+}
+
+function testGroupParentAttrs(aAccOrElmOrID, aChildItemCount, aIsHierarchical) {
+ testAttrs(
+ aAccOrElmOrID,
+ { "child-item-count": String(aChildItemCount) },
+ true
+ );
+
+ if (aIsHierarchical) {
+ testAttrs(aAccOrElmOrID, { hierarchical: "true" }, true);
+ } else {
+ testAbsentAttrs(aAccOrElmOrID, { hierarchical: "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;
+};
+
+// The pt font size of the input element can vary by Linux distro.
+const kInputFontSize = WIN
+ ? "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;
+ }
+}
+
+/**
+ * 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)"
+ ? "rgb(255, 255, 255)"
+ : 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
+) {
+ 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);
+}
+
+function compareAttrs(
+ aErrorMsg,
+ aAttrs,
+ aExpectedAttrs,
+ aSkipUnexpectedAttrs,
+ aAbsentAttrs
+) {
+ // Check if all obtained attributes are expected and have expected value.
+ for (let prop of aAttrs.enumerate()) {
+ if (!(prop.key in aExpectedAttrs)) {
+ if (!aSkipUnexpectedAttrs) {
+ ok(
+ false,
+ "Unexpected attribute '" +
+ prop.key +
+ "' having '" +
+ prop.value +
+ "'" +
+ aErrorMsg
+ );
+ }
+ } else {
+ var msg = "Attribute '" + prop.key + "' has wrong value" + aErrorMsg;
+ var expectedValue = aExpectedAttrs[prop.key];
+
+ if (typeof expectedValue == "function") {
+ ok(expectedValue(prop.value), msg);
+ } else {
+ is(prop.value, expectedValue, msg);
+ }
+ }
+ }
+
+ // Check if all expected attributes are presented.
+ for (let name in aExpectedAttrs) {
+ var value = "";
+ try {
+ value = aAttrs.getStringProperty(name);
+ } catch (e) {}
+
+ if (!value) {
+ ok(false, "There is no expected attribute '" + name + "' " + aErrorMsg);
+ }
+ }
+
+ // Check if all unexpected attributes are absent.
+ if (aAbsentAttrs) {
+ for (var name in aAbsentAttrs) {
+ var wasFound = false;
+
+ for (let prop of aAttrs.enumerate()) {
+ if (prop.key == name) {
+ wasFound = true;
+ }
+ }
+ }
+
+ ok(
+ !wasFound,
+ "There is an unexpected attribute '" + name + "' " + aErrorMsg
+ );
+ }
+}
diff --git a/accessible/tests/mochitest/attributes/a11y.ini b/accessible/tests/mochitest/attributes/a11y.ini
new file mode 100644
index 0000000000..0508c7289e
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/a11y.ini
@@ -0,0 +1,15 @@
+[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_css.xhtml]
+[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..0d4d84a1b8
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj.html
@@ -0,0 +1,300 @@
+<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"}, 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);
+
+ // inherited attributes by subdocuments
+ var subdoc = getAccessible("iframe").firstChild;
+ testAttrs(subdoc, {"busy": "true"}, 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": "" }, true);
+
+ // 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>
+
+ <!-- inherited from iframe -->
+ <iframe id="iframe" src="data:text/html,<html><body></body></html>"
+ aria-busy="true"></iframe>
+
+ <!-- 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_css.xhtml b/accessible/tests/mochitest/attributes/test_obj_css.xhtml
new file mode 100644
index 0000000000..df9afceba0
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj_css.xhtml
@@ -0,0 +1,57 @@
+<?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 CSS-based Object Attributes 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 doTest()
+ {
+ // CSS display
+ testCSSAttrs("display_mozbox");
+ testCSSAttrs("display_mozinlinebox");
+ testCSSAttrs("display_mozdeck");
+ testCSSAttrs("display_mozpopup");
+
+ 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=714579"
+ title="Don't use GetComputedStyle for object attribute calculation">
+ Mozilla Bug 714579
+ </a><br/>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="display_mozbox" style="display: -moz-box;" role="img"/>
+ <vbox id="display_mozinlinebox" style="display: -moz-inline-box;" role="img"/>
+ <vbox id="display_mozdeck" style="display: -moz-deck;" role="img"/>
+ <vbox id="display_mozpopup" style="display: -moz-popup;" role="img"/>
+
+ </hbox>
+</window>
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..6bfaaf7285
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj_group.html
@@ -0,0 +1,566 @@
+<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 options
+ // XXX bug 469123
+ // testGroupAttrs("select2_optgroup", 1, 3, 1);
+ // testGroupAttrs("select2_opt3", 2, 3, 1);
+ // testGroupAttrs("select2_opt4", 3, 3, 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 "hierarchical" flag for parent of headings
+ testAbsentAttrs("headings", {"child-item-count": "", "hierarchical": ""});
+
+ // ////////////////////////////////////////////////////////////////////////
+ // 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..b6cb95eb39
--- /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"}, true);
+ 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..130d9526e7
--- /dev/null
+++ b/accessible/tests/mochitest/autocomplete.js
@@ -0,0 +1,195 @@
+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(outer, 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 > 0) {
+ 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);
+ },
+
+ 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..bdb8c02f90
--- /dev/null
+++ b/accessible/tests/mochitest/bounds/a11y.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_list.html]
+[test_select.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/bounds/test_select.html b/accessible/tests/mochitest/bounds/test_select.html
new file mode 100644
index 0000000000..ac9a501f32
--- /dev/null
+++ b/accessible/tests/mochitest/bounds/test_select.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"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function openComboboxNCheckBounds(aID) {
+ this.combobox = getAccessible(aID);
+ this.comboboxList = this.combobox.firstChild;
+ this.comboboxOption = this.comboboxList.firstChild;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.comboboxOption),
+ ];
+
+ this.invoke = function openComboboxNCheckBounds_invoke() {
+ getNode(aID).focus();
+ synthesizeKey("VK_DOWN", { altKey: true });
+ };
+
+ this.finalCheck = function openComboboxNCheckBounds_invoke() {
+ testBounds(this.comboboxOption);
+ };
+
+ this.getID = function openComboboxNCheckBounds_getID() {
+ return "open combobox and test boundaries";
+ };
+ }
+
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest() {
+ // Combobox
+ testBounds("combobox");
+
+ // Option boundaries matches to combobox boundaries when collapsed.
+ var selectBounds = getBoundsForDOMElm("combobox");
+ testBounds("option1", selectBounds);
+
+ // Open combobox and test option boundaries.
+ gQueue = new eventQueue();
+ gQueue.push(new openComboboxNCheckBounds("combobox"));
+ 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="combobox">
+ <option id="option1">item1</option>
+ <option>item2</option>
+ </select>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/browser.js b/accessible/tests/mochitest/browser.js
new file mode 100644
index 0000000000..ffe67b0ed0
--- /dev/null
+++ b/accessible/tests/mochitest/browser.js
@@ -0,0 +1,157 @@
+/* import-globals-from common.js */
+
+var { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+/**
+ * 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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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..69014c6cd3
--- /dev/null
+++ b/accessible/tests/mochitest/common.js
@@ -0,0 +1,1076 @@
+// 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_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(0x25fe) + " ";
+
+const MAX_TRIM_LENGTH = 100;
+
+/**
+ * Services to determine if e10s is enabled.
+ */
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * 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 != 0) {
+ 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/unicode");
+ Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+
+ var str = {};
+ trans.getTransferData("text/unicode", 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;
+}
+
+/** Sets the test plugin(s) initially expected enabled state.
+ * It will automatically be reset to it's previous value after the test
+ * ends.
+ * @param aNewEnabledState [in] the enabled state, e.g. SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED
+ * @param aPluginName [in, optional] The name of the plugin, defaults to "Test Plug-in"
+ */
+function setTestPluginEnabledState(aNewEnabledState, aPluginName) {
+ var plugin = getTestPluginTag(aPluginName);
+ var oldEnabledState = plugin.enabledState;
+ plugin.enabledState = aNewEnabledState;
+ SimpleTest.registerCleanupFunction(function() {
+ getTestPluginTag(aPluginName).enabledState = oldEnabledState;
+ });
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// 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 getTestPluginTag(aPluginName) {
+ var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"].getService(
+ SpecialPowers.Ci.nsIPluginHost
+ );
+ var tags = ph.getPluginTags();
+ var name = aPluginName || "Test Plug-in";
+ for (var tag of tags) {
+ if (tag.name == name) {
+ return tag;
+ }
+ }
+
+ ok(false, "Could not find plugin tag with plugin name '" + name + "'");
+ return null;
+}
+
+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/editabletext/a11y.ini b/accessible/tests/mochitest/editabletext/a11y.ini
new file mode 100644
index 0000000000..68466fdf23
--- /dev/null
+++ b/accessible/tests/mochitest/editabletext/a11y.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+ editabletext.js
+ !/accessible/tests/mochitest/*.js
+
+[test_1.html]
+[test_2.html]
diff --git a/accessible/tests/mochitest/editabletext/editabletext.js b/accessible/tests/mochitest/editabletext/editabletext.js
new file mode 100644
index 0000000000..ef305c7842
--- /dev/null
+++ b/accessible/tests/mochitest/editabletext/editabletext.js
@@ -0,0 +1,409 @@
+/* import-globals-from ../common.js */
+/* import-globals-from ../events.js */
+
+/**
+ * Perform all editable text tests.
+ */
+function editableTextTestRun() {
+ this.add = function add(aTest) {
+ this.seq.push(aTest);
+ };
+
+ this.run = function run() {
+ this.iterate();
+ };
+
+ this.index = 0;
+ this.seq = [];
+
+ this.iterate = function iterate() {
+ if (this.index < this.seq.length) {
+ this.seq[this.index++].startTest(this);
+ return;
+ }
+
+ this.seq = null;
+ SimpleTest.finish();
+ };
+}
+
+/**
+ * Used to test nsIEditableTextAccessible methods.
+ */
+function editableTextTest(aID) {
+ /**
+ * Schedule a test, the given function with its arguments will be executed
+ * when preceding test is complete.
+ */
+ this.scheduleTest = function scheduleTest(aFunc, ...aFuncArgs) {
+ // A data container acts like a dummy invoker, it's never invoked but
+ // it's used to generate real invoker when previous invoker was handled.
+ var dataContainer = {
+ func: aFunc,
+ funcArgs: aFuncArgs,
+ };
+ this.mEventQueue.push(dataContainer);
+
+ if (!this.mEventQueueReady) {
+ this.unwrapNextTest();
+ this.mEventQueueReady = true;
+ }
+ };
+
+ /**
+ * setTextContents test.
+ */
+ this.setTextContents = function setTextContents(aValue, aSkipStartOffset) {
+ var testID = "setTextContents '" + aValue + "' for " + prettyName(aID);
+
+ function setTextContentsInvoke() {
+ dump(`\nsetTextContents '${aValue}'\n`);
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.setTextContents(aValue);
+ }
+
+ aSkipStartOffset = aSkipStartOffset || 0;
+ var insertTripple = aValue
+ ? [aSkipStartOffset, aSkipStartOffset + aValue.length, aValue]
+ : null;
+ var oldValue = getValue();
+ var removeTripple = oldValue
+ ? [aSkipStartOffset, aSkipStartOffset + oldValue.length, oldValue]
+ : null;
+
+ this.generateTest(
+ removeTripple,
+ insertTripple,
+ setTextContentsInvoke,
+ getValueChecker(aValue),
+ testID
+ );
+ };
+
+ /**
+ * insertText test.
+ */
+ this.insertText = function insertText(aStr, aPos, aResStr, aResPos) {
+ var testID =
+ "insertText '" + aStr + "' at " + aPos + " for " + prettyName(aID);
+
+ function insertTextInvoke() {
+ dump(`\ninsertText '${aStr}' at ${aPos} pos\n`);
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.insertText(aStr, aPos);
+ }
+
+ var resPos = aResPos != undefined ? aResPos : aPos;
+ this.generateTest(
+ null,
+ [resPos, resPos + aStr.length, aStr],
+ insertTextInvoke,
+ getValueChecker(aResStr),
+ testID
+ );
+ };
+
+ /**
+ * copyText test.
+ */
+ this.copyText = function copyText(aStartPos, aEndPos, aClipboardStr) {
+ var testID =
+ "copyText from " +
+ aStartPos +
+ " to " +
+ aEndPos +
+ " for " +
+ prettyName(aID);
+
+ function copyTextInvoke() {
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.copyText(aStartPos, aEndPos);
+ }
+
+ this.generateTest(
+ null,
+ null,
+ copyTextInvoke,
+ getClipboardChecker(aClipboardStr),
+ testID
+ );
+ };
+
+ /**
+ * copyText and pasteText test.
+ */
+ this.copyNPasteText = function copyNPasteText(
+ aStartPos,
+ aEndPos,
+ aPos,
+ aResStr
+ ) {
+ var testID =
+ "copyText from " +
+ aStartPos +
+ " to " +
+ aEndPos +
+ "and pasteText at " +
+ aPos +
+ " for " +
+ prettyName(aID);
+
+ function copyNPasteTextInvoke() {
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.copyText(aStartPos, aEndPos);
+ acc.pasteText(aPos);
+ }
+
+ this.generateTest(
+ null,
+ [aStartPos, aEndPos, getTextFromClipboard],
+ copyNPasteTextInvoke,
+ getValueChecker(aResStr),
+ testID
+ );
+ };
+
+ /**
+ * cutText test.
+ */
+ this.cutText = function cutText(
+ aStartPos,
+ aEndPos,
+ aResStr,
+ aResStartPos,
+ aResEndPos
+ ) {
+ var testID =
+ "cutText from " +
+ aStartPos +
+ " to " +
+ aEndPos +
+ " for " +
+ prettyName(aID);
+
+ function cutTextInvoke() {
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.cutText(aStartPos, aEndPos);
+ }
+
+ var resStartPos = aResStartPos != undefined ? aResStartPos : aStartPos;
+ var resEndPos = aResEndPos != undefined ? aResEndPos : aEndPos;
+ this.generateTest(
+ [resStartPos, resEndPos, getTextFromClipboard],
+ null,
+ cutTextInvoke,
+ getValueChecker(aResStr),
+ testID
+ );
+ };
+
+ /**
+ * cutText and pasteText test.
+ */
+ this.cutNPasteText = function copyNPasteText(
+ aStartPos,
+ aEndPos,
+ aPos,
+ aResStr
+ ) {
+ var testID =
+ "cutText from " +
+ aStartPos +
+ " to " +
+ aEndPos +
+ " and pasteText at " +
+ aPos +
+ " for " +
+ prettyName(aID);
+
+ function cutNPasteTextInvoke() {
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.cutText(aStartPos, aEndPos);
+ acc.pasteText(aPos);
+ }
+
+ this.generateTest(
+ [aStartPos, aEndPos, getTextFromClipboard],
+ [aPos, -1, getTextFromClipboard],
+ cutNPasteTextInvoke,
+ getValueChecker(aResStr),
+ testID
+ );
+ };
+
+ /**
+ * pasteText test.
+ */
+ this.pasteText = function pasteText(aPos, aResStr) {
+ var testID = "pasteText at " + aPos + " for " + prettyName(aID);
+
+ function pasteTextInvoke() {
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.pasteText(aPos);
+ }
+
+ this.generateTest(
+ null,
+ [aPos, -1, getTextFromClipboard],
+ pasteTextInvoke,
+ getValueChecker(aResStr),
+ testID
+ );
+ };
+
+ /**
+ * deleteText test.
+ */
+ this.deleteText = function deleteText(aStartPos, aEndPos, aResStr) {
+ var testID =
+ "deleteText from " +
+ aStartPos +
+ " to " +
+ aEndPos +
+ " for " +
+ prettyName(aID);
+
+ var oldValue = getValue().substring(aStartPos, aEndPos);
+ var removeTripple = oldValue ? [aStartPos, aEndPos, oldValue] : null;
+
+ function deleteTextInvoke() {
+ var acc = getAccessible(aID, [nsIAccessibleEditableText]);
+ acc.deleteText(aStartPos, aEndPos);
+ }
+
+ this.generateTest(
+ removeTripple,
+ null,
+ deleteTextInvoke,
+ getValueChecker(aResStr),
+ testID
+ );
+ };
+
+ // ////////////////////////////////////////////////////////////////////////////
+ // Implementation details.
+
+ function getValue() {
+ var elm = getNode(aID);
+ var elmClass = ChromeUtils.getClassName(elm);
+ if (elmClass === "HTMLTextAreaElement" || elmClass === "HTMLInputElement") {
+ return elm.value;
+ }
+
+ if (elmClass === "HTMLDocument") {
+ return elm.body.textContent;
+ }
+
+ return elm.textContent;
+ }
+
+ /**
+ * Common checkers.
+ */
+ function getValueChecker(aValue) {
+ var checker = {
+ check: function valueChecker_check() {
+ is(getValue(), aValue, "Wrong value " + aValue);
+ },
+ };
+ return checker;
+ }
+
+ function getClipboardChecker(aText) {
+ var checker = {
+ check: function clipboardChecker_check() {
+ is(getTextFromClipboard(), aText, "Wrong text in clipboard.");
+ },
+ };
+ return checker;
+ }
+
+ /**
+ * Process next scheduled test.
+ */
+ this.unwrapNextTest = function unwrapNextTest() {
+ var data = this.mEventQueue.mInvokers[this.mEventQueue.mIndex + 1];
+ if (data) {
+ data.func.apply(this, data.funcArgs);
+ }
+ };
+
+ /**
+ * Used to generate an invoker object for the sheduled test.
+ */
+ this.generateTest = function generateTest(
+ aRemoveTriple,
+ aInsertTriple,
+ aInvokeFunc,
+ aChecker,
+ aInvokerID
+ ) {
+ var et = this;
+ var invoker = {
+ eventSeq: [],
+
+ invoke: aInvokeFunc,
+ finalCheck: function finalCheck() {
+ // dumpTree(aID, `'${aID}' tree:`);
+
+ aChecker.check();
+ et.unwrapNextTest(); // replace dummy invoker on real invoker object.
+ },
+ getID: function getID() {
+ return aInvokerID;
+ },
+ };
+
+ if (aRemoveTriple) {
+ let checker = new textChangeChecker(
+ aID,
+ aRemoveTriple[0],
+ aRemoveTriple[1],
+ aRemoveTriple[2],
+ false
+ );
+ invoker.eventSeq.push(checker);
+ }
+
+ if (aInsertTriple) {
+ let checker = new textChangeChecker(
+ aID,
+ aInsertTriple[0],
+ aInsertTriple[1],
+ aInsertTriple[2],
+ true
+ );
+ invoker.eventSeq.push(checker);
+ }
+
+ // Claim that we don't want to fail when no events are expected.
+ if (!aRemoveTriple && !aInsertTriple) {
+ invoker.noEventsOnAction = true;
+ }
+
+ this.mEventQueue.mInvokers[this.mEventQueue.mIndex + 1] = invoker;
+ };
+
+ /**
+ * Run the tests.
+ */
+ this.startTest = function startTest(aTestRun) {
+ var testRunObj = aTestRun;
+ var thisObj = this;
+ this.mEventQueue.onFinish = function finishCallback() {
+ // Notify textRun object that all tests were finished.
+ testRunObj.iterate();
+
+ // Help GC to avoid leaks (refer to aTestRun from local variable, drop
+ // onFinish function).
+ thisObj.mEventQueue.onFinish = null;
+
+ return DO_NOT_FINISH_TEST;
+ };
+
+ this.mEventQueue.invoke();
+ };
+
+ this.mEventQueue = new eventQueue();
+ this.mEventQueueReady = false;
+}
diff --git a/accessible/tests/mochitest/editabletext/test_1.html b/accessible/tests/mochitest/editabletext/test_1.html
new file mode 100644
index 0000000000..10b5719374
--- /dev/null
+++ b/accessible/tests/mochitest/editabletext/test_1.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=452161
+-->
+<head>
+ <title>nsIAccessibleEditableText 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="editabletext.js"></script>
+
+ <script type="application/javascript">
+ // gA11yEventDumpToConsole = true;
+ // enableLogging("tree,verbose"); // debug
+
+ function addTestEditable(aID, aTestRun, aBeforeContent, aAfterContent) {
+ var et = new editableTextTest(aID);
+ var startOffset = aBeforeContent ? aBeforeContent.length : 0;
+ // XXX afterContent currently is not used
+
+ // ////////////////////////////////////////////////////////////////////////
+ // setTextContents
+ et.scheduleTest(et.setTextContents, "hello", startOffset);
+ et.scheduleTest(et.setTextContents, "olleh", startOffset);
+ et.scheduleTest(et.setTextContents, "", startOffset);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // insertText
+ et.scheduleTest(et.insertText, "hello", startOffset, "hello");
+ et.scheduleTest(et.insertText, "ma ", startOffset, "ma hello");
+ et.scheduleTest(et.insertText, "ma", startOffset + 2, "mama hello");
+ et.scheduleTest(et.insertText, " hello", startOffset + 10, "mama hello hello");
+
+ // XXX: bug 452584
+
+ // ////////////////////////////////////////////////////////////////////////
+ // deleteText
+// et.deleteText(0, 5, "hello hello");
+// et.deleteText(5, 6, "hellohello");
+// et.deleteText(5, 10, "hello");
+// et.deleteText(0, 5, "");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // copyNPasteText
+// et.copyNPasteText(0, 0, 0, "");
+// et.insertText("hello", 0, "hello");
+// et.copyNPasteText(0, 1, 0, "hhello");
+// et.copyNPasteText(5, 6, 6, "hhelloo");
+// et.copyNPasteText(3, 4, 1, "hehelloo");
+
+ // ////////////////////////////////////////////////////////////////////////
+// // cutNPasteText
+// et.cutNPasteText(0, 1, 1, "ehhelloo");
+// et.cutNPasteText(1, 2, 0, "hehelloo");
+// et.cutNPasteText(7, 8, 8, "hehelloo");
+
+ aTestRun.add(et);
+ }
+
+ // gA11yEventDumpToConsole = true; // debug stuff
+
+ function runTest() {
+ var testRun = new editableTextTestRun();
+
+ addTestEditable("input", testRun);
+ addTestEditable("div", testRun);
+ addTestEditable("divb", testRun, "pseudo element", "");
+ addTestEditable("diva", testRun, "", "pseudo element");
+ addTestEditable("divba", testRun, "before", "after");
+ addTestEditable(getNode("frame").contentDocument, testRun);
+
+ testRun.run(); // Will call SimpleTest.finish();
+ }
+
+ function doTest() {
+ // Prepare tested elements.
+
+ // Design mode on/off triggers an editable state change event on
+ // the document accessible.
+ var frame = getNode("frame");
+ waitForEvent(EVENT_STATE_CHANGE, frame.contentDocument, runTest);
+ frame.contentDocument.designMode = "on";
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+ <style>
+ #divb::before,
+ #diva::after {
+ content: "pseudo element";
+ }
+ #divba::before {
+ content: "before";
+ }
+ #divba::after {
+ content: "after";
+ }
+ </style>
+</head>
+<body>
+
+ <a target="_blank"
+ title="nsIAccessibleEditableText chrome tests"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=452161">
+ Bug 452161
+ </a>
+ <a target="_blank"
+ title="Cache rendered text on a11y side"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660">
+ Bug 626660
+ </a>
+ <a target="_blank"
+ title="Pseudo element support test"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1105611">
+ Bug 1105611
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input"/>
+
+ <div id="div" contenteditable="true"></div>
+ <div id="divb" contenteditable="true"></div>
+ <div id="diva" contenteditable="true"></div>
+ <div id="divba" contenteditable="true"></div>
+
+ <iframe id="frame"/>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/editabletext/test_2.html b/accessible/tests/mochitest/editabletext/test_2.html
new file mode 100644
index 0000000000..1d252d836f
--- /dev/null
+++ b/accessible/tests/mochitest/editabletext/test_2.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessibleEditableText 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="editabletext.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var et = new editableTextTest("input");
+
+ // 'ee' insertion/removal at 1 or 2 offset of 'hello'/'heeello' string
+ // reports 'ee' text was inserted/removed at 2 offset.
+ et.scheduleTest(et.insertText, "ee", 1, "heeello", 2);
+ et.scheduleTest(et.copyText, 1, 3, "ee");
+ et.scheduleTest(et.cutText, 1, 3, "hello", 2, 4);
+ et.scheduleTest(et.insertText, "ee", 2, "heeello", 2);
+ et.scheduleTest(et.cutText, 2, 4, "hello", 2, 4);
+
+ et.scheduleTest(et.deleteText, 1, 3, "hlo");
+ et.scheduleTest(et.pasteText, 1, "heelo");
+
+ var testRun = new editableTextTestRun();
+ testRun.add(et);
+ testRun.run(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="HyperText accessible should get focus when the caret is positioned inside of it, text is changed or copied into clipboard by ATs"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=524115">
+ Mozilla Bug 524115
+ </a>
+ <a target="_blank"
+ title="Cache rendered text on a11y side"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660">
+ Mozilla Bug 626660
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input" value="hello"/>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/elm/a11y.ini b/accessible/tests/mochitest/elm/a11y.ini
new file mode 100644
index 0000000000..2b3cf492ed
--- /dev/null
+++ b/accessible/tests/mochitest/elm/a11y.ini
@@ -0,0 +1,15 @@
+[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_canvas.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..ad8170f090
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_HTMLSpec.html
@@ -0,0 +1,1972 @@
+<!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">
+ 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 jump action
+ {
+ role: ROLE_TEXT_LEAF,
+ states: STATE_LINKED,
+ actions: "jump",
+ },
+ ],
+ };
+ 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_TEXT_CONTAINER,
+ 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 (windowless/windowed plugins and media)
+
+ ok(!isAccessible("embed_plugin_windowless"), "(blocked) windowless plugin embed element is not accessible");
+ ok(!isAccessible("embed_plugin_windowed"), "(blocked) windowed plugin embed element is not accessible");
+
+ 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 },
+ { role: ROLE_PUSHBUTTON },
+ ],
+ };
+ 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: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
+
+ todo(isAccessible("meter"), "meter element is not accessible");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:nav
+
+ obj = {
+ role: ROLE_LANDMARK,
+ attributes: { "xml-roles": "navigation" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("nav", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:object (windowless/windowed plugins and media) and HTML:param
+
+ ok(!isAccessible("object_plugin_windowless"), "(blocked) windowless plugin object element is not accessible");
+ ok(!isAccessible("object_plugin_windowed"), "(blocked) windowed plugin object element is not accessible");
+
+ 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 contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "text-position": "sub" },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:sub text
+ ],
+ };
+ testElm("sub_container", obj);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML:sup contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "text-position": "super" },
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { 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_plugin_windowless" type="application/x-test"
+ width="300" height="300"></embed>
+ <embed id="embed_plugin_windowed" type="application/x-test" wmode="window"
+ width="300" height="300"></embed>
+
+ <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>
+
+ <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_plugin_windowless" type="application/x-test"
+ width="300" height="300">
+ <param name="foo" value="bar">
+ </object>
+ <object id="object_plugin_windowed" type="application/x-test" wmode="window"
+ width="300" height="300"></object>
+
+ <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>
+ <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..c711acf05f
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_MathMLSpec.html
@@ -0,0 +1,617 @@
+<!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);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // mfenced
+
+ obj = {
+ role: ROLE_MATHML_FENCED,
+ attributes: { open: "]", close: "[", separators: "." },
+ };
+ 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_canvas.html b/accessible/tests/mochitest/elm/test_canvas.html
new file mode 100644
index 0000000000..65d6d4bca4
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_canvas.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Accessible boundaries for hit regions</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">
+ var kX = 10, kY = 10, kWidth = 150, kHeight = 100;
+ function doTest() {
+ var canv = document.getElementById("c");
+ var context = canv.getContext("2d");
+ var element = document.getElementById("showA");
+ context.beginPath();
+ context.rect(kX, kY, kWidth, kHeight);
+ context.addHitRegion({control: element});
+
+ var input = getAccessible("showA");
+ var [cnvX, cnvY, /* cnvWidth */, /* cnvHeight */] = getBoundsForDOMElm(canv);
+ var [accX, accY, accWidth, accHeight] = getBounds(input);
+
+ var [x, y, w, h] = CSSToDevicePixels(window, kX, kY, kWidth, kHeight);
+ is(accX, cnvX + x, "wrong accX");
+ is(accY, cnvY + y, "wrong accY");
+ is(accWidth, w, "wrong accWidth");
+ is(accHeight, h, "wrong accHeight");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [["canvas.hitregions.enabled", true]]}, doTest);
+ });
+
+ </script>
+</head>
+<body>
+
+ <canvas id="c">
+ <input id="showA" type="checkbox"><label for="showA"> Show As </label>
+ </canvas>
+
+</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..fe158f2cf4
--- /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'>" +
+ "<div style='display: table-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..2d5e3d9e38
--- /dev/null
+++ b/accessible/tests/mochitest/events.js
@@ -0,0 +1,2631 @@
+/* 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
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * 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 > 0 &&
+ this.mScenarios[matchIdx].length > 0
+ ) {
+ ok(
+ false,
+ "We have a matched scenario at index " +
+ matchIdx +
+ " already."
+ );
+ }
+
+ if (matchIdx == -1 || eventSeq.length > 0) {
+ 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 hanlded 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 == 0) {
+ 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 == 0) {
+ 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 && aArgs.where == "right") {
+ if (isHTMLElement(targetNode)) {
+ x = targetNode.offsetWidth - 1;
+ } else if (isXULElement(targetNode)) {
+ x = targetNode.getBoundingClientRect().width - 1;
+ }
+ }
+ 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";
+ };
+}
+
+/**
+ * Mouse move invoker.
+ */
+function synthMouseMove(aID, aCheckerOrEventSeq) {
+ this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
+
+ this.invoke = function synthMouseMove_invoke() {
+ synthesizeMouse(this.DOMNode, 1, 1, { type: "mousemove" });
+ synthesizeMouse(this.DOMNode, 2, 2, { 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 (atl + 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..076f146ba2
--- /dev/null
+++ b/accessible/tests/mochitest/events/a11y.ini
@@ -0,0 +1,63 @@
+[DEFAULT]
+support-files =
+ focus.html
+ scroll.html
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.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_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.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 = webrender
+[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_tabpanels.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..7947327c28
--- /dev/null
+++ b/accessible/tests/mochitest/events/docload/test_docload_root.html
@@ -0,0 +1,126 @@
+<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 invokerChecker(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.
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ // 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..5838b80083
--- /dev/null
+++ b/accessible/tests/mochitest/events/docload/test_docload_shutdown.html
@@ -0,0 +1,143 @@
+<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.
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ // 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/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..2ba3516bd2
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_statechange.html
@@ -0,0 +1,224 @@
+<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">
+
+
+ /**
+ * 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;
+ };
+ }
+
+ function setAttrOfMixedType(aID, aAttr, aState, aValue) {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new stateChangeChecker(aState, kOrdinalState,
+ aValue == "true", this.DOMNode),
+ ];
+
+ if (hasState(aID, STATE_MIXED) || aValue == "mixed") {
+ this.eventSeq.push(
+ new stateChangeChecker(STATE_MIXED, kOrdinalState,
+ aValue == "mixed", this.DOMNode)
+ );
+ }
+
+ this.invoke = function setAttrOfMixedType_invoke() {
+ this.DOMNode.setAttribute(aAttr, aValue);
+ };
+
+ this.getID = function setAttrOfMixedType_getID() {
+ return prettyName(aID) + " " + aAttr + " changed to '" + aValue + "'";
+ };
+ }
+
+ function setPressed(aID, aValue) {
+ this.__proto__ =
+ new setAttrOfMixedType(aID, "aria-pressed", STATE_PRESSED, aValue);
+ }
+
+ function setChecked(aID, aValue) {
+ this.__proto__ =
+ new setAttrOfMixedType(aID, "aria-checked", STATE_CHECKED, aValue);
+ }
+
+ function buildQueueForAttr(aList, aQueue, aID, aInvokerFunc) {
+ for (var i = 0; i < aList.length; i++) {
+ for (var j = i + 1; j < aList.length; j++) {
+ // XXX: changes from/to "undefined"/"" shouldn't fire state change
+ // events, bug 472142.
+ aQueue.push(new aInvokerFunc(aID, aList[i]));
+ aQueue.push(new aInvokerFunc(aID, aList[j]));
+ }
+ }
+ }
+
+ function buildQueueForAttrOfMixedType(aQueue, aID, aInvokerFunc) {
+ var list = [ "", "undefined", "false", "true", "mixed" ];
+ buildQueueForAttr(list, aQueue, aID, aInvokerFunc);
+ }
+
+ function buildQueueForAttrOfBoolType(aQueue, aID, aInvokerFunc) {
+ var list = [ "", "undefined", "false", "true" ];
+ buildQueueForAttr(list, aQueue, aID, aInvokerFunc);
+ }
+
+ function doTests() {
+ gQueue = new eventQueue();
+
+ 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));
+
+ buildQueueForAttrOfMixedType(gQueue, "pressable", setPressed);
+ buildQueueForAttrOfMixedType(gQueue, "pressable_native", setPressed);
+ buildQueueForAttrOfMixedType(gQueue, "checkable", setChecked);
+ buildQueueForAttrOfBoolType(gQueue, "checkableBool", setChecked);
+
+ 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(); // Will call 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_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..1dca41886e
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_contextmenu.html
@@ -0,0 +1,125 @@
+<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();
+ }
+
+ 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..b7cb3e1433
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_descrchange.html
@@ -0,0 +1,79 @@
+<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">
+ // //////////////////////////////////////////////////////////////////////////
+ // 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;
+ function doTests() {
+ gQueue = new eventQueue();
+
+ 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")));
+
+ gQueue.push(new setAttr("tst2", "title", "title",
+ new invokerChecker(EVENT_NAME_CHANGE, "tst2")));
+
+ 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=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="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..5f005bad49
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html
@@ -0,0 +1,260 @@
+<!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) {
+ let itemID = aItem instanceof Node ? aItem.id : aItem;
+ this.eventSeq = [
+ new focusChecker(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) {
+ this.eventSeq = [
+ new focusChecker(aID),
+ ];
+
+ 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) {
+ if (!aInvalidID) {
+ aInvalidID = "invalid";
+ }
+
+ this.eventSeq = [
+ new focusChecker(aID),
+ ];
+
+ 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) {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, aNewItemID),
+ new focusChecker(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),
+ ];
+
+ 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"));
+ gQueue.push(new changeARIAActiveDescendant("listbox", "item3"));
+
+ 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"));
+
+ gQueue.push(new clearARIAActiveDescendant("listbox"));
+ gQueue.push(new changeARIAActiveDescendant("listbox", "item1"));
+ gQueue.push(new changeARIAActiveDescendantInvalid("listbox", "invalid"));
+
+ gQueue.push(new changeARIAActiveDescendant("listbox", "roaming"));
+ gQueue.push(new moveARIAActiveDescendantID("roaming2", "roaming"));
+ gQueue.push(new changeARIAActiveDescendantInvalid("listbox", "roaming3"));
+ 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 focused = PromEvents.waitForEvent(EVENT_FOCUS, comboboxWithHiddenList);
+ comboboxWithHiddenList.focus();
+ await focused;
+ testStates(comboboxWithHiddenList, STATE_FOCUSED);
+ // hiddenList is owned, so unhiding causes insertion and relocation.
+ getNode("hiddenList").hidden = false;
+ focused = PromEvents.waitForEvent(EVENT_FOCUS, "hiddenListOption");
+ comboboxWithHiddenList.setAttribute("aria-activedescendant", "hiddenListOption");
+ await focused;
+ testStates("hiddenListOption", STATE_FOCUSED);
+
+ 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.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..c26130dc2e
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_contextmenu.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"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Context nenu 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")));
+ 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..abf6a53085
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_general.html
@@ -0,0 +1,164 @@
+<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)));
+ }
+ gQueue.push(new synthContextMenu(editableDoc, new contextMenuChecker()));
+ gQueue.push(new synthDownKey(editableDoc, new focusContextMenuItemChecker()));
+ gQueue.push(new synthEscapeKey(editableDoc, new focusChecker(editableDoc)));
+ 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..f3f9a014c1
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_general.xhtml
@@ -0,0 +1,118 @@
+<?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")));
+
+ // clicking on button having associated popup doesn't change a focus
+ gQueue.push(new synthClick("popupbutton", new nofocusChecker()));
+ // 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..5927bec925
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_listcontrols.xhtml
@@ -0,0 +1,152 @@
+<?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")));
+ 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..f4aee97221
--- /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")));
+
+ // 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 synthClick("vehicle", new focusChecker("vehicle")));
+ gQueue.push(new synthDownKey("cycle", 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)));
+
+ //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>
+ <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..ad9c3dc207
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_selects.html
@@ -0,0 +1,115 @@
+<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="../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;
+
+ 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;
+ }
+
+ gQueue = new eventQueue();
+
+ // first item is focused until there's selection
+ gQueue.push(new synthFocus("list", new focusChecker("orange")));
+
+ // item is selected and stays focused
+ gQueue.push(new synthDownKey("list", new nofocusChecker()));
+
+ // last selected item is focused
+ gQueue.push(new synthDownKey("list", new focusChecker("apple"), { shiftKey: true }));
+
+ // no focus event if nothing is changed
+ gQueue.push(new synthDownKey("list", new nofocusChecker("apple")));
+
+ // current item is focused
+ gQueue.push(new synthUpKey("list", new focusChecker("orange"), { ctrlKey: true }));
+
+ // focus on empty list (no items to be focused)
+ gQueue.push(new synthTab("list", new focusChecker("emptylist")));
+
+ // current item is focused
+ gQueue.push(new synthShiftTab("emptylist", new focusChecker("orange")));
+
+ // collapsed combobox keeps a focus
+ gQueue.push(new synthFocus("combobox", new focusChecker("combobox")));
+ gQueue.push(new synthDownKey("combobox", new nofocusChecker("combobox")));
+
+ // selected item is focused for expanded combobox
+ gQueue.push(new synthOpenComboboxKey("combobox", new focusChecker("cb_apple")));
+ gQueue.push(new synthUpKey("combobox", new focusChecker("cb_orange")));
+
+ // collapsed combobx keeps a focus
+ gQueue.push(new synthEscapeKey("combobox", new focusChecker("combobox")));
+
+ // no focus events for unfocused list controls when current item is
+ // changed
+ gQueue.push(new synthFocus("emptylist"));
+
+ gQueue.push(new changeCurrentItem("list", "orange"));
+ gQueue.push(new changeCurrentItem("combobox", "cb_apple"));
+
+ 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=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..8c841490bb
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focusable_statechange.html
@@ -0,0 +1,96 @@
+<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);
+ }];
+ }
+
+ 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;
+
+ 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>
+
+</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..d9bb7baa45
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_label.xhtml
@@ -0,0 +1,176 @@
+<?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()
+ {
+ var tree =
+ { LABEL: [
+ { TEXT_LEAF: [ ] }
+ ] };
+ 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 textChangeChecker(this.labelNode, 0, -1, aRemovedText, false),
+ new textChangeChecker(this.labelNode, 0, -1, aInsertedText, true)
+ ];
+
+ this.invoke = function setCrop_invoke()
+ {
+ if (!this.labelNode.hasAttribute("crop"))
+ this.labelNode.width = Math.floor(this.width - 2 * this.charWidth);
+
+ 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", "", kTextRemoved, "?<>!+_"));
+ 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));
+
+ if (MAC) {
+ // "valuetocro" -> "…etocro"
+ gQueue.push(new setCrop("croplabel", "left", "valu", "…"));
+ // "…etocro", "val…cro"
+ gQueue.push(new setCrop("croplabel", "center", "…eto", "val…"));
+ } else {
+ // "valuetocro" -> "…uetocro"
+ gQueue.push(new setCrop("croplabel", "left", "val", "…"));
+ // "…uetocro" -> "valu…cro"
+ gQueue.push(new setCrop("croplabel", "center", "…ueto", "valu…"));
+ }
+
+ 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">hello</label>
+ <description id="descr">hello</description>
+
+ <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..e089c76d0b
--- /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 type="application/javascript">
+ function openFileMenu()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENU_START, getNode("menubar")),
+ new invokerChecker(EVENT_MENUPOPUP_START, getNode("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(EVENT_MENUPOPUP_END, getNode("menupopup-file")),
+ new invokerChecker(EVENT_MENUPOPUP_START, getNode("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(EVENT_MENUPOPUP_END, getNode("menupopup-edit")),
+ new invokerChecker(EVENT_MENU_END, getNode("menubar"))
+ ];
+
+ this.invoke = function closeEditMenu_invoke()
+ {
+ synthesizeKey("KEY_Escape");
+ }
+
+ this.getID = function closeEditMenu_getID()
+ {
+ return "close edit menu, leave menubar";
+ }
+ }
+
+ 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, getNode("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) {
+ 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());
+
+ // 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..02d888a4ae
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_namechange.html
@@ -0,0 +1,119 @@
+<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">
+ // //////////////////////////////////////////////////////////////////////////
+ // 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;
+ function doTests() {
+ gQueue = new eventQueue();
+
+ gQueue.push(new setAttr("tst1", "aria-label", "hi",
+ new invokerChecker(EVENT_NAME_CHANGE, "tst1")));
+ gQueue.push(new setAttr("tst1", "aria-labelledby", "display",
+ new unexpectedInvokerChecker(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("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")));
+
+ // 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(); // Will call 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="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..cf3aaa7cb4
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_scroll.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">
+
+ <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("a[name='link1']");
+ }
+
+ function loadTab(aURL)
+ {
+ this.eventSeq = [
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
+ new asyncInvokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument)
+ ];
+
+ this.invoke = function loadTab_invoke()
+ {
+ tabBrowser().loadURI(aURL, {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ // Flush layout, so as to guarantee that the a11y tree is constructed.
+ browserDocument().documentElement.getBoundingClientRect();
+ }
+
+ this.getID = function loadTab_getID()
+ {
+ return "load tab: " + aURL;
+ }
+ }
+
+ function loadTabInBackground(aURL)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
+ ];
+
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument, 1)
+ ];
+
+ this.invoke = function loadTabInBackground_invoke()
+ {
+ tabBrowser().loadOneTab(aURL, {
+ 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();
+ }
+
+ this.getID = function loadTabInBackground_getID()
+ {
+ return "load tab in background: " + aURL;
+ }
+ }
+
+ function switchToBackgroundTab()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument)
+ ];
+
+ this.invoke = function switchToBackgroundTab_invoke()
+ {
+ tabBrowser().selectTabAtIndex(1);
+ }
+
+ this.getID = function switchToBackgroundTab_getID()
+ {
+ return "switch to background tab";
+ }
+ }
+
+ //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#link1";
+ gQueue.push(new loadTab(url));
+ gQueue.push(new loadTabInBackground(url));
+ gQueue.push(new switchToBackgroundTab());
+ 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=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..e696b128bd
--- /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(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..a976d54b30
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_selection.html
@@ -0,0 +1,115 @@
+<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();
+
+ // open combobox
+ gQueue.push(new synthClick("combobox",
+ new invokerChecker(EVENT_FOCUS, "cb1_item1")));
+ gQueue.push(new synthDownKey("cb1_item1",
+ selChangeSeq("cb1_item1", "cb1_item2")));
+
+ // closed combobox
+ gQueue.push(new synthEscapeKey("combobox",
+ new invokerChecker(EVENT_FOCUS, "combobox")));
+ gQueue.push(new synthDownKey("cb1_item2",
+ selChangeSeq("cb1_item2", "cb1_item3")));
+
+ // 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..9dad6ed2f8
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_statechange.html
@@ -0,0 +1,317 @@
+<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">
+ function stateChange(aState, aIsExtraState, aIsEnabled, aTarget) {
+ return [EVENT_STATE_CHANGE, evt => {
+ evt.QueryInterface(nsIAccessibleStateChangeEvent);
+ return evt.state == aState && evt.isExtraState == aIsExtraState &&
+ aIsEnabled == evt.isEnabled && getAccessible(aTarget) == evt.accessible;
+ }];
+ }
+
+ async function openNode(aIDDetails, aIDSummary, aIsOpen) {
+ let p = waitForEvent(...stateChange(STATE_EXPANDED, false, aIsOpen, aIDSummary));
+ if (aIsOpen) {
+ getNode(aIDDetails).setAttribute("open", "");
+ } else {
+ getNode(aIDDetails).removeAttribute("open");
+ }
+ await p;
+ }
+
+ async function makeEditableDoc(aDocNode, aIsEnabled) {
+ let p = waitForEvent(...stateChange(EXT_STATE_EDITABLE, true, true, aDocNode));
+ aDocNode.designMode = "on";
+ await p;
+ }
+
+ async function invalidInput(aNodeOrID) {
+ let p = waitForEvent(...stateChange(STATE_INVALID, false, true, aNodeOrID));
+ getNode(aNodeOrID).value = "I am not an email";
+ await p;
+ }
+
+ async function changeCheckInput(aID, aIsChecked) {
+ let p = waitForEvent(...stateChange(STATE_CHECKED, false, aIsChecked, aID));
+ getNode(aID).checked = aIsChecked;
+ await p;
+ }
+
+ async function changeRequiredState(aID, aIsRequired) {
+ let p = waitForEvent(...stateChange(STATE_REQUIRED, false, aIsRequired, aID));
+ 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([
+ stateChange(aState, aIsExtraState, aIsEnabled, fileControl),
+ stateChange(aState, aIsExtraState, aIsEnabled, browseButton)])
+ 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 dupeStateChange(aID, aAttr, aValue,
+ aState, aIsExtraState, aIsEnabled) {
+ let p = waitForEvents([
+ stateChange(aState, aIsExtraState, aIsEnabled, aID),
+ [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: [
+ stateChange(aState, aIsExtraState, false, aID),
+ stateChange(aState, aIsExtraState, true, aID)
+ ]
+ });
+ 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 = waitForEvent(...stateChange(aState, aIsExtraState, aIsEnabled, aID));
+ 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 = waitForEvent(...stateChange(STATE_LINKED, false, false, "link1"));
+ getNode("link1").removeAttribute("href");
+ await p;
+
+ p = waitForEvent(...stateChange(STATE_LINKED, false, false, "link2"));
+ getNode("link2").removeAttribute("onclick");
+ await p;
+
+ p = waitForEvent(...stateChange(STATE_LINKED, false, true, "link3"));
+ getNode("link3").setAttribute("href", "http://example.com");
+ await p;
+ }
+
+ async function testHasPopup() {
+ let p = waitForEvent(...stateChange(STATE_HASPOPUP, false, true, "popupButton"));
+ getNode("popupButton").setAttribute("aria-haspopup", "true");
+ await p;
+
+ p = waitForEvent(...stateChange(STATE_HASPOPUP, false, true, "popupButton"));
+ getNode("popupButton").setAttribute("aria-haspopup", "tree");
+ await p;
+
+ p = waitForEvent(...stateChange(STATE_HASPOPUP, false, true, "popupButton"));
+ getNode("popupButton").setAttribute("aria-haspopup", "menu");
+ await p;
+
+ p = waitForEvent(...stateChange(STATE_HASPOPUP, false, true, "popupButton"));
+ getNode("popupButton").setAttribute("aria-haspopup", "listbox");
+ await p;
+
+ p = waitForEvent(...stateChange(STATE_HASPOPUP, false, true, "popupButton"));
+ getNode("popupButton").setAttribute("aria-haspopup", "grid");
+ await p;
+
+ p = waitForEvent(...stateChange(STATE_HASPOPUP, false, false, "popupButton"));
+ getNode("popupButton").setAttribute("aria-haspopup", "false");
+ await p;
+
+ p = waitForEvent(...stateChange(STATE_HASPOPUP, false, true, "popupButton"));
+ getNode("popupButton").setAttribute("aria-haspopup", "dialog");
+ await p;
+
+ p = waitForEvent(...stateChange(STATE_HASPOPUP, false, false, "popupButton"));
+ getNode("popupButton").removeAttribute("aria-haspopup");
+ 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 testLinked();
+
+ await testHasPopup();
+
+ 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="eventdump"></div>
+
+ <button id="popupButton">action</button>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_statechange_tabpanels.xhtml b/accessible/tests/mochitest/events/test_statechange_tabpanels.xhtml
new file mode 100644
index 0000000000..90e8fad75b
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_statechange_tabpanels.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="tabpanels 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 doTests() {
+ 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;
+
+ 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>
+ </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..b270c6a7d0
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_text_alg.html
@@ -0,0 +1,246 @@
+<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>
+
+ <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>
+</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..bf87580a2d
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_valuechange.html
@@ -0,0 +1,279 @@
+<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 changeRangeValue(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 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);
+
+ // 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 changeRangeValue("range"));
+
+ 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">
+
+ <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..5e17a4f896
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/a11y.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+support-files = zoom_tree.xhtml
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+
+[test_browser.html]
+[test_canvas_hitregion.html]
+skip-if = (os == "android")
+[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_canvas_hitregion.html b/accessible/tests/mochitest/hittest/test_canvas_hitregion.html
new file mode 100644
index 0000000000..f47f1e3bc0
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_canvas_hitregion.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessible::childAtPoint() for canvas 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 redrawCheckbox(context, element, x, y) {
+ context.save();
+ context.font = "10px sans-serif";
+ context.textAlign = "left";
+ context.textBaseline = "middle";
+ var metrics = context.measureText(element.parentNode.textContent);
+ context.beginPath();
+ context.strokeStyle = "black";
+ context.rect(x - 5, y - 5, 10, 10);
+ context.stroke();
+ if (element.checked) {
+ context.fillStyle = "black";
+ context.fill();
+ }
+ context.fillText(element.parentNode.textContent, x + 5, y);
+
+ context.beginPath();
+ context.rect(x - 7, y - 7, 12 + metrics.width + 2, 14);
+
+ if (document.activeElement == element)
+ context.drawFocusIfNeeded(element);
+ context.addHitRegion({control: element});
+ context.restore();
+ }
+
+ function doTest() {
+ var offsetX = 20, offsetY = 40;
+ getNode("hitcanvas").scrollIntoView(true);
+
+ var context = document.getElementById("hitcanvas").getContext("2d");
+ redrawCheckbox(context, document.getElementById("hitcheck"),
+ offsetX, offsetY);
+
+ var hitcanvas = getAccessible("hitcanvas");
+ var hitcheck = getAccessible("hitcheck");
+
+ var [hitX, hitY /* hitWidth, hitHeight */] = getBounds(hitcanvas);
+ var [deltaX, deltaY] = CSSToDevicePixels(window, offsetX, offsetY);
+
+ var docAcc = getAccessible(document);
+
+ // test if we hit the region associated with the shadow dom checkbox
+ var tgtX = hitX + deltaX;
+ var tgtY = hitY + deltaY;
+ let hitAcc = docAcc.getDeepestChildAtPoint(tgtX, tgtY);
+ isObject(hitAcc, hitcheck, `Hit match at (${tgtX}, ${tgtY}`);
+
+ // test that we don't hit the region associated with the shadow dom checkbox
+ tgtY = hitY + deltaY * 2;
+ hitAcc = docAcc.getDeepestChildAtPoint(tgtX, tgtY);
+ isObject(hitAcc, hitcanvas, `Hit match at (${tgtX}, ${tgtY}`);
+
+ SimpleTest.finish();
+ }
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [["canvas.hitregions.enabled", true]]}, doTest);
+ });
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=966591"
+ title="nsIAccessible::childAtPoint() for canvas hit regions from browser tests">Mozilla Bug 966591</a>
+
+ <canvas id="hitcanvas">
+ <input id="hitcheck" type="checkbox"><label for="showA"> Show A </label>
+ </canvas>
+</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..236b44dac4
--- /dev/null
+++ b/accessible/tests/mochitest/hyperlink/test_general.html
@@ -0,0 +1,273 @@
+<!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);
+ 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,
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#b", "URI wrong!");
+ is(imageMapHyperlinkAcc.getURI(1).spec,
+ "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,
+ "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,
+ "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);
+ 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..b7ce2add31
--- /dev/null
+++ b/accessible/tests/mochitest/hyperlink/test_general.xhtml
@@ -0,0 +1,96 @@
+<?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,
+ "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,
+ "Mozilla Foundation", "http://www.mozilla.org/", 2, 3, true,
+ false, true);
+ testStates(labelWithValueAcc, STATE_LINKED, EXT_STATE_HORIZONTAL);
+
+ 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..14e70e9288
--- /dev/null
+++ b/accessible/tests/mochitest/layout.js
@@ -0,0 +1,385 @@
+/* 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
+ is(
+ xObj.value,
+ expectedX,
+ "Wrong x coordinate of text between offsets (" +
+ aStartOffset +
+ ", " +
+ aEndOffset +
+ ") for " +
+ prettyName(aID)
+ );
+
+ // y
+ isWithin(
+ yObj.value,
+ expectedY,
+ 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);
+ if (widthObj.value == expectedWidth) {
+ ok(true, msg);
+ } else {
+ todo(false, msg);
+ } // fails on some windows machines
+
+ // Height
+ isWithin(
+ heightObj.value,
+ expectedHeight,
+ 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);
+
+ isWithin(
+ x.value / aDPR,
+ xInCSS.value,
+ 1,
+ "Heights in CSS pixels is calculated correctly"
+ );
+ isWithin(
+ y.value / aDPR,
+ yInCSS.value,
+ 1,
+ "Heights in CSS pixels is calculated correctly"
+ );
+ isWithin(
+ width.value / aDPR,
+ widthInCSS.value,
+ 1,
+ "Heights in CSS pixels is calculated correctly"
+ );
+ isWithin(
+ height.value / aDPR,
+ heightInCSS.value,
+ 1,
+ "Heights 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 winUtil = aWindow.windowUtils;
+
+ var ratio = winUtil.screenPixelsPerCSSPixel;
+
+ // 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..d3efffb62b
--- /dev/null
+++ b/accessible/tests/mochitest/moz.build
@@ -0,0 +1,37 @@
+# -*- 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",
+ "editabletext/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..261a3790cd
--- /dev/null
+++ b/accessible/tests/mochitest/name/markup.js
@@ -0,0 +1,438 @@
+/* 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" });
+ }
+
+ // If @recreated attribute is used then this attribute change recreates an
+ // accessible. Wait for reorder event in this case or otherwise proceed next
+ // test immediately.
+ if (aRule.hasAttribute("recreated")) {
+ waitForEvent(
+ EVENT_REORDER,
+ aElm.parentNode,
+ gTestIterator.iterateNext,
+ gTestIterator
+ );
+ aElm.removeAttribute(attr);
+ } else if (aRule.hasAttribute("textchanged")) {
+ waitForEvent(
+ EVENT_TEXT_INSERTED,
+ aElm,
+ gTestIterator.iterateNext,
+ gTestIterator
+ );
+ aElm.removeAttribute(attr);
+ } else if (aRule.hasAttribute("contentchanged")) {
+ waitForEvent(EVENT_REORDER, aElm, gTestIterator.iterateNext, gTestIterator);
+ aElm.removeAttribute(attr);
+ } else {
+ aElm.removeAttribute(attr);
+ gTestIterator.iterateNext();
+ }
+}
+
+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 reorder event on " +
+ prettyName(parentNode) +
+ "\n"
+ );
+ }
+ waitForEvent(
+ EVENT_REORDER,
+ parentNode,
+ 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_REORDER, 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..7da43bb33e
--- /dev/null
+++ b/accessible/tests/mochitest/name/markuprules.xml
@@ -0,0 +1,385 @@
+<?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" textchanged="true"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputReset" defaultName="Reset">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="value" type="string" explict-name="false" textchanged="true"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputImage">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="alt" type="string" textchanged="true"/>
+ <rule attr="value" type="string" textchanged="true"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputImageNoValidSrc" defaultName="Submit Query">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="alt" type="string" explict-name="false" textchanged="true"/>
+ <rule attr="value" type="string" explict-name="false" textchanged="true"/>
+ </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" recreated="true"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLImgEmptyAlt">
+ <ruleset ref="ARIA"/>
+ <rule attr="title" type="string"/>
+ <rule attr="alt" type="string" recreated="true"/>
+ </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="HTMLImageEmptyAltTest"
+ ref="html:img" ruleset="HTMLImgEmptyAlt">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:img id="imgemptyalt"
+ aria-label="Logo of Mozilla"
+ aria-labelledby="l1 l2"
+ title="This is a logo"
+ alt=""
+ 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..b330b177c9
--- /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.loadURI(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..1707cff062
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_general.html
@@ -0,0 +1,706 @@
+<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 (every text node
+ // value in the name should be devided by spaces).
+ // XXX: this case is rather a feature than strong wanted behaviour.
+ testName("btn_labelledby_mixed_table", "text space text");
+
+ // 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");
+
+
+ // ///////////////////////////////////////////////////////////////////////
+ // 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");
+
+ 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>space</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>
+
+ <!-- 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>
+</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..2d8abaf4d5
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_general.xhtml
@@ -0,0 +1,339 @@
+<?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");
+
+ // 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>
+
+ <!-- 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..86afef66b2
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_list.html
@@ -0,0 +1,89 @@
+<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");
+
+ // 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)
+
+ testName("li_start", kDiscBulletText + "list start");
+ testName("li_end", kDiscBulletText + "list end");
+ };
+
+ this.getID = function bulletUpdate_getID() {
+ return "Update bullet of list items";
+ };
+ }
+
+ var gQueue = null;
+ function doTest() {
+ gQueue = new eventQueue();
+ gQueue.push(new bulletUpdate());
+ 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..e253405e06
--- /dev/null
+++ b/accessible/tests/mochitest/pivot.js
@@ -0,0 +1,665 @@
+/* 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 */
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// //////////////////////////////////////////////////////////////////////////////
+// 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 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..f824de34bd
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html
@@ -0,0 +1,33 @@
+<!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>
+ <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..f900afff87
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html
@@ -0,0 +1,267 @@
+<!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.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..fcea95bc63
--- /dev/null
+++ b/accessible/tests/mochitest/promisified-events.js
@@ -0,0 +1,262 @@
+/* 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_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,
+ UnexpectedEvents, waitForEvent, waitForEvents, waitForOrderedEvents */
+
+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_END = nsIAccessibleEvent.EVENT_SCROLLING_END;
+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 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.
+ */
+async function waitForEvents(events, message, ordered = false) {
+ let expected = events.expected || events;
+ let unexpected = events.unexpected || [];
+ // Next expected event index.
+ let currentIdx = 0;
+
+ let unexpectedListener = new UnexpectedEvents(unexpected);
+
+ let results = await Promise.all(
+ expected.map((evt, idx) => {
+ const [eventType, matchCriteria] = evt;
+ return waitForEvent(eventType, matchCriteria, message).then(result => {
+ return [result, idx == currentIdx++];
+ });
+ })
+ );
+
+ 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);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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..fdb4ed428f
--- /dev/null
+++ b/accessible/tests/mochitest/relations.js
@@ -0,0 +1,203 @@
+/* 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;
+
+// //////////////////////////////////////////////////////////////////////////////
+// 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..4c2fc7f9b9
--- /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(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 loadOneTab(aURI)
+ {
+ this.invoke = function loadOneTab_invoke()
+ {
+ tabBrowser().loadOneTab(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 loadOneTab_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 loadOneTab("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..e003d3f8e7
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_general.html
@@ -0,0 +1,408 @@
+<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 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="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>
+
+ <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..b50ad37a73
--- /dev/null
+++ b/accessible/tests/mochitest/role.js
@@ -0,0 +1,197 @@
+/* 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_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_SUGGESTION = nsIAccessibleRole.ROLE_SUGGESTION;
+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..7b0a23ddc1
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_aria.html
@@ -0,0 +1,439 @@
+<!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_alertdialog", ROLE_DIALOG);
+ testRole("aria_application", ROLE_APPLICATION);
+ testRole("aria_article", ROLE_ARTICLE);
+ testRole("aria_blockquote", ROLE_BLOCKQUOTE);
+ testRole("aria_button", ROLE_PUSHBUTTON);
+ testRole("aria_caption", ROLE_CAPTION);
+ testRole("aria_checkbox", ROLE_CHECKBUTTON);
+ testRole("aria_code", ROLE_CODE);
+ testRole("aria_columnheader", ROLE_COLUMNHEADER);
+ testRole("aria_combobox", ROLE_EDITCOMBOBOX);
+ testRole("aria_comment", ROLE_COMMENT);
+ testRole("aria_deletion", ROLE_CONTENT_DELETION);
+ testRole("aria_dialog", ROLE_DIALOG);
+ testRole("aria_directory", ROLE_LIST);
+ testRole("aria_document", ROLE_NON_NATIVE_DOCUMENT);
+ testRole("aria_form", ROLE_FORM);
+ testRole("aria_form_with_label", ROLE_FORM);
+ testRole("aria_feed", ROLE_GROUPING);
+ testRole("aria_figure", ROLE_FIGURE);
+ testRole("aria_grid", ROLE_TABLE);
+ testRole("aria_gridcell", ROLE_GRID_CELL);
+ testRole("aria_group", ROLE_GROUPING);
+ testRole("aria_heading", ROLE_HEADING);
+ testRole("aria_img", ROLE_GRAPHIC);
+ testRole("aria_insertion", ROLE_CONTENT_INSERTION);
+ testRole("aria_link", ROLE_LINK);
+ testRole("aria_list", ROLE_LIST);
+ testRole("aria_listbox", ROLE_LISTBOX);
+ testRole("aria_listitem", ROLE_LISTITEM);
+ testRole("aria_log", ROLE_TEXT); // weak role
+ testRole("aria_mark", ROLE_MARK);
+ testRole("aria_marquee", ROLE_ANIMATION);
+ testRole("aria_math", ROLE_FLAT_EQUATION);
+ testRole("aria_menu", ROLE_MENUPOPUP);
+ testRole("aria_menubar", ROLE_MENUBAR);
+ testRole("aria_menuitem", ROLE_MENUITEM);
+ testRole("aria_menuitemcheckbox", ROLE_CHECK_MENU_ITEM);
+ testRole("aria_menuitemradio", ROLE_RADIO_MENU_ITEM);
+ testRole("aria_note", ROLE_NOTE);
+ testRole("aria_paragraph", ROLE_PARAGRAPH);
+ testRole("aria_presentation", ROLE_TEXT); // weak role
+ testRole("aria_progressbar", ROLE_PROGRESSBAR);
+ testRole("aria_radio", ROLE_RADIOBUTTON);
+ testRole("aria_radiogroup", ROLE_RADIO_GROUP);
+ testRole("aria_region_no_name", ROLE_TEXT);
+ testRole("aria_region_has_label", ROLE_REGION);
+ testRole("aria_region_has_labelledby", ROLE_REGION);
+ testRole("aria_region_has_title", ROLE_REGION);
+ testRole("aria_region_empty_name", ROLE_TEXT);
+ testRole("aria_row", ROLE_ROW);
+ testRole("aria_rowheader", ROLE_ROWHEADER);
+ testRole("aria_scrollbar", ROLE_SCROLLBAR);
+ testRole("aria_searchbox", ROLE_ENTRY);
+ testRole("aria_separator", ROLE_SEPARATOR);
+ testRole("aria_slider", ROLE_SLIDER);
+ testRole("aria_spinbutton", ROLE_SPINBUTTON);
+ testRole("aria_status", ROLE_STATUSBAR);
+ testRole("aria_suggestion", ROLE_SUGGESTION);
+ testRole("aria_switch", ROLE_SWITCH);
+ testRole("aria_tab", ROLE_PAGETAB);
+ testRole("aria_tablist", ROLE_PAGETABLIST);
+ testRole("aria_tabpanel", ROLE_PROPERTYPAGE);
+ testRole("aria_term", ROLE_TERM);
+ testRole("aria_textbox", ROLE_ENTRY);
+ testRole("aria_timer", ROLE_TEXT); // weak role
+ testRole("aria_toolbar", ROLE_TOOLBAR);
+ testRole("aria_tooltip", ROLE_TOOLTIP);
+ testRole("aria_tree", ROLE_OUTLINE);
+ testRole("aria_treegrid", ROLE_TREE_TABLE);
+ testRole("aria_treeitem", 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("articleform", ROLE_FORM);
+
+ // Test article exposed as article
+ testRole("testArticle", ROLE_ARTICLE);
+
+ // weak roles that are forms of "live regions"
+ testRole("log_table", ROLE_TABLE);
+ testRole("timer_div", ROLE_SECTION);
+
+ // other roles that are forms of "live regions"
+ testRole("marquee_h1", ROLE_ANIMATION);
+
+ // strong landmark
+ testRole("application", ROLE_APPLICATION);
+ testRole("form", ROLE_FORM);
+ testRole("application_table", ROLE_APPLICATION);
+
+ // landmarks
+ let landmarks = ["banner", "complementary", "contentinfo",
+ "main", "navigation", "search"];
+ for (const l in landmarks) {
+ testRole(landmarks[l], ROLE_LANDMARK);
+ }
+
+ for (const l in landmarks) {
+ let id = landmarks[l] + "_table";
+ testRole(id, ROLE_LANDMARK);
+
+ let accessibleTable = getAccessible(id, [nsIAccessibleTable], null,
+ DONOTFAIL_IF_NO_INTERFACE);
+ ok(!!accessibleTable, "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_cell", 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);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // roles transformed by ARIA state attributes
+ testRole("togglebutton", ROLE_TOGGLE_BUTTON);
+ testRole("implicit_gridcell", ROLE_GRID_CELL);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ignore unknown roles, take first known
+ testRole("unknown_roles", ROLE_PUSHBUTTON);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // misc roles
+ testRole("note", ROLE_NOTE);
+ testRole("scrollbar", ROLE_SCROLLBAR);
+ testRole("dir", 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", "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", "button");
+ testRole(testDoc, ROLE_DOCUMENT);
+ document.body.setAttribute("role", "main");
+ testRole(testDoc, ROLE_DOCUMENT);
+
+ // Test equation image
+ testRole("img_eq", ROLE_FLAT_EQUATION);
+
+ // Test textual equation
+ testRole("txt_eq", ROLE_FLAT_EQUATION);
+
+ // Test initial ARIA roles on the body element.
+ testBodyRole("iframe_aria_application", ROLE_APPLICATION);
+ testBodyRole("iframe_aria_dialog", 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_button", ROLE_DOCUMENT);
+ testBodyRole("iframe_aria_main", ROLE_DOCUMENT);
+ // role="alert" is not valid on the body of content documents.
+ testBodyRole("iframe_aria_alert", 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_alertdialog" role="alertdialog"></span>
+ <span id="aria_application" role="application"></span>
+ <span id="aria_article" role="article"></span>
+ <span id="aria_blockquote" role="blockquote"></span>
+ <span id="aria_button" role="button"></span>
+ <span id="aria_caption" role="caption"></span>
+ <span id="aria_checkbox" role="checkbox"></span>
+ <span id="aria_code" role="code"></span>
+ <span id="aria_columnheader" role="columnheader"></span>
+ <span id="aria_combobox" role="combobox"></span>
+ <span id="aria_comment" role="comment"></span>
+ <span id="aria_deletion" role="deletion"></span>
+ <span id="aria_dialog" role="dialog"></span>
+ <span id="aria_directory" role="directory"></span>
+ <span id="aria_document" role="document"></span>
+ <span id="aria_form" role="form"></span>
+ <span id="aria_form_with_label" role="form" aria-label="Label"></span>
+ <span id="aria_feed" role="feed"></span>
+ <span id="aria_figure" role="figure"></span>
+ <span id="aria_grid" role="grid"></span>
+ <span id="aria_gridcell" role="gridcell"></span>
+ <span id="aria_group" role="group"></span>
+ <span id="aria_heading" role="heading"></span>
+ <span id="aria_img" role="img"></span>
+ <span id="aria_insertion" role="insertion"></span>
+ <span id="aria_link" role="link"></span>
+ <span id="aria_list" role="list"></span>
+ <span id="aria_listbox" role="listbox"></span>
+ <span id="aria_listitem" role="listitem"></span>
+ <span id="aria_log" role="log"></span>
+ <span id="aria_mark" role="mark"></span>
+ <span id="aria_marquee" role="marquee"></span>
+ <span id="aria_math" role="math"></span>
+ <span id="aria_menu" role="menu"></span>
+ <span id="aria_menubar" role="menubar"></span>
+ <span id="aria_menuitem" role="menuitem"></span>
+ <span id="aria_menuitemcheckbox" role="menuitemcheckbox"></span>
+ <span id="aria_menuitemradio" role="menuitemradio"></span>
+ <span id="aria_note" role="note"></span>
+ <span id="aria_paragraph" role="paragraph"></span>
+ <span id="aria_presentation" role="presentation" tabindex="0"></span>
+ <span id="aria_progressbar" role="progressbar"></span>
+ <span id="aria_radio" role="radio"></span>
+ <span id="aria_radiogroup" role="radiogroup"></span>
+ <span id="aria_region_no_name" role="region"></span>
+ <span id="aria_region_has_label" 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_title" 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_row" role="row"></span>
+ <span id="aria_rowheader" role="rowheader"></span>
+ <span id="aria_scrollbar" role="scrollbar"></span>
+ <span id="aria_searchbox" role="textbox"></span>
+ <span id="aria_separator" role="separator"></span>
+ <span id="aria_slider" role="slider"></span>
+ <span id="aria_spinbutton" role="spinbutton"></span>
+ <span id="aria_status" role="status"></span>
+ <span id="aria_suggestion" role="suggestion"></span>
+ <span id="aria_switch" role="switch"></span>
+ <span id="aria_tab" role="tab"></span>
+ <span id="aria_tablist" role="tablist"></span>
+ <span id="aria_tabpanel" role="tabpanel"></span>
+ <span id="aria_term" role="term"></span>
+ <span id="aria_textbox" role="textbox"></span>
+ <span id="aria_timer" role="timer"></span>
+ <span id="aria_toolbar" role="toolbar"></span>
+ <span id="aria_tooltip" role="tooltip"></span>
+ <span id="aria_tree" role="tree"></span>
+ <span id="aria_treegrid" role="treegrid"></span>
+ <span id="aria_treeitem" role="treeitem"></span>
+
+ <article id="articlemain" role="main">a main area</article>
+ <article id="articleform" role="form">a form area</article>
+
+ <div id="testArticle" 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>
+ <h1 role="marquee" id="marquee_h1">marquee</h1>
+ <div role="timer" id="timer_div">timer</div>
+
+ <!-- landmarks -->
+ <div role="application" id="application">application</div>
+ <div role="form" id="form">form</div>
+
+ <!-- weak landmarks -->
+ <div role="banner" id="banner">banner</div>
+ <div role="complementary" id="complementary">complementary</div>
+ <div role="contentinfo" id="contentinfo">contentinfo</div>
+ <div role="main" id="main">main</div>
+ <div role="navigation" id="navigation">navigation</div>
+ <div role="search" id="search">search</div>
+
+ <!-- landmarks are tables -->
+ <table role="application" id="application_table">application table
+ <tr><td>hi<td></tr></table>
+ <table role="banner" id="banner_table">banner table
+ <tr><td>hi<td></tr></table>
+ <table role="complementary" id="complementary_table">complementary table
+ <tr><td>hi<td></tr></table>
+ <table role="contentinfo" id="contentinfo_table">contentinfo table
+ <tr><td>hi<td></tr></table>
+ <table role="main" id="main_table">main table
+ <tr><td>hi<td></tr></table>
+ <table role="navigation" id="navigation_table">navigation table
+ <tr><td>hi<td></tr></table>
+ <table role="search" id="search_table">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>
+
+ <!-- user agents must not map abstract roles to platform API -->
+ <!-- test abstract base type roles -->
+ <div role="composite" id="composite">composite</div>
+ <div role="landmark" id="landmark">landmark</div>
+ <div role="roletype" id="roletype">roletype</div>
+ <div role="structure" id="structure">structure</div>
+ <div role="widget" id="widget">widget</div>
+ <div role="window" id="window">window</div>
+ <!-- test abstract input roles -->
+ <div role="input" id="input">input</div>
+ <div role="range" id="range">range</div>
+ <div role="select" id="select">select</div>
+ <!-- test abstract structure roles -->
+ <div role="section" id="section">section</div>
+ <div role="sectionhead" id="sectionhead">sectionhead</div>
+
+ <!-- roles transformed by ARIA roles of ancestors -->
+ <table role="grid">
+ <tr>
+ <td id="implicit_gridcell">foo</td>
+ </tr>
+ </table>
+
+ <!-- roles transformed by ARIA state attributes -->
+ <button aria-pressed="true" id="togglebutton">
+
+ <!-- take the first known mappable role -->
+ <div role="wiggly:worm abc123 button" id="unknown_roles">worm button</div>
+
+ <!-- misc roles -->
+ <div role="note" id="note">note</div>
+ <div role="scrollbar" id="scrollbar">scrollbar</div>
+
+ <div id="dir" 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>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>
+
+ <iframe id="iframe_aria_application"
+ 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_document"
+ 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_main"
+ src="data:text/html,<body role='main'>"></iframe>
+ <iframe id="iframe_aria_alert"
+ 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..77444e8187
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_svg.html
@@ -0,0 +1,88 @@
+<!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);
+
+ 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>
+ </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..e71fca489b
--- /dev/null
+++ b/accessible/tests/mochitest/selectable.js
@@ -0,0 +1,123 @@
+/* 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 = getAccessible(aSelectedChildren[idx]);
+ var actualAcc = selectedChildren.queryElementAt(idx, nsIAccessible);
+ is(
+ actualAcc,
+ expectedAcc,
+ 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 = getAccessible(aSelectedChildren[idx]);
+ is(
+ acc.getSelectedItemAt(idx),
+ expectedAcc,
+ 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++) {
+ if (child == getAccessible(aSelectedChildren[jdx])) {
+ 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..8f109e8b81
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/a11y.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/treeview.css
+
+[test_aria.html]
+[test_listbox.xhtml]
+[test_menu.xhtml]
+[test_menulist.xhtml]
+[test_select.html]
+[test_tabs.xhtml]
+[test_tree.xhtml]
diff --git a/accessible/tests/mochitest/selectable/test_aria.html b/accessible/tests/mochitest/selectable/test_aria.html
new file mode 100644
index 0000000000..46ecd94031
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_aria.html
@@ -0,0 +1,246 @@
+<html>
+
+<head>
+ <title>nsIAccessibleSelectable ARIA widgets testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ </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"
+ src="../selectable.js"></script>
+
+ <script type="application/javascript">
+ function testSelectable(aID, aSelectableChildren) {
+ var acc = getAccessible(aID, [nsIAccessibleSelectable]);
+
+ testSelectableSelection(acc, []);
+
+ acc.selectAll();
+ testSelectableSelection(acc, aSelectableChildren);
+
+ acc.unselectAll();
+ testSelectableSelection(acc, []);
+ }
+
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // role="tablist"
+
+ let id = "tablist_single";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // role="tablist" aria-multiselectable
+
+ id = "tablist_multi";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ let select = getAccessible(id, [nsIAccessibleSelectable]);
+ select.addItemToSelection(0);
+ testSelectableSelection(id, [ "tab_multi1" ]);
+ select.removeItemFromSelection(0);
+ testSelectableSelection(id, [ ]);
+ select.selectAll();
+ testSelectableSelection(id, [ "tab_multi1", "tab_multi2" ]);
+ select.unselectAll();
+ testSelectableSelection(id, [ ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // role="listbox"
+
+ id = "listbox1";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // role="listbox" aria-multiselectable
+
+ id = "listbox2";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ select = getAccessible(id, [nsIAccessibleSelectable]);
+ select.addItemToSelection(0);
+ testSelectableSelection(id, [ "listbox2_item1" ]);
+ select.removeItemFromSelection(0);
+ testSelectableSelection(id, [ ]);
+ select.selectAll();
+ testSelectableSelection(id, [ "listbox2_item1", "listbox2_item2" ]);
+ select.unselectAll();
+ testSelectableSelection(id, [ ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // role="grid"
+
+ id = "grid1";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // role="tree"
+
+ id = "tree1";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // role="treegrid"
+
+ id = "treegrid1";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // role="grid" aria-multiselectable, selectable children in subtree
+
+ id = "grid2";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectable(id,
+ ["grid2_colhead1", "grid2_colhead2", "grid2_colhead3",
+ "grid2_rowhead", "grid2_cell1", "grid2_cell2"]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=530014"
+ title="ARIA single selectable widget should implement nsIAccessibleSelectable">
+ Mozilla Bug 530014
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=566551"
+ title="ARIA grid and accessible selectable methods shouldn't use GetNextSibling">
+ Mozilla Bug 566551
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176"
+ title="add pseudo SelectAccessible interface">
+ Mozilla Bug 590176
+ </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_single">
+ <div role="tab">tab1</div>
+ <div role="tab">tab2</div>
+ </div>
+
+ <div role="tablist" id="tablist_multi" aria-multiselectable="true">
+ <div role="tab" id="tab_multi1">tab1</div>
+ <div role="tab" id="tab_multi2">tab2</div>
+ </div>
+
+ <div role="listbox" id="listbox1">
+ <div role="option">item1</div>
+ <div role="option">item2</div>
+ </div>
+
+ <div role="listbox" id="listbox2" aria-multiselectable="true">
+ <div role="option" id="listbox2_item1">item1</div>
+ <div role="option" id="listbox2_item2">item2</div>
+ </div>
+
+ <div role="grid" id="grid1">
+ <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="tree1">
+ <div role="treeitem">
+ item1
+ <div role="group">
+ <div role="treeitem">item1.1</div>
+ </div>
+ </div>
+ <div>item2</div>
+ </div>
+
+ <div role="treegrid" id="treegrid1">
+ <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>
+
+ <table tabindex="0" border="2" cellspacing="0" id="grid2" role="grid"
+ aria-multiselectable="true">
+ <thead>
+ <tr>
+ <th tabindex="-1" role="columnheader" id="grid2_colhead1"
+ style="width:6em">Entry #</th>
+ <th tabindex="-1" role="columnheader" id="grid2_colhead2"
+ style="width:10em">Date</th>
+ <th tabindex="-1" role="columnheader" id="grid2_colhead3"
+ style="width:20em">Expense</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td tabindex="-1" role="rowheader" id="grid2_rowhead"
+ aria-readonly="true">1</td>
+ <td tabindex="-1" role="gridcell" id="grid2_cell1"
+ aria-selected="false">03/14/05</td>
+ <td tabindex="-1" role="gridcell" id="grid2_cell2"
+ aria-selected="false">Conference Fee</td>
+ </tr>
+ </tobdy>
+ </table>
+</body>
+</html>
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_select.html b/accessible/tests/mochitest/selectable/test_select.html
new file mode 100644
index 0000000000..324831a498
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_select.html
@@ -0,0 +1,241 @@
+<html>
+
+<head>
+ <title>nsIAccessibleSelectable HTML select testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ </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"
+ src="../selectable.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest() {
+ // ////////////////////////////////////////////////////////////////////////
+ // select@size="1" 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 2nd item
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "cb1_item2" ], "addItemToSelection(1): ");
+
+ // unselect 2nd item, 1st item gets selected automatically
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ "cb1_item1" ],
+ "removeItemFromSelection(1): ");
+
+ // doesn't change selection
+ is(select.selectAll(), false,
+ "No way to select all items in combobox '" + id + "'");
+ testSelectableSelection(select, [ "cb1_item1" ], "selectAll: ");
+
+ // doesn't change selection
+ select.unselectAll();
+ testSelectableSelection(select, [ "cb1_item1" ], "unselectAll: ");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // select@size="1" with optgroups
+
+ id = "combobox2";
+ combobox = getAccessible(id);
+ comboboxList = combobox.firstChild;
+ ok(isAccessible(comboboxList, [nsIAccessibleSelectable]),
+ "No selectable accessible for list of " + id);
+
+ select = getAccessible(comboboxList, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ "cb2_item1" ]);
+
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "cb2_item2" ]);
+
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ "cb2_item1" ]);
+
+ is(select.selectAll(), false,
+ "No way to select all items in combobox " + id + "'");
+ testSelectableSelection(select, [ "cb2_item1" ]);
+
+ select.unselectAll();
+ testSelectableSelection(select, [ "cb2_item1" ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // select@size="4" aka single selectable listbox
+
+ id = "listbox";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ select = getAccessible(id, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ ]);
+
+ // select 2nd item
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "lb1_item2" ], "addItemToSelection(1): ");
+
+ // unselect 2nd item, 1st item gets selected automatically
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ ],
+ "removeItemFromSelection(1): ");
+
+ // doesn't change selection
+ is(select.selectAll(), false,
+ "No way to select all items in single selectable listbox '" + id + "'");
+ testSelectableSelection(select, [ ], "selectAll: ");
+
+ // doesn't change selection
+ select.unselectAll();
+ testSelectableSelection(select, [ ], "unselectAll: ");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // select@size="4" with optgroups, single selectable
+
+ id = "listbox2";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ select = getAccessible(id, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ ]);
+
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "lb2_item2" ]);
+
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ ]);
+
+ is(select.selectAll(), false,
+ "No way to select all items in single selectable listbox " + id + "'");
+ testSelectableSelection(select, [ ]);
+
+ select.unselectAll();
+ testSelectableSelection(select, [ ]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // select@size="4" multiselect aka listbox
+
+ id = "listbox3";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ select = getAccessible(id, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ ]);
+
+ select.addItemToSelection(0);
+ testSelectableSelection(select, [ "lb3_item1" ], "addItemToSelection: ");
+
+ select.removeItemFromSelection(0);
+ testSelectableSelection(select, [ ], "removeItemFromSelection: ");
+
+ is(select.selectAll(), true,
+ "All items in listbox '" + id + "' should be selected");
+ testSelectableSelection(select, [ "lb3_item1", "lb3_item2"],
+ "selectAll: ");
+
+ select.unselectAll();
+ testSelectableSelection(select, [ ], "unselectAll: ");
+
+ // ////////////////////////////////////////////////////////////////////////
+ // select@size="4" multiselect with optgroups
+
+ id = "listbox4";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ select = getAccessible(id, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ ]);
+
+ select.addItemToSelection(0);
+ testSelectableSelection(select, [ "lb4_item1" ]);
+
+ select.removeItemFromSelection(0);
+ testSelectableSelection(select, [ ]);
+
+ is(select.selectAll(), true,
+ "All items in listbox '" + id + "' should be selected");
+ testSelectableSelection(select, [ "lb4_item1", "lb4_item2"]);
+
+ select.unselectAll();
+ testSelectableSelection(select, [ ]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=530014"
+ title="ARIA single selectable widget should implement nsIAccessibleSelectable">
+ Mozilla Bug 530014
+ </a><br>
+ <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>
+
+ <select id="combobox">
+ <option id="cb1_item1">option1</option>
+ <option id="cb1_item2">option2</option>
+ </select>
+
+ <select id="combobox2">
+ <option id="cb2_item1">option1</option>
+ <optgroup>optgroup
+ <option id="cb2_item2">option2</option>
+ </optgroup>
+ </select>
+
+ <select id="listbox" size="4">
+ <option id="lb1_item1">option1</option>
+ <option id="lb1_item2">option2</option>
+ </select>
+
+ <select id="listbox2" size="4">
+ <option id="lb2_item1">option1</option>
+ <optgroup>optgroup>
+ <option id="lb2_item2">option2</option>
+ </optgroup>
+ </select>
+
+ <select id="listbox3" size="4" multiple="true">
+ <option id="lb3_item1">option1</option>
+ <option id="lb3_item2">option2</option>
+ </select>
+
+ <select id="listbox4" size="4" multiple="true">
+ <option id="lb4_item1">option1</option>
+ <optgroup>optgroup>
+ <option id="lb4_item2">option2</option>
+ </optgroup>
+ </select>
+
+</body>
+</html>
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..8e394b11a1
--- /dev/null
+++ b/accessible/tests/mochitest/states.js
@@ -0,0 +1,363 @@
+// //////////////////////////////////////////////////////////////////////////////
+// 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_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 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,
+ "Focussed " + 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..de83f2aecc
--- /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_CHECKABLE | STATE_PRESSED);
+ 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..d110faeb04
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_doc_busy.html
@@ -0,0 +1,113 @@
+<!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");
+
+ function matchDocBusyChange(isBusy) {
+ return function(event) {
+ const scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent);
+ return (
+ event.DOMNode == document &&
+ scEvent.state === STATE_BUSY &&
+ scEvent.isEnabled === isBusy
+ );
+ };
+ }
+
+ 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;
+ // We never actually wait on this; we just check the booleans it sets.
+ // We still need to hold a reference so it doesn't get garbage collected
+ // before it does anything.
+ // eslint-disable-next-line no-unused-vars
+ 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 downloadPromptOpened = BrowserTestUtils.domWindowOpened(null,
+ async win => {
+ info("Window opened, waiting for load event");
+ await BrowserTestUtils.waitForEvent(win, "load");
+ info("Window loaded, checking if download prompt");
+ return win.location &&
+ win.location.href == "chrome://mozapps/content/downloads/unknownContentType.xhtml";
+ }
+ );
+
+ info("Clicking link to trigger download");
+ synthesizeMouse(getNode("link"), 1, 1, {});
+ info("Waiting for download prompt to open");
+ const downloadWin = await downloadPromptOpened;
+
+ // 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");
+ downloadWin.close();
+ // 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..37ff568458
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_selects.html
@@ -0,0 +1,196 @@
+<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">
+ function openComboboxNCheckStates(aID) {
+ this.combobox = getAccessible(aID);
+ this.comboboxList = this.combobox.firstChild;
+ this.comboboxOption = this.comboboxList.firstChild;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.comboboxOption),
+ ];
+
+ this.invoke = function openComboboxNCheckStates_invoke() {
+ getNode(aID).focus();
+ synthesizeKey("VK_DOWN", { altKey: true });
+ };
+
+ this.finalCheck = function openComboboxNCheckStates_invoke() {
+ // Expanded state on combobox.
+ testStates(this.combobox, STATE_EXPANDED);
+
+ // Floating state on combobox list.
+ testStates(this.comboboxList, STATE_FLOATING);
+ };
+
+ this.getID = function openComboboxNCheckStates_getID() {
+ return "open combobox and test states";
+ };
+ }
+
+ // gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ 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,
+ EXT_STATE_ACTIVE, STATE_FOCUSED, 0);
+
+ var opt2 = comboboxList.lastChild;
+ testStates(opt2, STATE_SELECTABLE | STATE_FOCUSABLE, 0, STATE_SELECTED, 0,
+ STATE_FOCUSED, EXT_STATE_ACTIVE);
+
+ // 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);
+
+ // open combobox
+ gQueue = new eventQueue();
+ gQueue.push(new openComboboxNCheckStates("combobox"));
+ 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=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..764c1ad01c
--- /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,
+ 0,
+ "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..28d8c995db
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_visibility.xhtml
@@ -0,0 +1,151 @@
+<?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 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>
+
+ <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: 5em;">
+ <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/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..83fdf1e207
--- /dev/null
+++ b/accessible/tests/mochitest/table.js
@@ -0,0 +1,1033 @@
+/**
+ * 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) {
+ var columnPickerObj = {
+ role: ROLE_PUSHBUTTON,
+ };
+
+ headersObj.children.push(columnPickerObj);
+ }
+
+ 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 unselectColumn method of accessible table.
+ */
+function testUnselectTableColumn(aIdentifier, aColIdx, aCellsArray) {
+ var acc = getAccessible(aIdentifier, [nsIAccessibleTable]);
+ if (!acc) {
+ return;
+ }
+
+ var rowCount = aCellsArray.length;
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ // Unselect origin cell.
+ var [origRowIdx, origColIdx] = getOrigRowAndColumn(
+ aCellsArray,
+ rowIdx,
+ aColIdx
+ );
+ aCellsArray[origRowIdx][origColIdx] = false;
+ }
+
+ acc.unselectColumn(aColIdx);
+ testTableSelection(
+ aIdentifier,
+ aCellsArray,
+ "Unselect " + aColIdx + " column: "
+ );
+}
+
+/**
+ * Test selectColumn method of accessible table.
+ */
+function testSelectTableColumn(aIdentifier, aColIdx, aCellsArray) {
+ var acc = getAccessible(aIdentifier, [nsIAccessibleTable]);
+ if (!acc) {
+ return;
+ }
+
+ var rowCount = aCellsArray.length;
+ var colsCount = aCellsArray[0].length;
+
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ var cellState = aCellsArray[rowIdx][colIdx];
+
+ if (colIdx == aColIdx) {
+ // select target column
+ if (!(cellState & kSpanned)) {
+ // Select the cell if it is origin.
+ aCellsArray[rowIdx][colIdx] = true;
+ } else {
+ // If the cell is spanned then search origin cell and select it.
+ var [origRowIdx, origColIdx] = getOrigRowAndColumn(
+ aCellsArray,
+ rowIdx,
+ colIdx
+ );
+ aCellsArray[origRowIdx][origColIdx] = true;
+ }
+ } else if (!(cellState & kSpanned)) {
+ // unselect other columns
+ if (colIdx > aColIdx) {
+ // Unselect the cell if traversed column index is greater than column
+ // index of target cell.
+ aCellsArray[rowIdx][colIdx] = false;
+ } else if (!(aCellsArray[rowIdx][aColIdx] & kColSpanned)) {
+ // Unselect the cell if the target cell is not row spanned.
+ aCellsArray[rowIdx][colIdx] = false;
+ } else {
+ // Unselect the cell if it is not spanned to the target cell.
+ for (
+ var spannedColIdx = colIdx + 1;
+ spannedColIdx < aColIdx;
+ spannedColIdx++
+ ) {
+ var spannedCellState = aCellsArray[rowIdx][spannedColIdx];
+ if (!(spannedCellState & kRowSpanned)) {
+ aCellsArray[rowIdx][colIdx] = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ acc.selectColumn(aColIdx);
+ testTableSelection(
+ aIdentifier,
+ aCellsArray,
+ "Select " + aColIdx + " column: "
+ );
+}
+
+/**
+ * Test unselectRow method of accessible table.
+ */
+function testUnselectTableRow(aIdentifier, aRowIdx, aCellsArray) {
+ var acc = getAccessible(aIdentifier, [nsIAccessibleTable]);
+ if (!acc) {
+ return;
+ }
+
+ var colsCount = aCellsArray[0].length;
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ // Unselect origin cell.
+ var [origRowIdx, origColIdx] = getOrigRowAndColumn(
+ aCellsArray,
+ aRowIdx,
+ colIdx
+ );
+ aCellsArray[origRowIdx][origColIdx] = false;
+ }
+
+ acc.unselectRow(aRowIdx);
+ testTableSelection(
+ aIdentifier,
+ aCellsArray,
+ "Unselect " + aRowIdx + " row: "
+ );
+}
+
+/**
+ * Test selectRow method of accessible table.
+ */
+function testSelectTableRow(aIdentifier, aRowIdx, aCellsArray) {
+ var acc = getAccessible(aIdentifier, [nsIAccessibleTable]);
+ if (!acc) {
+ return;
+ }
+
+ var rowCount = aCellsArray.length;
+ var colsCount = aCellsArray[0].length;
+
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ var cellState = aCellsArray[rowIdx][colIdx];
+
+ if (rowIdx == aRowIdx) {
+ // select the given row
+ if (!(cellState & kSpanned)) {
+ // Select the cell if it is origin.
+ aCellsArray[rowIdx][colIdx] = true;
+ } else {
+ // If the cell is spanned then search origin cell and select it.
+ var [origRowIdx, origColIdx] = getOrigRowAndColumn(
+ aCellsArray,
+ rowIdx,
+ colIdx
+ );
+
+ aCellsArray[origRowIdx][origColIdx] = true;
+ }
+ } else if (!(cellState & kSpanned)) {
+ // unselect other rows
+ if (rowIdx > aRowIdx) {
+ // Unselect the cell if traversed row index is greater than row
+ // index of target cell.
+ aCellsArray[rowIdx][colIdx] = false;
+ } else if (!(aCellsArray[aRowIdx][colIdx] & kRowSpanned)) {
+ // Unselect the cell if the target cell is not row spanned.
+ aCellsArray[rowIdx][colIdx] = false;
+ } else {
+ // Unselect the cell if it is not spanned to the target cell.
+ for (
+ var spannedRowIdx = rowIdx + 1;
+ spannedRowIdx < aRowIdx;
+ spannedRowIdx++
+ ) {
+ var spannedCellState = aCellsArray[spannedRowIdx][colIdx];
+ if (!(spannedCellState & kRowSpanned)) {
+ aCellsArray[rowIdx][colIdx] = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ acc.selectRow(aRowIdx);
+ testTableSelection(aIdentifier, aCellsArray, "Select " + aRowIdx + " row: ");
+}
+
+/**
+ * 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..3e4d7ccc53
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_mtable.html
@@ -0,0 +1,126 @@
+<!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);
+
+ 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>
+ </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..784939bf03
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_sels_ariagrid.html
@@ -0,0 +1,159 @@
+<!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);
+ testUnselectTableColumn("table", 3, cellsArray);
+ testUnselectTableRow("table", 3, cellsArray);
+ testSelectTableColumn("table", 0, cellsArray);
+ testSelectTableRow("table", 0, cellsArray);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // a bit strange ARIA grid
+ cellsArray =
+ [
+ [ false, false],
+ [ false, false],
+ ];
+
+ testTableSelection("grid2", cellsArray);
+ testSelectTableColumn("grid2", 0, cellsArray);
+ testSelectTableRow("grid2", 0, cellsArray);
+ testUnselectTableColumn("grid2", 0, cellsArray);
+ testUnselectTableRow("grid2", 0, cellsArray);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA grid (column and row headers)
+
+ cellsArray =
+ [
+ [ undefined, true, false],
+ [ undefined, true, false],
+ ];
+
+ testTableSelection("grid3", cellsArray);
+ testSelectTableColumn("grid3", 0, cellsArray);
+ testSelectTableRow("grid3", 0, cellsArray);
+ testUnselectTableColumn("grid3", 0, cellsArray);
+ testUnselectTableRow("grid3", 0, 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..0c87112d17
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_sels_table.html
@@ -0,0 +1,178 @@
+<!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 rowCount = 4;
+ for (let rowIdx = 0; rowIdx < rowCount; rowIdx++)
+ testSelectTableRow("table", rowIdx, cellsArray);
+
+ for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ testSelectTableRow("table", rowIdx, cellsArray);
+ testUnselectTableRow("table", rowIdx, cellsArray);
+ }
+
+ var columsCount = 8;
+ for (let colIdx = 0; colIdx < columsCount; colIdx++)
+ testSelectTableColumn("table", colIdx, cellsArray);
+
+ for (let colIdx = 0; colIdx < columsCount; colIdx++) {
+ testSelectTableColumn("table", colIdx, cellsArray);
+ testUnselectTableColumn("table", colIdx, 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=417929"
+ title="nsIAccessiblTable selectRows does not unselect previously selected rows">
+ Mozilla Bug 417929
+ </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..254377b342
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_sels_tree.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="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);
+ testSelectTableRow("tree", 0, cellsArray);
+ testUnselectTableRow("tree", 0, 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..8541b6a9da
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_struct_ariagrid.html
@@ -0,0 +1,147 @@
+<!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);
+
+ 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>
+</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_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..77bc74eceb
--- /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 = ["jump"];
+ 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..486e615b65
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_atcaretoffset.html
@@ -0,0 +1,423 @@
+<!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, kTodo, kTodo, kTodo ],
+ [ "TextAfterOffset", BOUNDARY_WORD_END, kTodo, kTodo, kTodo ],
+ ];
+ 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..12d94a9f6c
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_hypertext.html
@@ -0,0 +1,149 @@
+<!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", kTodo, kOk, kTodo);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // 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>
+
+</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..3793a21a75
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_lineboundary.html
@@ -0,0 +1,341 @@
+<!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],
+ [7, 7, "", 7, 7],
+ [8, 15, "Line 3 ", 8, 15]]);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // __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] ]);
+
+ 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 -->
+</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..9a9f546e96
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_paragraphboundary.html
@@ -0,0 +1,140 @@
+<!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]]);
+
+ // 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>
+ <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..ef4677472c
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_selection.html
@@ -0,0 +1,96 @@
+<!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...
+
+ 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>
+
+</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..b3eb5dc0f4
--- /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", kTodo, kOk, kTodo ],
+ [ 8, "ml_edivbr1", kTodo, kOk, kTodo ],
+ [ 9, "ml_divbr1", kTodo, kOk, kTodo ],
+ [ 9, "ml_edivbr1", kTodo, kOk, kTodo ] ] ],
+ [ 13, 18, "\n\ntwo", 7, 12 ],
+ [ 19, 19, " words", 12, 18,
+ [ [ 19, "ml_divbr1", kTodo, kTodo, kTodo ],
+ [ 19, "ml_edivbr1", kTodo, kTodo, kTodo ] ] ],
+ ] );
+
+ 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", kTodo, kTodo, kOk ],
+ [ 18, "ml_edivbr1", kTodo, kTodo, kOk ],
+ [ 19, "ml_divbr1", kTodo, kTodo, kOk ],
+ [ 19, "ml_edivbr1", kTodo, kTodo, 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", kTodo, kTodo, kOk ],
+ [ 18, "ml_edivbr1", kTodo, kTodo, 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", kTodo, kOk, kTodo ]
+ ] ],
+ [ 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..98855d84b5
--- /dev/null
+++ b/accessible/tests/mochitest/textattrs/a11y.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/moz.png
+
+[test_general.html]
+[test_invalid.html]
+[test_spelling.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..fe2b4a6dd3
--- /dev/null
+++ b/accessible/tests/mochitest/textattrs/test_general.html
@@ -0,0 +1,737 @@
+<html>
+
+<head>
+ <title>Text attributes tests</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style type="text/css">
+ .gencontent:before { content: "*"; }
+ .gencontent:after { content: "*"; }
+ </style>
+
+ <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 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);
+
+ attrs = { "text-position": "super", "font-size": "10pt" };
+ testTextAttrs(ID, 5, attrs, defAttrs, 5, 13);
+
+ attrs = {};
+ testTextAttrs(ID, 13, attrs, defAttrs, 13, 27);
+
+ attrs = { "text-position": "super" };
+ testTextAttrs(ID, 27, attrs, defAttrs, 27, 35);
+
+ attrs = {};
+ testTextAttrs(ID, 35, attrs, defAttrs, 35, 39);
+
+ attrs = { "text-position": "sub", "font-size": "10pt" };
+ testTextAttrs(ID, 39, attrs, defAttrs, 39, 50);
+
+ attrs = {};
+ testTextAttrs(ID, 50, attrs, defAttrs, 50, 55);
+
+ attrs = { "text-position": "sub" };
+ testTextAttrs(ID, 55, attrs, defAttrs, 55, 64);
+
+ attrs = {};
+ testTextAttrs(ID, 64, attrs, defAttrs, 64, 69);
+
+ attrs = { "text-position": "super" };
+ testTextAttrs(ID, 69, attrs, defAttrs, 69, 84);
+
+ attrs = {};
+ testTextAttrs(ID, 84, attrs, defAttrs, 84, 89);
+
+ attrs = { "text-position": "sub" };
+ testTextAttrs(ID, 89, attrs, defAttrs, 89, 102);
+
+ attrs = {};
+ testTextAttrs(ID, 102, attrs, defAttrs, 102, 107);
+
+ attrs = { "text-position": "super" };
+ testTextAttrs(ID, 107, attrs, defAttrs, 107, 123);
+
+ attrs = {};
+ testTextAttrs(ID, 123, attrs, defAttrs, 123, 128);
+
+ attrs = { "text-position": "sub" };
+ testTextAttrs(ID, 128, attrs, defAttrs, 128, 142);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // 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": "rgb(0, 0, 0)",
+ };
+ 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": "rgb(0, 0, 0)",
+ };
+ testTextAttrs(ID, 15, attrs, defAttrs, 15, 22);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": "rgb(0, 0, 0)",
+ };
+ 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": "rgb(0, 0, 0)",
+ };
+ 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",
+ };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 3);
+ testTextAttrs(ID, 3, { }, defAttrs, 3, 7);
+ 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);
+ 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>
+
+</body>
+</html>
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_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/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..b40f1a982c
--- /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, 15);
+ 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..fb1c0884df
--- /dev/null
+++ b/accessible/tests/mochitest/textrange/test_selection.html
@@ -0,0 +1,129 @@
+<!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");
+
+ 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>
+</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..a229eeca83
--- /dev/null
+++ b/accessible/tests/mochitest/tree/a11y.ini
@@ -0,0 +1,56 @@
+[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_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_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..1572584689
--- /dev/null
+++ b/accessible/tests/mochitest/tree/dockids.html
@@ -0,0 +1,30 @@
+<!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");
+ l.href = "http://www.mozilla.org";
+ l.textContent = "Another ";
+ document.documentElement.appendChild(l);
+
+ l = document.createElement("a");
+ 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_globals.html b/accessible/tests/mochitest/tree/test_aria_globals.html
new file mode 100644
index 0000000000..f7180b7bb1
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_globals.html
@@ -0,0 +1,124 @@
+<!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",
+ "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="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_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..67c81239f5
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_button.xhtml
@@ -0,0 +1,72 @@
+<?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);
+
+ 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"/>
+ </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..b08a0e3d83
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_combobox.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"
+ 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"
+ autocompletesearch="unifiedcomplete"
+ 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..371146e5eb
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_display_contents.html
@@ -0,0 +1,49 @@
+<!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);
+
+ 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>
+</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..a8bd1f0e78
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_divs.html
@@ -0,0 +1,333 @@
+<!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 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);
+
+ 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>
+</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..e5236d713a
--- /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: [
+ { TEXT_LEAF: [ ] }
+ ] },
+ { 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_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..5e56165292
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_list.html
@@ -0,0 +1,354 @@
+<!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
+ role: ROLE_STATICTEXT,
+ name: "foo",
+ },
+ { // ::marker content counter
+ role: ROLE_STATICTEXT,
+ name: "1",
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Oranges",
+ },
+ ],
+ },
+ { // li
+ role: ROLE_LISTITEM,
+ children: [
+ { // ::marker content text
+ role: ROLE_STATICTEXT,
+ name: "foo",
+ },
+ { // ::marker content counter
+ role: ROLE_STATICTEXT,
+ name: "2",
+ },
+ {
+ 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..1880f9c8fa
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_media.html
@@ -0,0 +1,121 @@
+<!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;
+
+ 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..4f9fb35ecf
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_select.html
@@ -0,0 +1,137 @@
+<!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_STATICTEXT,
+ children: [ ],
+ },
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ },
+ ],
+ },
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+ 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..65a4885b76
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_tabbox.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="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_PAGETAB,
+ 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..492112b003
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_table.html
@@ -0,0 +1,458 @@
+<!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);
+
+ 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;overflow:auto;">
+ <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>
+</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..5d0aedf2b7
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_table_2.html
@@ -0,0 +1,240 @@
+<!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",
+ },
+ ] },
+ { 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..73d7b54f58
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_table_3.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: #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",
+ },
+ ] },
+ { ROW: [
+ { CELL: [ { role: TEXT_LEAF, name: "Film Title" } ] },
+ { CELL: [ { role: TEXT_LEAF, name: "Released" } ] },
+ { CELL: [ { role: TEXT_LEAF, name: "Studio" } ] },
+ { CELL: [ { role: TEXT_LEAF, name: "Worldwide Gross" } ] },
+ { CELL: [ { role: TEXT_LEAF, name: "Domestic Gross" } ] },
+ { CELL: [ { role: TEXT_LEAF, name: "Foreign Gross" } ] },
+ { CELL: [ { role: TEXT_LEAF, name: "Budget" } ] },
+ ] },
+ { ROW: [
+ { role: CELL },
+ ] },
+ { ROW: [
+ { CELL: [ { 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: [
+ { CELL: [ { 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..890a46209c
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_tree.xhtml
@@ -0,0 +1,180 @@
+<?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: [ { 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..b52eef3c84
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/a11y.ini
@@ -0,0 +1,44 @@
+[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_bug1100602.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_list.html]
+[test_list_editabledoc.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..d7e6fa58fb
--- /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: fixed; 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..200a302085
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
@@ -0,0 +1,797 @@
+<!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;
+
+ function doTest() {
+ gQueue = new eventQueue();
+
+ // 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(); // 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="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>
+</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_bug1100602.html b/accessible/tests/mochitest/treeupdate/test_bug1100602.html
new file mode 100644
index 0000000000..7233b585c1
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1100602.html
@@ -0,0 +1,103 @@
+<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">
+ /**
+ * Change list style type to none.
+ */
+ function hideBullet() {
+ this.eventSeq = [];
+ this.liAcc = getAccessible("list_element");
+ this.bullet = this.liAcc.firstChild;
+
+ this.eventSeq.push(new invokerChecker(EVENT_HIDE, this.bullet));
+ this.eventSeq.push(new invokerChecker(EVENT_REORDER, this.liAcc));
+
+ this.invoke = function hideBullet_invoke() {
+ getNode("list").setAttribute("style", "list-style-type: none;");
+ };
+
+ this.finalCheck = function hideBullet_finalCheck() {
+ is(this.liAcc.name, "list element",
+ "Check that first child of LI is not a bullet.");
+ };
+
+ this.getID = function hideBullet_getID() {
+ return "Hide bullet by setting style to none";
+ };
+ }
+
+ /**
+ * Change list style type to circles.
+ */
+ function showBullet() {
+ this.eventSeq = [];
+ this.liAcc = getAccessible("list_element");
+
+ this.eventSeq.push(new invokerChecker(EVENT_SHOW,
+ function(aNode) { return aNode.firstChild; },
+ this.liAcc));
+ this.eventSeq.push(new invokerChecker(EVENT_REORDER, this.liAcc));
+
+ this.invoke = function showBullet_invoke() {
+ getNode("list").setAttribute("style", "list-style-type: circle;");
+ };
+
+ this.finalCheck = function showBullet_finalCheck() {
+ is(this.liAcc.name, "◦ list element",
+ "Check that first child of LI is a circle bullet.");
+ };
+
+ this.getID = function showBullet_getID() {
+ return "Show bullet by setting style to circle";
+ };
+ }
+
+ var gQueue = null;
+ function doTest() {
+ var list = getNode("list");
+ list.setAttribute("style", "list-style-type: circle;");
+
+ gQueue = new eventQueue();
+ gQueue.push(new hideBullet());
+ gQueue.push(new showBullet());
+ gQueue.invoke(); // 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">
+ <li id="list_element">list element</li>
+ </ol>
+
+</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..6766d08a14
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1189277.html
@@ -0,0 +1,81 @@
+<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");
+ 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..3f91838e59
--- /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..59ab5cae76
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_cssoverflow.html
@@ -0,0 +1,150 @@
+<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 hidden 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("container3"));
+
+ 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 id="scrollarea2" style="overflow:hidden;"></div></div>
+ <div id="container3"><div style="overflow: hidden; 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..1a3d2c8877
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_delayed_removal.html
@@ -0,0 +1,453 @@
+<!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_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_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");
+ }
+
+ 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();
+ 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="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..b401b54eff
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_imagemap.html
@@ -0,0 +1,399 @@
+<!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",
+ "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",
+ "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",
+ "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_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_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..4684337b3d
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_menubutton.xhtml
@@ -0,0 +1,140 @@
+<?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, aMenuItemRole)
+ {
+ var menuItemRole = aMenuItemRole || ROLE_MENUITEM;
+ this.button = getAccessible(aButtonID);
+ this.menupopup = this.button.firstChild;
+
+ var checker = new invokerChecker(EVENT_REORDER, this.menupopup);
+ this.__proto__ = new synthClick(aButtonID, checker);
+
+ this.invoke = function openMenu_invoke()
+ {
+ var tree =
+ { PUSHBUTTON: [
+ { MENUPOPUP: [ ] }
+ ] };
+ testAccessibleTree(this.button, tree);
+
+ this.__proto__.invoke();
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ var tree =
+ { PUSHBUTTON: [
+ { MENUPOPUP: [
+ { role: menuItemRole, children: [ ] },
+ { role: menuItemRole, children: [ ] }
+ ] }
+ ] };
+ testAccessibleTree(this.button, tree);
+
+ 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;
+ gQueue.push(new openMenu(columnPickerBtn, ROLE_CHECK_MENU_ITEM));
+ 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..288de1dd1d
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_optgroup.html
@@ -0,0 +1,126 @@
+<!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: [
+ { TEXT_LEAF: [] },
+ ] },
+ { COMBOBOX_OPTION: [
+ { TEXT_LEAF: [] },
+ ] },
+ ]},
+ { 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..92cf892569
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_recreation.html
@@ -0,0 +1,91 @@
+<!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 = "Set a listbox as multiselectable";
+ events = waitForOrderedEvents(
+ [[EVENT_HIDE, "div3"], [EVENT_SHOW, "div3"], [EVENT_REORDER, "container"]], msg);
+ document.getElementById("div3").setAttribute("aria-multiselectable", "true");
+ 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;
+
+ 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"/>
+ </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..c4c8c8b8f6
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_select.html
@@ -0,0 +1,136 @@
+<!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: [
+ { TEXT_LEAF: [] },
+ ] },
+ { COMBOBOX_OPTION: [
+ { TEXT_LEAF: [] },
+ ] },
+ ] },
+ ] };
+ 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";
+ };
+ }
+
+ function doTest() {
+ const gQueue = new eventQueue();
+
+ gQueue.push(new addOptions("select"));
+ gQueue.push(new removeOptions("select"));
+ gQueue.push(new setRoleOnOption());
+
+ 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>
+</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..1a42fc4d52
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_shadow_slots.html
@@ -0,0 +1,467 @@
+<!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"]);
+ }
+
+ // 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"} ] }] });
+
+ }
+
+ async function doTest() {
+ await attachFlatShadow();
+
+ await attachOneDeepShadow();
+
+ await changeSlotFlat();
+
+ await changeSlotOneDeep();
+
+ await changeSlotNested();
+
+ 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();
+
+ 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="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="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..816cfec77c
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_tooltip.xhtml
@@ -0,0 +1,71 @@
+<?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: [] }] });
+
+ 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..f93e318b64
--- /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-image: url("chrome://global/skin/console/bullet-question.png");
+}
+
+treechildren::-moz-tree-image(cyclerState2) {
+ list-style-image: url("chrome://global/skin/console/bullet-warning.png");
+}
+
+treechildren::-moz-tree-image(cyclerState3) {
+ list-style-image: url("chrome://global/skin/console/bullet-error.png");
+}
diff --git a/accessible/tests/mochitest/treeview.js b/accessible/tests/mochitest/treeview.js
new file mode 100644
index 0000000000..df2468a37a
--- /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..8ebcf83f0c
--- /dev/null
+++ b/accessible/tests/mochitest/value/a11y.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_ariavalue.html]
+[test_general.html]
+[test_number.html]
+[test_progress.html]
+[test_range.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..fa240e082e
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_ariavalue.html
@@ -0,0 +1,68 @@
+<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);
+ }
+
+ 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>
+</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_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>