summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/tests/browser
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/urlbar/tests/browser')
-rw-r--r--browser/components/urlbar/tests/browser/POSTSearchEngine.xml6
-rw-r--r--browser/components/urlbar/tests/browser/add_search_engine_0.xml7
-rw-r--r--browser/components/urlbar/tests/browser/add_search_engine_1.xml7
-rw-r--r--browser/components/urlbar/tests/browser/add_search_engine_2.xml7
-rw-r--r--browser/components/urlbar/tests/browser/add_search_engine_3.xml7
-rw-r--r--browser/components/urlbar/tests/browser/add_search_engine_invalid.html11
-rw-r--r--browser/components/urlbar/tests/browser/add_search_engine_many.html24
-rw-r--r--browser/components/urlbar/tests/browser/add_search_engine_one.html12
-rw-r--r--browser/components/urlbar/tests/browser/add_search_engine_same_names.html15
-rw-r--r--browser/components/urlbar/tests/browser/add_search_engine_two.html16
-rw-r--r--browser/components/urlbar/tests/browser/authenticate.sjs218
-rw-r--r--browser/components/urlbar/tests/browser/browser.ini434
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_formatValue.js178
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_formatValue_detachedTab.js76
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_hiddenFocus.js21
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow.js156
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow_resize.js58
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_privateFeature.js74
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms.js306
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_backgroundTabs.js63
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_modifiedUrl.js100
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_moveTab.js133
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_popup.js145
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_revert.js170
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_searchBar.js104
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_searchMode.js81
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_switch_tab.js139
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_telemetry.js378
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_setURI.js128
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_tooltip.js83
-rw-r--r--browser/components/urlbar/tests/browser/browser_UrlbarInput_trimURLs.js127
-rw-r--r--browser/components/urlbar/tests/browser/browser_aboutHomeLoading.js196
-rw-r--r--browser/components/urlbar/tests/browser/browser_acknowledgeFeedbackAndDismissal.js304
-rw-r--r--browser/components/urlbar/tests/browser/browser_action_searchengine.js125
-rw-r--r--browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js63
-rw-r--r--browser/components/urlbar/tests/browser/browser_add_search_engine.js325
-rw-r--r--browser/components/urlbar/tests/browser/browser_autoFill_backspaced.js268
-rw-r--r--browser/components/urlbar/tests/browser/browser_autoFill_canonize.js62
-rw-r--r--browser/components/urlbar/tests/browser/browser_autoFill_caretNotAtEnd.js34
-rw-r--r--browser/components/urlbar/tests/browser/browser_autoFill_firstResult.js200
-rw-r--r--browser/components/urlbar/tests/browser/browser_autoFill_paste.js38
-rw-r--r--browser/components/urlbar/tests/browser/browser_autoFill_placeholder.js1017
-rw-r--r--browser/components/urlbar/tests/browser/browser_autoFill_preserve.js257
-rw-r--r--browser/components/urlbar/tests/browser/browser_autoFill_trimURLs.js183
-rw-r--r--browser/components/urlbar/tests/browser/browser_autoFill_typed.js172
-rw-r--r--browser/components/urlbar/tests/browser/browser_autoFill_undo.js50
-rw-r--r--browser/components/urlbar/tests/browser/browser_autoOpen.js93
-rw-r--r--browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js185
-rw-r--r--browser/components/urlbar/tests/browser/browser_autocomplete_autoselect.js122
-rw-r--r--browser/components/urlbar/tests/browser/browser_autocomplete_cursor.js37
-rw-r--r--browser/components/urlbar/tests/browser/browser_autocomplete_edit_completed.js75
-rw-r--r--browser/components/urlbar/tests/browser/browser_autocomplete_enter_race.js193
-rw-r--r--browser/components/urlbar/tests/browser/browser_autocomplete_no_title.js34
-rw-r--r--browser/components/urlbar/tests/browser/browser_autocomplete_readline_navigation.js71
-rw-r--r--browser/components/urlbar/tests/browser/browser_autocomplete_tag_star_visibility.js167
-rw-r--r--browser/components/urlbar/tests/browser/browser_bestMatch.js229
-rw-r--r--browser/components/urlbar/tests/browser/browser_blanking.js54
-rw-r--r--browser/components/urlbar/tests/browser/browser_bufferer_onQueryResults.js82
-rw-r--r--browser/components/urlbar/tests/browser/browser_calculator.js33
-rw-r--r--browser/components/urlbar/tests/browser/browser_canonizeURL.js277
-rw-r--r--browser/components/urlbar/tests/browser/browser_caret_position.js359
-rw-r--r--browser/components/urlbar/tests/browser/browser_click_row_border.js36
-rw-r--r--browser/components/urlbar/tests/browser/browser_closePanelOnClick.js34
-rw-r--r--browser/components/urlbar/tests/browser/browser_content_opener.js23
-rw-r--r--browser/components/urlbar/tests/browser/browser_contextualsearch.js119
-rw-r--r--browser/components/urlbar/tests/browser/browser_copy_during_load.js51
-rw-r--r--browser/components/urlbar/tests/browser/browser_copying.js416
-rw-r--r--browser/components/urlbar/tests/browser/browser_customizeMode.js73
-rw-r--r--browser/components/urlbar/tests/browser/browser_cutting.js17
-rw-r--r--browser/components/urlbar/tests/browser/browser_decode.js144
-rw-r--r--browser/components/urlbar/tests/browser/browser_delete.js51
-rw-r--r--browser/components/urlbar/tests/browser/browser_deleteAllText.js100
-rw-r--r--browser/components/urlbar/tests/browser/browser_display_selectedAction_Extensions.js57
-rw-r--r--browser/components/urlbar/tests/browser/browser_dns_first_for_single_words.js52
-rw-r--r--browser/components/urlbar/tests/browser/browser_downArrowKeySearch.js83
-rw-r--r--browser/components/urlbar/tests/browser/browser_dragdropURL.js106
-rw-r--r--browser/components/urlbar/tests/browser/browser_dynamicResults.js799
-rw-r--r--browser/components/urlbar/tests/browser/browser_edit_invalid_url.js91
-rw-r--r--browser/components/urlbar/tests/browser/browser_engagement.js206
-rw-r--r--browser/components/urlbar/tests/browser/browser_enter.js331
-rw-r--r--browser/components/urlbar/tests/browser/browser_enterAfterMouseOver.js97
-rw-r--r--browser/components/urlbar/tests/browser/browser_focusedCmdK.js15
-rw-r--r--browser/components/urlbar/tests/browser/browser_groupLabels.js629
-rw-r--r--browser/components/urlbar/tests/browser/browser_handleCommand_fallback.js142
-rw-r--r--browser/components/urlbar/tests/browser/browser_hashChangeProxyState.js151
-rw-r--r--browser/components/urlbar/tests/browser/browser_helpUrl.js428
-rw-r--r--browser/components/urlbar/tests/browser/browser_heuristicNotAddedFirst.js159
-rw-r--r--browser/components/urlbar/tests/browser/browser_hideHeuristic.js513
-rw-r--r--browser/components/urlbar/tests/browser/browser_ime_composition.js327
-rw-r--r--browser/components/urlbar/tests/browser/browser_inputHistory.js548
-rw-r--r--browser/components/urlbar/tests/browser/browser_inputHistory_autofill.js207
-rw-r--r--browser/components/urlbar/tests/browser/browser_inputHistory_emptystring.js94
-rw-r--r--browser/components/urlbar/tests/browser/browser_keepStateAcrossTabSwitches.js224
-rw-r--r--browser/components/urlbar/tests/browser/browser_keyword.js238
-rw-r--r--browser/components/urlbar/tests/browser/browser_keywordBookmarklets.js133
-rw-r--r--browser/components/urlbar/tests/browser/browser_keywordSearch.js57
-rw-r--r--browser/components/urlbar/tests/browser/browser_keywordSearch_postData.js74
-rw-r--r--browser/components/urlbar/tests/browser/browser_keyword_override.js61
-rw-r--r--browser/components/urlbar/tests/browser/browser_keyword_select_and_type.js97
-rw-r--r--browser/components/urlbar/tests/browser/browser_loadRace.js90
-rw-r--r--browser/components/urlbar/tests/browser/browser_locationBarCommand.js291
-rw-r--r--browser/components/urlbar/tests/browser/browser_locationBarExternalLoad.js94
-rw-r--r--browser/components/urlbar/tests/browser/browser_locationchange_urlbar_edit_dos.js67
-rw-r--r--browser/components/urlbar/tests/browser/browser_middleClick.js255
-rw-r--r--browser/components/urlbar/tests/browser/browser_new_tab_urlbar_reset.js39
-rw-r--r--browser/components/urlbar/tests/browser/browser_oneOffs.js980
-rw-r--r--browser/components/urlbar/tests/browser/browser_oneOffs_contextMenu.js80
-rw-r--r--browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js516
-rw-r--r--browser/components/urlbar/tests/browser/browser_oneOffs_keyModifiers.js392
-rw-r--r--browser/components/urlbar/tests/browser/browser_oneOffs_searchSuggestions.js358
-rw-r--r--browser/components/urlbar/tests/browser/browser_oneOffs_settings.js89
-rw-r--r--browser/components/urlbar/tests/browser/browser_pasteAndGo.js80
-rw-r--r--browser/components/urlbar/tests/browser/browser_paste_multi_lines.js239
-rw-r--r--browser/components/urlbar/tests/browser/browser_paste_then_focus.js60
-rw-r--r--browser/components/urlbar/tests/browser/browser_paste_then_switch_tab.js73
-rw-r--r--browser/components/urlbar/tests/browser/browser_percent_encoded.js59
-rw-r--r--browser/components/urlbar/tests/browser/browser_placeholder.js412
-rw-r--r--browser/components/urlbar/tests/browser/browser_populateAfterPushState.js32
-rw-r--r--browser/components/urlbar/tests/browser/browser_primary_selection_safe_on_new_tab.js73
-rw-r--r--browser/components/urlbar/tests/browser/browser_privateBrowsingWindowChange.js51
-rw-r--r--browser/components/urlbar/tests/browser/browser_queryContextCache.js482
-rw-r--r--browser/components/urlbar/tests/browser/browser_quickactions.js783
-rw-r--r--browser/components/urlbar/tests/browser/browser_quickactions_devtools.js176
-rw-r--r--browser/components/urlbar/tests/browser/browser_quickactions_tab_refocus.js194
-rw-r--r--browser/components/urlbar/tests/browser/browser_raceWithTabs.js86
-rw-r--r--browser/components/urlbar/tests/browser/browser_redirect_error.js137
-rw-r--r--browser/components/urlbar/tests/browser/browser_remoteness_switch.js56
-rw-r--r--browser/components/urlbar/tests/browser/browser_remotetab.js111
-rw-r--r--browser/components/urlbar/tests/browser/browser_removeUnsafeProtocolsFromURLBarPaste.js95
-rw-r--r--browser/components/urlbar/tests/browser/browser_remove_match.js297
-rw-r--r--browser/components/urlbar/tests/browser/browser_restoreEmptyInput.js64
-rw-r--r--browser/components/urlbar/tests/browser/browser_resultSpan.js254
-rw-r--r--browser/components/urlbar/tests/browser/browser_result_menu.js266
-rw-r--r--browser/components/urlbar/tests/browser/browser_result_onSelection.js67
-rw-r--r--browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js435
-rw-r--r--browser/components/urlbar/tests/browser/browser_revert.js33
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchFunction.js278
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchHistoryLimit.js87
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_alias_replacement.js274
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_autofill.js133
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_clickLink.js94
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_engineRemoval.js109
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_excludeResults.js217
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_heuristic.js221
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_indicator.js377
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_indicator_clickthrough.js100
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_localOneOffs_actionText.js459
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_newWindow.js40
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_no_results.js290
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_oneOffButton.js108
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_pickResult.js89
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_preview.js489
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_sessionStore.js332
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_setURI.js119
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_suggestions.js579
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchMode_switchTabs.js317
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchSettings.js30
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchSingleWordNotification.js372
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchSuggestions.js341
-rw-r--r--browser/components/urlbar/tests/browser/browser_searchTelemetry.js220
-rw-r--r--browser/components/urlbar/tests/browser/browser_search_bookmarks_from_bookmarks_menu.js55
-rw-r--r--browser/components/urlbar/tests/browser/browser_search_history_from_history_panel.js97
-rw-r--r--browser/components/urlbar/tests/browser/browser_selectStaleResults.js311
-rw-r--r--browser/components/urlbar/tests/browser/browser_selectionKeyNavigation.js200
-rw-r--r--browser/components/urlbar/tests/browser/browser_separatePrivateDefault.js223
-rw-r--r--browser/components/urlbar/tests/browser/browser_separatePrivateDefault_differentEngine.js354
-rw-r--r--browser/components/urlbar/tests/browser/browser_shortcuts_add_search_engine.js243
-rw-r--r--browser/components/urlbar/tests/browser/browser_speculative_connect.js199
-rw-r--r--browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js236
-rw-r--r--browser/components/urlbar/tests/browser/browser_stop.js75
-rw-r--r--browser/components/urlbar/tests/browser/browser_stopSearchOnSelection.js113
-rw-r--r--browser/components/urlbar/tests/browser/browser_stop_pending.js459
-rw-r--r--browser/components/urlbar/tests/browser/browser_strip_on_share.js125
-rw-r--r--browser/components/urlbar/tests/browser/browser_suggestedIndex.js120
-rw-r--r--browser/components/urlbar/tests/browser/browser_suppressFocusBorder.js391
-rw-r--r--browser/components/urlbar/tests/browser/browser_switchTab_closesUrlbarPopup.js42
-rw-r--r--browser/components/urlbar/tests/browser/browser_switchTab_currentTab.js41
-rw-r--r--browser/components/urlbar/tests/browser/browser_switchTab_decodeuri.js51
-rw-r--r--browser/components/urlbar/tests/browser/browser_switchTab_inputHistory.js91
-rw-r--r--browser/components/urlbar/tests/browser/browser_switchTab_override.js100
-rw-r--r--browser/components/urlbar/tests/browser/browser_switchToTabHavingURI_aOpenParams.js217
-rw-r--r--browser/components/urlbar/tests/browser/browser_switchToTab_chiclet.js110
-rw-r--r--browser/components/urlbar/tests/browser/browser_switchToTab_closes_newtab.js63
-rw-r--r--browser/components/urlbar/tests/browser/browser_switchToTab_fullUrl_repeatedKeydown.js60
-rw-r--r--browser/components/urlbar/tests/browser/browser_tabKeyBehavior.js378
-rw-r--r--browser/components/urlbar/tests/browser/browser_tabMatchesInAwesomebar.js224
-rw-r--r--browser/components/urlbar/tests/browser/browser_tabMatchesInAwesomebar_perwindowpb.js120
-rw-r--r--browser/components/urlbar/tests/browser/browser_tabToSearch.js641
-rw-r--r--browser/components/urlbar/tests/browser/browser_textruns.js55
-rw-r--r--browser/components/urlbar/tests/browser/browser_tokenAlias.js861
-rw-r--r--browser/components/urlbar/tests/browser/browser_top_sites.js481
-rw-r--r--browser/components/urlbar/tests/browser/browser_top_sites_private.js174
-rw-r--r--browser/components/urlbar/tests/browser/browser_typed_value.js69
-rw-r--r--browser/components/urlbar/tests/browser/browser_unitConversion.js88
-rw-r--r--browser/components/urlbar/tests/browser/browser_updateForDomainCompletion.js51
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_annotation.js333
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry_abandonment.js357
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry_engagement.js1340
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry_noEvent.js81
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_selection.js307
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry.js1218
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_autofill.js733
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_dynamic.js136
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_extension.js155
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_handoff.js182
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_persisted.js270
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_places.js270
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_quickactions.js133
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_remotetab.js185
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_searchmode.js592
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_sponsored_topsites.js181
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tabtosearch.js416
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tip.js130
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_topsite.js136
-rw-r--r--browser/components/urlbar/tests/browser/browser_urlbar_telemetry_zeroPrefix.js266
-rw-r--r--browser/components/urlbar/tests/browser/browser_userTypedValue.js46
-rw-r--r--browser/components/urlbar/tests/browser/browser_valueOnTabSwitch.js166
-rw-r--r--browser/components/urlbar/tests/browser/browser_view_emptyResultSet.js40
-rw-r--r--browser/components/urlbar/tests/browser/browser_view_resultDisplay.js354
-rw-r--r--browser/components/urlbar/tests/browser/browser_view_resultTypes_display.js317
-rw-r--r--browser/components/urlbar/tests/browser/browser_view_selectionByMouse.js607
-rw-r--r--browser/components/urlbar/tests/browser/browser_waitForLoadOrTimeout.js37
-rw-r--r--browser/components/urlbar/tests/browser/browser_whereToOpen.js192
-rw-r--r--browser/components/urlbar/tests/browser/dummy_page.html9
-rw-r--r--browser/components/urlbar/tests/browser/dynamicResult0.css50
-rw-r--r--browser/components/urlbar/tests/browser/dynamicResult1.css50
-rw-r--r--browser/components/urlbar/tests/browser/file_blank_but_not_blank.html2
-rw-r--r--browser/components/urlbar/tests/browser/file_urlbar_edit_dos.html18
-rw-r--r--browser/components/urlbar/tests/browser/file_userTypedValue.html1
-rw-r--r--browser/components/urlbar/tests/browser/head-common.js156
-rw-r--r--browser/components/urlbar/tests/browser/head.js125
-rw-r--r--browser/components/urlbar/tests/browser/moz.pngbin0 -> 580 bytes
-rw-r--r--browser/components/urlbar/tests/browser/print_postdata.sjs25
-rw-r--r--browser/components/urlbar/tests/browser/redirect_error.sjs16
-rw-r--r--browser/components/urlbar/tests/browser/redirect_to.sjs9
-rw-r--r--browser/components/urlbar/tests/browser/searchSuggestionEngine.sjs57
-rw-r--r--browser/components/urlbar/tests/browser/searchSuggestionEngine.xml11
-rw-r--r--browser/components/urlbar/tests/browser/searchSuggestionEngine2.xml13
-rw-r--r--browser/components/urlbar/tests/browser/searchSuggestionEngineMany.xml11
-rw-r--r--browser/components/urlbar/tests/browser/searchSuggestionEngineSlow.xml11
-rw-r--r--browser/components/urlbar/tests/browser/slow-page.sjs23
-rw-r--r--browser/components/urlbar/tests/browser/urlbarTelemetrySearchSuggestions.sjs9
-rw-r--r--browser/components/urlbar/tests/browser/urlbarTelemetrySearchSuggestions.xml6
-rw-r--r--browser/components/urlbar/tests/browser/urlbarTelemetryUrlbarDynamic.css45
244 files changed, 46058 insertions, 0 deletions
diff --git a/browser/components/urlbar/tests/browser/POSTSearchEngine.xml b/browser/components/urlbar/tests/browser/POSTSearchEngine.xml
new file mode 100644
index 0000000000..8b387ea9ae
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/POSTSearchEngine.xml
@@ -0,0 +1,6 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>POST Search</ShortName>
+ <Url type="text/html" method="POST" template="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/print_postdata.sjs">
+ <Param name="searchterms" value="{searchTerms}"/>
+ </Url>
+</OpenSearchDescription>
diff --git a/browser/components/urlbar/tests/browser/add_search_engine_0.xml b/browser/components/urlbar/tests/browser/add_search_engine_0.xml
new file mode 100644
index 0000000000..a24348deb7
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/add_search_engine_0.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>add_search_engine_0</ShortName>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform">
+ <Param name="terms" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/components/urlbar/tests/browser/add_search_engine_1.xml b/browser/components/urlbar/tests/browser/add_search_engine_1.xml
new file mode 100644
index 0000000000..61092247a9
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/add_search_engine_1.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>add_search_engine_1</ShortName>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform">
+ <Param name="terms" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/components/urlbar/tests/browser/add_search_engine_2.xml b/browser/components/urlbar/tests/browser/add_search_engine_2.xml
new file mode 100644
index 0000000000..3f5c2f0037
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/add_search_engine_2.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>add_search_engine_2</ShortName>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform">
+ <Param name="terms" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/components/urlbar/tests/browser/add_search_engine_3.xml b/browser/components/urlbar/tests/browser/add_search_engine_3.xml
new file mode 100644
index 0000000000..bacfffa3e2
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/add_search_engine_3.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>add_search_engine_3</ShortName>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform">
+ <Param name="terms" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/components/urlbar/tests/browser/add_search_engine_invalid.html b/browser/components/urlbar/tests/browser/add_search_engine_invalid.html
new file mode 100644
index 0000000000..ea5baa93d5
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/add_search_engine_invalid.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<link rel="search"
+ type="application/opensearchdescription+xml"
+ title="add_search_engine_404"
+ href="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/add_search_engine_404.xml">
+</head>
+<body></body>
+</html>
diff --git a/browser/components/urlbar/tests/browser/add_search_engine_many.html b/browser/components/urlbar/tests/browser/add_search_engine_many.html
new file mode 100644
index 0000000000..d75bccc890
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/add_search_engine_many.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="/>
+<link rel="search"
+ type="application/opensearchdescription+xml"
+ title="add_search_engine_0"
+ href="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/add_search_engine_0.xml">
+<link rel="search"
+ type="application/opensearchdescription+xml"
+ title="add_search_engine_1"
+ href="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/add_search_engine_1.xml">
+<link rel="search"
+ type="application/opensearchdescription+xml"
+ title="add_search_engine_2"
+ href="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/add_search_engine_2.xml">
+<link rel="search"
+ type="application/opensearchdescription+xml"
+ title="add_search_engine_3"
+ href="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/add_search_engine_3.xml">
+</head>
+<body></body>
+</html>
diff --git a/browser/components/urlbar/tests/browser/add_search_engine_one.html b/browser/components/urlbar/tests/browser/add_search_engine_one.html
new file mode 100644
index 0000000000..c11409604f
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/add_search_engine_one.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="/>
+<link rel="search"
+ type="application/opensearchdescription+xml"
+ title="add_search_engine_0"
+ href="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/add_search_engine_0.xml">
+</head>
+<body></body>
+</html>
diff --git a/browser/components/urlbar/tests/browser/add_search_engine_same_names.html b/browser/components/urlbar/tests/browser/add_search_engine_same_names.html
new file mode 100644
index 0000000000..7112905a75
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/add_search_engine_same_names.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<link rel="search"
+ type="application/opensearchdescription+xml"
+ title="add_search_engine_0"
+ href="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/add_search_engine_0.xml">
+<link rel="search"
+ type="application/opensearchdescription+xml"
+ title="add_search_engine_0"
+ href="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/add_search_engine_0.xml">
+</head>
+<body></body>
+</html>
diff --git a/browser/components/urlbar/tests/browser/add_search_engine_two.html b/browser/components/urlbar/tests/browser/add_search_engine_two.html
new file mode 100644
index 0000000000..c1d33860a4
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/add_search_engine_two.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="/>
+<link rel="search"
+ type="application/opensearchdescription+xml"
+ title="add_search_engine_0"
+ href="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/add_search_engine_0.xml">
+ <link rel="search"
+ type="application/opensearchdescription+xml"
+ title="add_search_engine_1"
+ href="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/add_search_engine_2.xml">
+</head>
+<body></body>
+</html>
diff --git a/browser/components/urlbar/tests/browser/authenticate.sjs b/browser/components/urlbar/tests/browser/authenticate.sjs
new file mode 100644
index 0000000000..8218a46eb6
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/authenticate.sjs
@@ -0,0 +1,218 @@
+"use strict";
+
+function handleRequest(request, response) {
+ try {
+ reallyHandleRequest(request, response);
+ } catch (e) {
+ response.setStatusLine("1.0", 200, "AlmostOK");
+ response.write("Error handling request: " + e);
+ }
+}
+
+function reallyHandleRequest(request, response) {
+ let match;
+ let requestAuth = true,
+ requestProxyAuth = true;
+
+ // Allow the caller to drive how authentication is processed via the query.
+ // Eg, http://localhost:8888/authenticate.sjs?user=foo&realm=bar
+ // The extra ? allows the user/pass/realm checks to succeed if the name is
+ // at the beginning of the query string.
+ let query = "?" + request.queryString;
+
+ let expected_user = "",
+ expected_pass = "",
+ realm = "mochitest";
+ let proxy_expected_user = "",
+ proxy_expected_pass = "",
+ proxy_realm = "mochi-proxy";
+ let huge = false,
+ plugin = false,
+ anonymous = false;
+ let authHeaderCount = 1;
+ // user=xxx
+ match = /[^_]user=([^&]*)/.exec(query);
+ if (match) {
+ expected_user = match[1];
+ }
+
+ // pass=xxx
+ match = /[^_]pass=([^&]*)/.exec(query);
+ if (match) {
+ expected_pass = match[1];
+ }
+
+ // realm=xxx
+ match = /[^_]realm=([^&]*)/.exec(query);
+ if (match) {
+ realm = match[1];
+ }
+
+ // proxy_user=xxx
+ match = /proxy_user=([^&]*)/.exec(query);
+ if (match) {
+ proxy_expected_user = match[1];
+ }
+
+ // proxy_pass=xxx
+ match = /proxy_pass=([^&]*)/.exec(query);
+ if (match) {
+ proxy_expected_pass = match[1];
+ }
+
+ // proxy_realm=xxx
+ match = /proxy_realm=([^&]*)/.exec(query);
+ if (match) {
+ proxy_realm = match[1];
+ }
+
+ // huge=1
+ match = /huge=1/.exec(query);
+ if (match) {
+ huge = true;
+ }
+
+ // plugin=1
+ match = /plugin=1/.exec(query);
+ if (match) {
+ plugin = true;
+ }
+
+ // multiple=1
+ match = /multiple=([^&]*)/.exec(query);
+ if (match) {
+ authHeaderCount = match[1] + 0;
+ }
+
+ // anonymous=1
+ match = /anonymous=1/.exec(query);
+ if (match) {
+ anonymous = true;
+ }
+
+ // Look for an authentication header, if any, in the request.
+ //
+ // EG: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
+ //
+ // This test only supports Basic auth. The value sent by the client is
+ // "username:password", obscured with base64 encoding.
+
+ let actual_user = "",
+ actual_pass = "",
+ authHeader,
+ authPresent = false;
+ if (request.hasHeader("Authorization")) {
+ authPresent = true;
+ authHeader = request.getHeader("Authorization");
+ match = /Basic (.+)/.exec(authHeader);
+ if (match.length != 2) {
+ throw Error("Couldn't parse auth header: " + authHeader);
+ }
+
+ let userpass = atob(match[1]);
+ match = /(.*):(.*)/.exec(userpass);
+ if (match.length != 3) {
+ throw Error("Couldn't decode auth header: " + userpass);
+ }
+ actual_user = match[1];
+ actual_pass = match[2];
+ }
+
+ let proxy_actual_user = "",
+ proxy_actual_pass = "";
+ if (request.hasHeader("Proxy-Authorization")) {
+ authHeader = request.getHeader("Proxy-Authorization");
+ match = /Basic (.+)/.exec(authHeader);
+ if (match.length != 2) {
+ throw Error("Couldn't parse auth header: " + authHeader);
+ }
+
+ let userpass = atob(match[1]);
+ match = /(.*):(.*)/.exec(userpass);
+ if (match.length != 3) {
+ throw Error("Couldn't decode auth header: " + userpass);
+ }
+ proxy_actual_user = match[1];
+ proxy_actual_pass = match[2];
+ }
+
+ // Don't request authentication if the credentials we got were what we
+ // expected.
+ if (expected_user == actual_user && expected_pass == actual_pass) {
+ requestAuth = false;
+ }
+ if (
+ proxy_expected_user == proxy_actual_user &&
+ proxy_expected_pass == proxy_actual_pass
+ ) {
+ requestProxyAuth = false;
+ }
+
+ if (anonymous) {
+ if (authPresent) {
+ response.setStatusLine(
+ "1.0",
+ 400,
+ "Unexpected authorization header found"
+ );
+ } else {
+ response.setStatusLine("1.0", 200, "Authorization header not found");
+ }
+ } else if (requestProxyAuth) {
+ response.setStatusLine("1.0", 407, "Proxy authentication required");
+ for (let i = 0; i < authHeaderCount; ++i) {
+ response.setHeader(
+ "Proxy-Authenticate",
+ 'basic realm="' + proxy_realm + '"',
+ true
+ );
+ }
+ } else if (requestAuth) {
+ response.setStatusLine("1.0", 401, "Authentication required");
+ for (let i = 0; i < authHeaderCount; ++i) {
+ response.setHeader(
+ "WWW-Authenticate",
+ 'basic realm="' + realm + '"',
+ true
+ );
+ }
+ } else {
+ response.setStatusLine("1.0", 200, "OK");
+ }
+
+ response.setHeader("Content-Type", "application/xhtml+xml", false);
+ response.write("<html xmlns='http://www.w3.org/1999/xhtml'>");
+ response.write(
+ "<p>Login: <span id='ok'>" +
+ (requestAuth ? "FAIL" : "PASS") +
+ "</span></p>\n"
+ );
+ response.write(
+ "<p>Proxy: <span id='proxy'>" +
+ (requestProxyAuth ? "FAIL" : "PASS") +
+ "</span></p>\n"
+ );
+ response.write("<p>Auth: <span id='auth'>" + authHeader + "</span></p>\n");
+ response.write("<p>User: <span id='user'>" + actual_user + "</span></p>\n");
+ response.write("<p>Pass: <span id='pass'>" + actual_pass + "</span></p>\n");
+
+ if (huge) {
+ response.write("<div style='display: none'>");
+ for (let i = 0; i < 100000; i++) {
+ response.write("123456789\n");
+ }
+ response.write("</div>");
+ response.write(
+ "<span id='footnote'>This is a footnote after the huge content fill</span>"
+ );
+ }
+
+ if (plugin) {
+ response.write(
+ "<embed id='embedtest' style='width: 400px; height: 100px;' " +
+ "type='application/x-test'></embed>\n"
+ );
+ }
+
+ response.write("</html>");
+}
diff --git a/browser/components/urlbar/tests/browser/browser.ini b/browser/components/urlbar/tests/browser/browser.ini
new file mode 100644
index 0000000000..64290236d3
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser.ini
@@ -0,0 +1,434 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+support-files =
+ dummy_page.html
+ head.js
+ head-common.js
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+
+prefs =
+ browser.urlbar.trending.featureGate=false
+ extensions.screenshots.disabled=false
+ screenshots.browser.component.enabled=true
+
+[browser_UrlbarInput_formatValue.js]
+[browser_UrlbarInput_formatValue_detachedTab.js]
+skip-if =
+ apple_catalina # Bug 1756585
+ os == 'win' # Bug 1756585
+[browser_UrlbarInput_hiddenFocus.js]
+[browser_UrlbarInput_overflow.js]
+[browser_UrlbarInput_overflow_resize.js]
+[browser_UrlbarInput_privateFeature.js]
+[browser_UrlbarInput_searchTerms.js]
+[browser_UrlbarInput_searchTerms_backgroundTabs.js]
+[browser_UrlbarInput_searchTerms_modifiedUrl.js]
+[browser_UrlbarInput_searchTerms_moveTab.js]
+[browser_UrlbarInput_searchTerms_popup.js]
+[browser_UrlbarInput_searchTerms_revert.js]
+[browser_UrlbarInput_searchTerms_searchBar.js]
+[browser_UrlbarInput_searchTerms_searchMode.js]
+[browser_UrlbarInput_searchTerms_switch_tab.js]
+[browser_UrlbarInput_searchTerms_telemetry.js]
+[browser_UrlbarInput_setURI.js]
+https_first_disabled = true
+skip-if =
+ apple_catalina && debug # Bug 1773790
+[browser_UrlbarInput_tooltip.js]
+[browser_UrlbarInput_trimURLs.js]
+https_first_disabled = true
+[browser_aboutHomeLoading.js]
+skip-if =
+ tsan # Intermittently times out, see 1622698 (frequent on TSan).
+ os == 'linux' && bits == 64 && !debug # Bug 1622698
+[browser_acknowledgeFeedbackAndDismissal.js]
+[browser_action_searchengine.js]
+[browser_action_searchengine_alias.js]
+[browser_add_search_engine.js]
+support-files =
+ add_search_engine_0.xml
+ add_search_engine_1.xml
+ add_search_engine_2.xml
+ add_search_engine_3.xml
+ add_search_engine_invalid.html
+ add_search_engine_one.html
+ add_search_engine_many.html
+ add_search_engine_same_names.html
+ add_search_engine_two.html
+[browser_autoFill_backspaced.js]
+[browser_autoFill_canonize.js]
+https_first_disabled = true
+[browser_autoFill_caretNotAtEnd.js]
+[browser_autoFill_firstResult.js]
+[browser_autoFill_paste.js]
+[browser_autoFill_placeholder.js]
+[browser_autoFill_preserve.js]
+[browser_autoFill_trimURLs.js]
+[browser_autoFill_typed.js]
+[browser_autoFill_undo.js]
+[browser_autoOpen.js]
+[browser_autocomplete_a11y_label.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_autocomplete_autoselect.js]
+[browser_autocomplete_cursor.js]
+[browser_autocomplete_edit_completed.js]
+[browser_autocomplete_enter_race.js]
+https_first_disabled = true
+[browser_autocomplete_no_title.js]
+[browser_autocomplete_readline_navigation.js]
+skip-if = os != "mac" # Mac only feature
+[browser_autocomplete_tag_star_visibility.js]
+[browser_bestMatch.js]
+[browser_blanking.js]
+support-files =
+ file_blank_but_not_blank.html
+[browser_bufferer_onQueryResults.js]
+[browser_calculator.js]
+[browser_canonizeURL.js]
+https_first_disabled = true
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_caret_position.js]
+[browser_click_row_border.js]
+[browser_closePanelOnClick.js]
+[browser_content_opener.js]
+[browser_contextualsearch.js]
+[browser_copy_during_load.js]
+support-files =
+ slow-page.sjs
+[browser_copying.js]
+https_first_disabled = true
+support-files =
+ authenticate.sjs
+[browser_customizeMode.js]
+[browser_cutting.js]
+[browser_decode.js]
+[browser_delete.js]
+[browser_deleteAllText.js]
+[browser_display_selectedAction_Extensions.js]
+[browser_dns_first_for_single_words.js]
+skip-if = verify && os == 'linux' # Bug 1581635
+[browser_downArrowKeySearch.js]
+https_first_disabled = true
+[browser_dragdropURL.js]
+[browser_dynamicResults.js]
+https_first_disabled = true
+support-files =
+ dynamicResult0.css
+ dynamicResult1.css
+[browser_edit_invalid_url.js]
+[browser_engagement.js]
+[browser_enter.js]
+[browser_enterAfterMouseOver.js]
+[browser_focusedCmdK.js]
+[browser_groupLabels.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_handleCommand_fallback.js]
+[browser_hashChangeProxyState.js]
+[browser_helpUrl.js]
+[browser_heuristicNotAddedFirst.js]
+[browser_hideHeuristic.js]
+[browser_ime_composition.js]
+[browser_inputHistory.js]
+https_first_disabled = true
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_inputHistory_autofill.js]
+[browser_inputHistory_emptystring.js]
+[browser_keepStateAcrossTabSwitches.js]
+https_first_disabled = true
+[browser_keyword.js]
+support-files =
+ print_postdata.sjs
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_keywordBookmarklets.js]
+[browser_keywordSearch.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_keywordSearch_postData.js]
+support-files =
+ POSTSearchEngine.xml
+ print_postdata.sjs
+[browser_keyword_override.js]
+[browser_keyword_select_and_type.js]
+[browser_loadRace.js]
+[browser_locationBarCommand.js]
+https_first_disabled = true
+skip-if =
+ os == 'linux' && bits == 64 && !debug # Bug 1787020
+[browser_locationBarExternalLoad.js]
+[browser_locationchange_urlbar_edit_dos.js]
+support-files =
+ file_urlbar_edit_dos.html
+[browser_middleClick.js]
+[browser_new_tab_urlbar_reset.js]
+[browser_oneOffs.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_oneOffs_contextMenu.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_oneOffs_heuristicRestyle.js]
+skip-if =
+ os == "linux" && bits == 64 && !debug # Bug 1775811
+[browser_oneOffs_keyModifiers.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_oneOffs_searchSuggestions.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+ searchSuggestionEngine2.xml
+[browser_oneOffs_settings.js]
+[browser_pasteAndGo.js]
+https_first_disabled = true
+[browser_paste_multi_lines.js]
+[browser_paste_then_focus.js]
+[browser_paste_then_switch_tab.js]
+[browser_percent_encoded.js]
+[browser_placeholder.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine2.xml
+ searchSuggestionEngine.sjs
+[browser_populateAfterPushState.js]
+[browser_primary_selection_safe_on_new_tab.js]
+[browser_privateBrowsingWindowChange.js]
+[browser_queryContextCache.js]
+[browser_quickactions.js]
+skip-if =
+ os == "linux" # Bug 1806090
+[browser_quickactions_devtools.js]
+[browser_quickactions_tab_refocus.js]
+[browser_raceWithTabs.js]
+[browser_redirect_error.js]
+support-files = redirect_error.sjs
+[browser_remoteness_switch.js]
+https_first_disabled = true
+[browser_remotetab.js]
+[browser_removeUnsafeProtocolsFromURLBarPaste.js]
+[browser_remove_match.js]
+[browser_restoreEmptyInput.js]
+[browser_resultSpan.js]
+[browser_result_menu.js]
+[browser_result_onSelection.js]
+[browser_retainedResultsOnFocus.js]
+[browser_revert.js]
+[browser_searchFunction.js]
+[browser_searchHistoryLimit.js]
+[browser_searchMode_alias_replacement.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_searchMode_autofill.js]
+[browser_searchMode_clickLink.js]
+https_first_disabled = true
+support-files =
+ dummy_page.html
+[browser_searchMode_engineRemoval.js]
+[browser_searchMode_excludeResults.js]
+[browser_searchMode_heuristic.js]
+https_first_disabled = true
+[browser_searchMode_indicator.js]
+https_first_disabled = true
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_searchMode_indicator_clickthrough.js]
+[browser_searchMode_localOneOffs_actionText.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_searchMode_newWindow.js]
+[browser_searchMode_no_results.js]
+[browser_searchMode_oneOffButton.js]
+[browser_searchMode_pickResult.js]
+https_first_disabled = true
+[browser_searchMode_preview.js]
+[browser_searchMode_sessionStore.js]
+https_first_disabled = true
+skip-if = os == 'mac' && debug && verify # bug 1671045
+[browser_searchMode_setURI.js]
+https_first_disabled = true
+[browser_searchMode_suggestions.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+ searchSuggestionEngineMany.xml
+[browser_searchMode_switchTabs.js]
+[browser_searchSettings.js]
+[browser_searchSingleWordNotification.js]
+https_first_disabled = true
+skip-if =
+ os == 'linux' && bits == 64 # Bug 1773830
+[browser_searchSuggestions.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_searchTelemetry.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_search_bookmarks_from_bookmarks_menu.js]
+[browser_search_history_from_history_panel.js]
+[browser_selectStaleResults.js]
+support-files =
+ searchSuggestionEngineSlow.xml
+ searchSuggestionEngine.sjs
+[browser_selectionKeyNavigation.js]
+[browser_separatePrivateDefault.js]
+support-files =
+ POSTSearchEngine.xml
+ print_postdata.sjs
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+ searchSuggestionEngine2.xml
+[browser_separatePrivateDefault_differentEngine.js]
+support-files =
+ POSTSearchEngine.xml
+ print_postdata.sjs
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+ searchSuggestionEngine2.xml
+[browser_shortcuts_add_search_engine.js]
+support-files =
+ add_search_engine_many.html
+ add_search_engine_two.html
+ add_search_engine_0.xml
+ add_search_engine_1.xml
+[browser_speculative_connect.js]
+support-files =
+ searchSuggestionEngine2.xml
+ searchSuggestionEngine.sjs
+[browser_speculative_connect_not_with_client_cert.js]
+[browser_stop.js]
+[browser_stopSearchOnSelection.js]
+support-files =
+ searchSuggestionEngineSlow.xml
+ searchSuggestionEngine.sjs
+[browser_stop_pending.js]
+https_first_disabled = true
+support-files =
+ slow-page.sjs
+[browser_strip_on_share.js]
+[browser_suggestedIndex.js]
+[browser_suppressFocusBorder.js]
+[browser_switchTab_closesUrlbarPopup.js]
+[browser_switchTab_decodeuri.js]
+[browser_switchTab_inputHistory.js]
+[browser_switchTab_override.js]
+[browser_switchToTabHavingURI_aOpenParams.js]
+[browser_switchToTab_chiclet.js]
+[browser_switchToTab_closes_newtab.js]
+[browser_switchToTab_fullUrl_repeatedKeydown.js]
+[browser_tabKeyBehavior.js]
+[browser_tabMatchesInAwesomebar.js]
+support-files =
+ moz.png
+[browser_tabMatchesInAwesomebar_perwindowpb.js]
+[browser_tabToSearch.js]
+[browser_textruns.js]
+[browser_tokenAlias.js]
+[browser_top_sites.js]
+https_first_disabled = true
+[browser_top_sites_private.js]
+https_first_disabled = true
+[browser_typed_value.js]
+[browser_unitConversion.js]
+[browser_updateForDomainCompletion.js]
+https_first_disabled = true
+[browser_urlbar_annotation.js]
+support-files =
+ redirect_to.sjs
+[browser_urlbar_event_telemetry_abandonment.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+skip-if = os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_urlbar_event_telemetry_engagement.js]
+https_first_disabled = true
+skip-if =
+ apple_catalina # Bug 1625690
+ apple_silicon # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs
+ os == 'linux' # Bug 1748986, bug 1775824
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_urlbar_event_telemetry_noEvent.js]
+[browser_urlbar_selection.js]
+skip-if = (os == 'mac') # bug 1570474
+[browser_urlbar_telemetry.js]
+tags = search-telemetry
+support-files =
+ urlbarTelemetrySearchSuggestions.sjs
+ urlbarTelemetrySearchSuggestions.xml
+[browser_urlbar_telemetry_autofill.js]
+tags = search-telemetry
+[browser_urlbar_telemetry_dynamic.js]
+tags = search-telemetry
+support-files =
+ urlbarTelemetryUrlbarDynamic.css
+[browser_urlbar_telemetry_extension.js]
+tags = search-telemetry
+[browser_urlbar_telemetry_handoff.js]
+tags = search-telemetry
+[browser_urlbar_telemetry_persisted.js]
+tags = search-telemetry
+[browser_urlbar_telemetry_places.js]
+https_first_disabled = true
+tags = search-telemetry
+[browser_urlbar_telemetry_quickactions.js]
+tags = search-telemetry
+[browser_urlbar_telemetry_remotetab.js]
+tags = search-telemetry
+[browser_urlbar_telemetry_searchmode.js]
+tags = search-telemetry
+support-files =
+ urlbarTelemetrySearchSuggestions.sjs
+ urlbarTelemetrySearchSuggestions.xml
+[browser_urlbar_telemetry_sponsored_topsites.js]
+https_first_disabled = true
+tags = search-telemetry
+[browser_urlbar_telemetry_tabtosearch.js]
+tags = search-telemetry
+[browser_urlbar_telemetry_tip.js]
+tags = search-telemetry
+[browser_urlbar_telemetry_topsite.js]
+tags = search-telemetry
+[browser_urlbar_telemetry_zeroPrefix.js]
+tags = search-telemetry
+[browser_userTypedValue.js]
+support-files = file_userTypedValue.html
+[browser_valueOnTabSwitch.js]
+[browser_view_emptyResultSet.js]
+[browser_view_resultDisplay.js]
+[browser_view_resultTypes_display.js]
+support-files =
+ print_postdata.sjs
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_view_selectionByMouse.js]
+skip-if =
+ os == "linux" && asan # Bug 1789051
+[browser_waitForLoadOrTimeout.js]
+https_first_disabled = true
+skip-if =
+ tsan # Bug 1683730
+ os == "linux" && bits == 64 && !debug # Bug 1666092
+[browser_whereToOpen.js]
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_formatValue.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_formatValue.js
new file mode 100644
index 0000000000..d4b73603f9
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_formatValue.js
@@ -0,0 +1,178 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Checks that the url formatter properly recognizes the host and de-emphasizes
+// the rest of the url.
+
+/**
+ * Tests a given url.
+ * The de-emphasized parts must be wrapped in "<" and ">" chars.
+ *
+ * @param {string} aExpected The url to test.
+ * @param {string} aClobbered [optional] Normally the url is de-emphasized
+ * in-place, thus it's enough to pass aExpected. Though, in some cases
+ * the formatter may decide to replace the url with a fixed one, because
+ * it can't properly guess a host. In that case aClobbered is the
+ * expected de-emphasized value.
+ * @param {boolean} synthesizeInput [optional] Whether to synthesize an input
+ * event to test.
+ */
+function testVal(aExpected, aClobbered = null, synthesizeInput = false) {
+ let str = aExpected.replace(/[<>]/g, "");
+ if (synthesizeInput) {
+ gURLBar.focus();
+ gURLBar.select();
+ EventUtils.sendString(str);
+ Assert.equal(
+ gURLBar.editor.rootElement.textContent,
+ str,
+ "Url is not highlighted"
+ );
+ gBrowser.selectedBrowser.focus();
+ } else {
+ gURLBar.value = str;
+ }
+
+ let selectionController = gURLBar.editor.selectionController;
+ let selection = selectionController.getSelection(
+ selectionController.SELECTION_URLSECONDARY
+ );
+ let value = gURLBar.editor.rootElement.textContent;
+ let result = "";
+ for (let i = 0; i < selection.rangeCount; i++) {
+ let range = selection.getRangeAt(i).toString();
+ let pos = value.indexOf(range);
+ result += value.substring(0, pos) + "<" + range + ">";
+ value = value.substring(pos + range.length);
+ }
+ result += value;
+ Assert.equal(
+ result,
+ aClobbered || aExpected,
+ "Correct part of the url is de-emphasized" +
+ (synthesizeInput ? " (with input simulation)" : "")
+ );
+
+ // Now re-test synthesizing input.
+ if (!synthesizeInput) {
+ testVal(aExpected, aClobbered, true);
+ }
+}
+
+function test() {
+ const prefname = "browser.urlbar.formatting.enabled";
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(prefname);
+ gURLBar.setURI();
+ });
+
+ gBrowser.selectedBrowser.focus();
+
+ testVal("<https://>mozilla.org");
+ testVal("<https://>mözilla.org");
+ testVal("<https://>mozilla.imaginatory");
+
+ testVal("<https://www.>mozilla.org");
+ testVal("<https://sub.>mozilla.org");
+ testVal("<https://sub1.sub2.sub3.>mozilla.org");
+ testVal("<www.>mozilla.org");
+ testVal("<sub.>mozilla.org");
+ testVal("<sub1.sub2.sub3.>mozilla.org");
+ testVal("<mozilla.com.>mozilla.com");
+ testVal("<https://mozilla.com:mozilla.com@>mozilla.com");
+ testVal("<mozilla.com:mozilla.com@>mozilla.com");
+
+ testVal("<ftp.>mozilla.org");
+ testVal("<ftp://ftp.>mozilla.org");
+
+ testVal("<https://sub.>mozilla.org");
+ testVal("<https://sub1.sub2.sub3.>mozilla.org");
+ testVal("<https://user:pass@sub1.sub2.sub3.>mozilla.org");
+ testVal("<https://user:pass@>mozilla.org");
+ testVal("<user:pass@sub1.sub2.sub3.>mozilla.org");
+ testVal("<user:pass@>mozilla.org");
+
+ testVal("<https://>mozilla.org< >");
+ testVal("mozilla.org< >");
+ // RTL characters in domain change order of domain and suffix. Domain should
+ // be highlighted correctly.
+ testVal("<http://>اختبار.اختبار</www.mozilla.org/index.html>");
+
+ testVal("<https://>mozilla.org</file.ext>");
+ testVal("<https://>mozilla.org</sub/file.ext>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo&bar>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo&bar#top>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo&bar#top>");
+ testVal("foo.bar<?q=test>");
+ testVal("foo.bar<#mozilla.org>");
+ testVal("foo.bar<?somewhere.mozilla.org>");
+ testVal("foo.bar<?@mozilla.org>");
+ testVal("foo.bar<#x@mozilla.org>");
+ testVal("foo.bar<#@x@mozilla.org>");
+ testVal("foo.bar<?x@mozilla.org>");
+ testVal("foo.bar<?@x@mozilla.org>");
+ testVal("<foo.bar@x@>mozilla.org");
+ testVal("<foo.bar@:baz@>mozilla.org");
+ testVal("<foo.bar:@baz@>mozilla.org");
+ testVal("<foo.bar@:ba:z@>mozilla.org");
+ testVal("<foo.:bar:@baz@>mozilla.org");
+ testVal(
+ "foopy:\\blah@somewhere.com//whatever/",
+ "foopy</blah@somewhere.com//whatever/>"
+ );
+
+ testVal("<https://sub.>mozilla.org<:666/file.ext>");
+ testVal("<sub.>mozilla.org<:666/file.ext>");
+ testVal("localhost<:666/file.ext>");
+
+ let IPs = [
+ "192.168.1.1",
+ "[::]",
+ "[::1]",
+ "[1::]",
+ "[::]",
+ "[::1]",
+ "[1::]",
+ "[1:2:3:4:5:6:7::]",
+ "[::1:2:3:4:5:6:7]",
+ "[1:2:a:B:c:D:e:F]",
+ "[1::8]",
+ "[1:2::8]",
+ "[fe80::222:19ff:fe11:8c76]",
+ "[0000:0123:4567:89AB:CDEF:abcd:ef00:0000]",
+ "[::192.168.1.1]",
+ "[1::0.0.0.0]",
+ "[1:2::255.255.255.255]",
+ "[1:2:3::255.255.255.255]",
+ "[1:2:3:4::255.255.255.255]",
+ "[1:2:3:4:5::255.255.255.255]",
+ "[1:2:3:4:5:6:255.255.255.255]",
+ ];
+ IPs.forEach(function (IP) {
+ testVal(IP);
+ testVal(IP + "</file.ext>");
+ testVal(IP + "<:666/file.ext>");
+ testVal("<https://>" + IP);
+ testVal(`<https://>${IP}</file.ext>`);
+ testVal(`<https://user:pass@>${IP}<:666/file.ext>`);
+ testVal(`<user:pass@>${IP}<:666/file.ext>`);
+ testVal(`user:\\pass@${IP}/`, `user</pass@${IP}/>`);
+ });
+
+ testVal("mailto:admin@mozilla.org");
+ testVal("gopher://mozilla.org/");
+ testVal("about:config");
+ testVal("jar:http://mozilla.org/example.jar!/");
+ testVal("view-source:http://mozilla.org/");
+ testVal("foo9://mozilla.org/");
+ testVal("foo+://mozilla.org/");
+ testVal("foo.://mozilla.org/");
+ testVal("foo-://mozilla.org/");
+
+ // Disable formatting.
+ Services.prefs.setBoolPref(prefname, false);
+
+ testVal("https://mozilla.org");
+}
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_formatValue_detachedTab.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_formatValue_detachedTab.js
new file mode 100644
index 0000000000..02da2a8e2b
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_formatValue_detachedTab.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+// After detaching a tab into a new window, the input value in the new window
+// should be formatted.
+
+add_task(async function detach() {
+ // Sometimes the value isn't formatted on Mac when running in verify chaos
+ // mode. The usual, proper front-end code path is hit, and the path that
+ // removes formatting is not hit, so it seems like some kind of race in the
+ // editor or selection code in Gecko. Since this has only been observed on Mac
+ // in chaos mode and doesn't seem to be a problem in urlbar code, skip the
+ // test in that case.
+ if (AppConstants.platform == "macosx" && Services.env.get("MOZ_CHAOSMODE")) {
+ Assert.ok(true, "Skipping test in chaos mode on Mac");
+ return;
+ }
+
+ UrlbarPrefs.clear("formatting.enabled");
+ Assert.ok(
+ UrlbarPrefs.get("formatting.enabled"),
+ "Formatting is enabled by default"
+ );
+
+ info("Waiting for new tab");
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: "https://example.com/detach",
+ });
+
+ let winPromise = BrowserTestUtils.waitForNewWindow();
+ info("Detaching tab");
+ let win = gBrowser.replaceTabWithWindow(tab, {});
+ info("Waiting for new window");
+ await winPromise;
+
+ // Wait an extra tick for good measure since the code itself also waits for
+ // `delayedStartupPromise`.
+ info("Waiting for delayed startup in new window");
+ await win.delayedStartupPromise;
+ info("Waiting for tick");
+ await TestUtils.waitForTick();
+
+ assertValue("<https://>example.com</detach>", win);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Asserts formatting in the input is correct.
+ *
+ * @param {string} expectedValue
+ * The URL to test. The parts the are expected to be de-emphasized should be
+ * wrapped in "<" and ">" chars.
+ * @param {window} win
+ * The input in this window will be tested.
+ */
+function assertValue(expectedValue, win = window) {
+ let selectionController = win.gURLBar.editor.selectionController;
+ let selection = selectionController.getSelection(
+ selectionController.SELECTION_URLSECONDARY
+ );
+ let value = win.gURLBar.editor.rootElement.textContent;
+ let result = "";
+ for (let i = 0; i < selection.rangeCount; i++) {
+ let range = selection.getRangeAt(i).toString();
+ let pos = value.indexOf(range);
+ result += value.substring(0, pos) + "<" + range + ">";
+ value = value.substring(pos + range.length);
+ }
+ result += value;
+ Assert.equal(
+ result,
+ expectedValue,
+ "Correct part of the url is de-emphasized"
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_hiddenFocus.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_hiddenFocus.js
new file mode 100644
index 0000000000..08e5ae97d3
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_hiddenFocus.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ registerCleanupFunction(async function () {
+ BrowserTestUtils.removeTab(tab);
+ gURLBar.setURI();
+ });
+
+ gURLBar.blur();
+ ok(!gURLBar.focused, "url bar is not focused");
+ ok(!gURLBar.hasAttribute("focused"), "url bar is not visibly focused");
+ gURLBar.setHiddenFocus();
+ ok(gURLBar.focused, "url bar is focused");
+ ok(!gURLBar.hasAttribute("focused"), "url bar is not visibly focused");
+ gURLBar.removeHiddenFocus();
+ ok(gURLBar.focused, "url bar is focused");
+ ok(gURLBar.hasAttribute("focused"), "url bar is visibly focused");
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow.js
new file mode 100644
index 0000000000..b0a3337d84
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow.js
@@ -0,0 +1,156 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function testVal(aExpected, overflowSide = "") {
+ info(`Testing ${aExpected}`);
+ try {
+ gURLBar.setURI(makeURI(aExpected));
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_MALFORMED_URI) {
+ throw ex;
+ }
+ // For values without a protocol fallback to setting the raw value.
+ gURLBar.value = aExpected;
+ }
+
+ Assert.equal(
+ gURLBar.selectionStart,
+ gURLBar.selectionEnd,
+ "Selection sanity check"
+ );
+
+ gURLBar.focus();
+ Assert.equal(
+ document.activeElement,
+ gURLBar.inputField,
+ "URL Bar should be focused"
+ );
+ Assert.equal(
+ gURLBar.valueFormatter.scheme.value,
+ "",
+ "Check the scheme value"
+ );
+ Assert.equal(
+ getComputedStyle(gURLBar.valueFormatter.scheme).visibility,
+ "hidden",
+ "Check the scheme box visibility"
+ );
+
+ gURLBar.blur();
+ await window.promiseDocumentFlushed(() => {});
+ // The attribute doesn't always change, so we can't use waitForAttribute.
+ await TestUtils.waitForCondition(
+ () => gURLBar.getAttribute("textoverflow") === overflowSide
+ );
+
+ let scheme = aExpected.match(/^([a-z]+:\/{0,2})/)?.[1] || "";
+ // We strip http, so we should not show the scheme for it.
+ if (
+ scheme == "http://" &&
+ Services.prefs.getBoolPref("browser.urlbar.trimURLs", true)
+ ) {
+ scheme = "";
+ }
+
+ Assert.equal(
+ gURLBar.valueFormatter.scheme.value,
+ scheme,
+ "Check the scheme value"
+ );
+ let isOverflowed =
+ gURLBar.inputField.scrollWidth > gURLBar.inputField.clientWidth;
+ Assert.equal(isOverflowed, !!overflowSide, "Check The input field overflow");
+ Assert.equal(
+ gURLBar.getAttribute("textoverflow"),
+ overflowSide,
+ "Check the textoverflow attribute"
+ );
+ if (overflowSide) {
+ let side = gURLBar.getAttribute("domaindir") == "ltr" ? "right" : "left";
+ Assert.equal(side, overflowSide, "Check the overflow side");
+ Assert.equal(
+ getComputedStyle(gURLBar.valueFormatter.scheme).visibility,
+ scheme && isOverflowed && overflowSide == "left" ? "visible" : "hidden",
+ "Check the scheme box visibility"
+ );
+
+ info("Focus, change scroll position and blur, to ensure proper restore");
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_End");
+ gURLBar.blur();
+ await window.promiseDocumentFlushed(() => {});
+ // The attribute doesn't always change, so we can't use waitForAttribute.
+ await TestUtils.waitForCondition(
+ () => gURLBar.getAttribute("textoverflow") === overflowSide
+ );
+
+ Assert.equal(side, overflowSide, "Check the overflow side");
+ Assert.equal(
+ getComputedStyle(gURLBar.valueFormatter.scheme).visibility,
+ scheme && isOverflowed && overflowSide == "left" ? "visible" : "hidden",
+ "Check the scheme box visibility"
+ );
+ }
+}
+
+add_task(async function () {
+ // We use a new tab for the test to be sure all the tab switching and loading
+ // is complete before starting, otherwise onLocationChange for this tab could
+ // override the value we set with an empty value.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ registerCleanupFunction(function () {
+ gURLBar.setURI();
+ BrowserTestUtils.removeTab(tab);
+ });
+
+ let lotsOfSpaces = "%20".repeat(200);
+
+ // اسماء.شبكة
+ let rtlDomain =
+ "\u0627\u0633\u0645\u0627\u0621\u002e\u0634\u0628\u0643\u0629";
+ let rtlChar = "\u0627";
+
+ // Mix the direction of the tests to cover more cases, and to ensure the
+ // textoverflow attribute changes every time, because tewtVal waits for that.
+ await testVal(`https://mozilla.org/${lotsOfSpaces}/test/`, "right");
+ await testVal(`https://mozilla.org/`);
+ await testVal(`https://${rtlDomain}/${lotsOfSpaces}/test/`, "left");
+ await testVal(`https://mozilla.org:8888/${lotsOfSpaces}/test/`, "right");
+ await testVal(`https://${rtlDomain}:8888/${lotsOfSpaces}/test/`, "left");
+
+ await testVal(`ftp://mozilla.org/${lotsOfSpaces}/test/`, "right");
+ await testVal(`ftp://${rtlDomain}/${lotsOfSpaces}/test/`, "left");
+ await testVal(`ftp://mozilla.org/`);
+
+ await testVal(`http://${rtlDomain}/${lotsOfSpaces}/test/`, "left");
+ await testVal(`http://mozilla.org/`);
+ await testVal(`http://mozilla.org/${lotsOfSpaces}/test/`, "right");
+ await testVal(`http://${rtlDomain}:8888/${lotsOfSpaces}/test/`, "left");
+ await testVal(`http://[::1]/${rtlChar}/${lotsOfSpaces}/test/`, "right");
+
+ info("Test with formatting disabled");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.formatting.enabled", false],
+ ["browser.urlbar.trimURLs", false],
+ ],
+ });
+
+ await testVal(`https://mozilla.org/`);
+ await testVal(`https://${rtlDomain}/${lotsOfSpaces}/test/`, "left");
+ await testVal(`https://mozilla.org/${lotsOfSpaces}/test/`, "right");
+
+ info("Test with trimURLs disabled");
+ await testVal(`http://${rtlDomain}/${lotsOfSpaces}/test/`, "left");
+
+ await SpecialPowers.popPrefEnv();
+
+ info("Tests without protocol");
+ await testVal(`mozilla.org/${lotsOfSpaces}/test/`, "right");
+ await testVal(`mozilla.org/`);
+ await testVal(`${rtlDomain}/${lotsOfSpaces}/test/`, "left");
+ await testVal(`mozilla.org:8888/${lotsOfSpaces}/test/`, "right");
+ await testVal(`${rtlDomain}:8888/${lotsOfSpaces}/test/`, "left");
+ await testVal(`[::1]/${rtlChar}/${lotsOfSpaces}/test/`, "right");
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow_resize.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow_resize.js
new file mode 100644
index 0000000000..879911d703
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow_resize.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+async function testVal(win, url) {
+ info(`Testing ${url}`);
+ win.gURLBar.setURI(makeURI(url));
+
+ let urlbar = win.gURLBar;
+ urlbar.blur();
+
+ for (let width of [1000, 800]) {
+ win.resizeTo(width, 500);
+ await win.promiseDocumentFlushed(() => {});
+ Assert.greater(
+ urlbar.inputField.scrollWidth,
+ urlbar.inputField.clientWidth,
+ "Check The input field overflows"
+ );
+ // Resize is handled on a timer, so we must wait for it.
+ await TestUtils.waitForCondition(
+ () => urlbar.inputField.scrollLeft == urlbar.inputField.scrollLeftMax,
+ "The urlbar input field is completely scrolled to the end"
+ );
+ await TestUtils.waitForCondition(
+ () => urlbar.getAttribute("textoverflow") == "left",
+ "Wait for the textoverflow attribute"
+ );
+ }
+}
+
+add_task(async function () {
+ // We use a new tab for the test to be sure all the tab switching and loading
+ // is complete before starting, otherwise onLocationChange for this tab could
+ // override the value we set with an empty value.
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
+
+ let lotsOfSpaces = "%20".repeat(200);
+
+ // اسماء.شبكة
+ let rtlDomain =
+ "\u0627\u0633\u0645\u0627\u0621\u002e\u0634\u0628\u0643\u0629";
+
+ // Mix the direction of the tests to cover more cases, and to ensure the
+ // textoverflow attribute changes every time, because tewtVal waits for that.
+ await testVal(win, `https://${rtlDomain}/${lotsOfSpaces}/test/`);
+
+ info("Test with formatting and trimurl disabled");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.formatting.enabled", false],
+ ["browser.urlbar.trimURLs", false],
+ ],
+ });
+
+ await testVal(win, `https://${rtlDomain}/${lotsOfSpaces}/test/`);
+ await testVal(win, `http://${rtlDomain}/${lotsOfSpaces}/test/`);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_privateFeature.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_privateFeature.js
new file mode 100644
index 0000000000..fb81e9f536
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_privateFeature.js
@@ -0,0 +1,74 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests that _loadURL correctly sets and passes on the `private` window
+// attribute (or not) with various arguments.
+
+add_task(async function privateFeatureSetOnNewWindowImplicitly() {
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ let newWinOpened = BrowserTestUtils.waitForNewWindow();
+
+ privateWin.gURLBar._loadURL("about:blank", null, "window", {});
+
+ let newWin = await newWinOpened;
+ Assert.equal(
+ newWin.docShell.treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIAppWindow).chromeFlags &
+ Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW,
+ Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW,
+ "New window opened from existing private window should be marked as private"
+ );
+ await BrowserTestUtils.closeWindow(newWin);
+ await BrowserTestUtils.closeWindow(privateWin);
+});
+
+add_task(async function privateFeatureSetOnNewWindowExplicitly() {
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ let newWinOpened = BrowserTestUtils.waitForNewWindow();
+
+ privateWin.gURLBar._loadURL("about:blank", null, "window", { private: true });
+
+ let newWin = await newWinOpened;
+ Assert.equal(
+ newWin.docShell.treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIAppWindow).chromeFlags &
+ Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW,
+ Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW,
+ "New window opened from existing private window should be marked as private"
+ );
+ await BrowserTestUtils.closeWindow(newWin);
+ await BrowserTestUtils.closeWindow(privateWin);
+});
+
+add_task(async function privateFeatureNotSetOnNewWindowExplicitly() {
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ let newWinOpened = BrowserTestUtils.waitForNewWindow();
+
+ privateWin.gURLBar._loadURL("about:blank", null, "window", {
+ private: false,
+ });
+
+ let newWin = await newWinOpened;
+ Assert.notEqual(
+ newWin.docShell.treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIAppWindow).chromeFlags &
+ Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW,
+ Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW,
+ "New window opened from existing private window should be marked as private"
+ );
+ await BrowserTestUtils.closeWindow(newWin);
+ await BrowserTestUtils.closeWindow(privateWin);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms.js
new file mode 100644
index 0000000000..3d38036d84
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms.js
@@ -0,0 +1,306 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// These tests check the behavior of the Urlbar when loading a page
+// whose url matches that of the default search engine.
+
+let defaultTestEngine;
+
+// The main search string used in tests
+const SEARCH_STRING = "chocolate cake";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", true]],
+ });
+
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: "MozSearch",
+ search_url: "https://www.example.com/",
+ search_url_get_params: "q={searchTerms}&pc=fake_code",
+ },
+ { setAsDefault: true }
+ );
+ defaultTestEngine = Services.search.getEngineByName("MozSearch");
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+// Starts a search with a tab and asserts that
+// the state of the Urlbar contains the search term
+async function searchWithTab(
+ searchString,
+ tab = null,
+ engine = defaultTestEngine
+) {
+ if (!tab) {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ }
+
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(engine, searchString);
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedSearchUrl
+ );
+
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+ await browserLoadedPromise;
+
+ assertSearchStringIsInUrlbar(searchString);
+
+ return { tab, expectedSearchUrl };
+}
+
+// Search terms should show up in the url bar if the pref is on
+// and the SERP url matches the one constructed in Firefox
+add_task(async function list_of_search_strings() {
+ const searches = [
+ {
+ // Single word
+ searchString: "chocolate",
+ },
+ {
+ // Word with space
+ searchString: "chocolate cake",
+ },
+ {
+ // Special characters
+ searchString: "chocolate;,?:@&=+$-_.!~*'()#cake",
+ },
+ {
+ searchString: '"chocolate cake" -recipes',
+ },
+ {
+ // Search with special characters
+ searchString: "site:example.com chocolate -cake",
+ },
+ ];
+
+ for (let { searchString } of searches) {
+ let { tab } = await searchWithTab(searchString);
+ BrowserTestUtils.removeTab(tab);
+ }
+});
+
+// If a user does a search, goes to another page, and then
+// goes back to the SERP, the search term should show.
+add_task(async function go_back() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ BrowserTestUtils.loadURIString(
+ tab.linkedBrowser,
+ "http://www.example.com/some_url"
+ );
+ await browserLoadedPromise;
+
+ let pageShowPromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "pageshow"
+ );
+ tab.linkedBrowser.goBack();
+ await pageShowPromise;
+
+ assertSearchStringIsInUrlbar(SEARCH_STRING);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Manually loading a url that matches a search query url
+// should show the search term in the Urlbar.
+add_task(async function load_url() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(
+ defaultTestEngine,
+ SEARCH_STRING
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedSearchUrl
+ );
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, expectedSearchUrl);
+ await browserLoadedPromise;
+ assertSearchStringIsInUrlbar(SEARCH_STRING);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Focusing and blurring the urlbar while the search terms
+// persist should change the pageproxystate.
+add_task(async function focus_and_unfocus() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+
+ gURLBar.focus();
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "invalid",
+ "Should have matching pageproxystate."
+ );
+
+ gURLBar.blur();
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "valid",
+ "Should have matching pageproxystate."
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// If the user modifies the search term, blurring the
+// urlbar should keep the urlbar in an invalid pageproxystate.
+add_task(async function focus_and_unfocus_modified() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+
+ gURLBar.focus();
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "invalid",
+ "Should have matching pageproxystate."
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: "another search term",
+ fireInputEvent: true,
+ });
+
+ gURLBar.blur();
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "invalid",
+ "Should have matching pageproxystate."
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// If Top Sites is cached in the UrlbarView, don't show it if the search terms
+// persist in the Urlbar.
+add_task(async function focus_after_top_sites() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Prevent the persist tip from interrupting clicking the Urlbar
+ // after the the SERP has been loaded.
+ [
+ `browser.urlbar.tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.PERSIST}`,
+ 10000,
+ ],
+ ["browser.newtabpage.activity-stream.feeds.topsites", true],
+ ],
+ });
+
+ // Populate Top Sites on a clean version of Places.
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesTestUtils.promiseAsyncUpdates();
+ await TestUtils.waitForTick();
+
+ const urls = [];
+ const N_TOP_SITES = 5;
+ const N_VISITS = 5;
+
+ for (let i = 0; i < N_TOP_SITES; i++) {
+ let url = `https://${i}.example.com/hello_world${i}`;
+ urls.unshift(url);
+ // Each URL needs to be added several times to boost its frecency enough to
+ // qualify as a top site.
+ for (let j = 0; j < N_VISITS; j++) {
+ await PlacesTestUtils.addVisits(url);
+ }
+ }
+
+ let changedPromise = TestUtils.topicObserved("newtab-top-sites-changed").then(
+ () => info("Observed newtab-top-sites-changed")
+ );
+ await updateTopSites(sites => sites?.length == N_TOP_SITES);
+ await changedPromise;
+
+ // Ensure Top Sites is cached.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ Assert.ok(gURLBar.view.isOpen, "UrlbarView should be open.");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ N_TOP_SITES,
+ `The number of results should be the same as the number of Top Sites ${N_TOP_SITES}.`
+ );
+ for (let i = 0; i < urls.length; i++) {
+ let { url } = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.equal(url, urls[i], "The result url should be a Top Site.");
+ }
+
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(
+ defaultTestEngine,
+ SEARCH_STRING
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: SEARCH_STRING,
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ expectedSearchUrl
+ );
+ Assert.equal(
+ gBrowser.selectedBrowser.searchTerms,
+ SEARCH_STRING,
+ "The search term should be in the Urlbar."
+ );
+
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.notEqual(
+ details.url,
+ urls[0],
+ "The first result should not be a Top Site."
+ );
+ Assert.equal(
+ details.heuristic,
+ true,
+ "The first result should be the heuristic result."
+ );
+ Assert.equal(
+ details.url,
+ expectedSearchUrl,
+ "The first result url should be the same as the SERP."
+ );
+ Assert.equal(
+ details.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "The first result be a search result."
+ );
+ Assert.equal(
+ details.searchParams?.query,
+ SEARCH_STRING,
+ "The first result should have a matching query."
+ );
+
+ // Clean up.
+ SpecialPowers.popPrefEnv();
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_backgroundTabs.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_backgroundTabs.js
new file mode 100644
index 0000000000..8ed29a9c5b
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_backgroundTabs.js
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// These tests check the behavior of the Urlbar when search terms are
+// expected to be shown and tabs are opened in the background.
+
+let defaultTestEngine;
+
+// The main search string used in tests
+const SEARCH_STRING = "chocolate cake";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", true]],
+ });
+
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: "MozSearch",
+ search_url: "https://www.example.com/",
+ search_url_get_params: "q={searchTerms}&pc=fake_code",
+ },
+ { setAsDefault: true }
+ );
+ defaultTestEngine = Services.search.getEngineByName("MozSearch");
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+// If a user opens background tab search from the Urlbar,
+// the search term should show when the tab is focused.
+add_task(async function ctrl_open() {
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(
+ defaultTestEngine,
+ SEARCH_STRING
+ );
+ // Search for the term in a new background tab.
+ let newTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ expectedSearchUrl
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: SEARCH_STRING,
+ fireInputEvent: true,
+ });
+ gURLBar.focus();
+
+ EventUtils.synthesizeKey("KEY_Enter", {
+ altKey: true,
+ shiftKey: true,
+ });
+
+ // Find the background tab that was created, and switch to it.
+ let backgroundTab = await newTabPromise;
+ await BrowserTestUtils.switchTab(gBrowser, backgroundTab);
+ assertSearchStringIsInUrlbar(SEARCH_STRING);
+
+ BrowserTestUtils.removeTab(backgroundTab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_modifiedUrl.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_modifiedUrl.js
new file mode 100644
index 0000000000..4c20864171
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_modifiedUrl.js
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// These tests check the behavior of the Urlbar when search terms are
+// expected to be shown but the url is modified from what the browser expects.
+
+let defaultTestEngine;
+
+// The main search string used in tests
+const SEARCH_STRING = "chocolate cake";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", true]],
+ });
+
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: "MozSearch",
+ search_url: "https://www.example.com/",
+ search_url_get_params: "q={searchTerms}&pc=fake_code",
+ },
+ { setAsDefault: true }
+ );
+ defaultTestEngine = Services.search.getEngineByName("MozSearch");
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+// If a SERP uses the History API to modify the URI,
+// the search term should still show in the URL bar.
+add_task(async function history_push_state() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(
+ defaultTestEngine,
+ SEARCH_STRING
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedSearchUrl
+ );
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, expectedSearchUrl);
+ await browserLoadedPromise;
+
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(gBrowser);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ let url = new URL(content.window.location);
+ url.searchParams.set("pc", "fake_code_2");
+ content.history.pushState({}, "", url);
+ });
+
+ await locationChangePromise;
+ // Check URI to make sure that it's actually been changed
+ Assert.equal(
+ gBrowser.currentURI.spec,
+ `https://www.example.com/?q=chocolate+cake&pc=fake_code_2`,
+ "URI of Urlbar should have changed"
+ );
+
+ Assert.equal(
+ gURLBar.value,
+ SEARCH_STRING,
+ `Search string ${SEARCH_STRING} should be in the url bar`
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Loading a url that looks like a search query url but has additional
+// query params should not show the search term in the Urlbar.
+add_task(async function url_with_additional_query_params() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(
+ defaultTestEngine,
+ SEARCH_STRING
+ );
+ // Add a query param
+ expectedSearchUrl += "&another_code=something_else";
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedSearchUrl
+ );
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, expectedSearchUrl);
+ await browserLoadedPromise;
+
+ Assert.equal(gURLBar.value, expectedSearchUrl, `URL should be in URL bar`);
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "valid",
+ "Pageproxystate should be valid"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_moveTab.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_moveTab.js
new file mode 100644
index 0000000000..59f0eca916
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_moveTab.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/. */
+
+/*
+ These tests check the behavior of the Urlbar when search terms are shown
+ and the tab with the default SERP moves from one window to another.
+
+ Unlike other searchTerm tests, these modify the currentURI to ensure
+ that the currentURI has a different spec than the default SERP so that
+ the search terms won't show if the originalURI wasn't properly copied
+ during the tab swap.
+*/
+
+let originalEngine, defaultTestEngine;
+
+// The main search keyword used in tests
+const SEARCH_STRING = "chocolate cake";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", true]],
+ });
+
+ await SearchTestUtils.installSearchExtension({
+ name: "MozSearch",
+ search_url: "https://www.example.com/",
+ search_url_get_params: "q={searchTerms}&pc=fake_code",
+ });
+ defaultTestEngine = Services.search.getEngineByName("MozSearch");
+
+ originalEngine = await Services.search.getDefault();
+ await Services.search.setDefault(
+ defaultTestEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ registerCleanupFunction(async function () {
+ await Services.search.setDefault(
+ originalEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await PlacesUtils.history.clear();
+ });
+});
+
+async function searchWithTab(
+ searchString,
+ tab = null,
+ engine = defaultTestEngine
+) {
+ if (!tab) {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ }
+
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(engine, searchString);
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedSearchUrl
+ );
+
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+ await browserLoadedPromise;
+
+ return { tab, expectedSearchUrl };
+}
+
+// Move a tab showing the search term into its own window.
+add_task(async function move_tab_into_new_window() {
+ let { tab, expectedSearchUrl } = await searchWithTab(SEARCH_STRING);
+
+ // Mock the default SERP modifying the existing url
+ // so that the originalURI and currentURI differ.
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [expectedSearchUrl],
+ async url => {
+ content.history.pushState({}, "", url + "&pc2=firefox");
+ }
+ );
+
+ // Move the tab into its own window.
+ let newWindow = gBrowser.replaceTabWithWindow(tab);
+ await BrowserTestUtils.waitForEvent(tab.linkedBrowser, "SwapDocShells");
+
+ assertSearchStringIsInUrlbar(SEARCH_STRING, { win: newWindow });
+
+ // Clean up.
+ await BrowserTestUtils.closeWindow(newWindow);
+});
+
+// Move a tab from its own window into an existing window.
+add_task(async function move_tab_into_existing_window() {
+ // Load a second window with the default SERP.
+ let win = await BrowserTestUtils.openNewBrowserWindow({ remote: true });
+ let browser = win.gBrowser.selectedBrowser;
+ let tab = win.gBrowser.tabs[0];
+
+ // Load the default SERP into the second window.
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(
+ defaultTestEngine,
+ SEARCH_STRING
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ expectedSearchUrl
+ );
+ BrowserTestUtils.loadURIString(browser, expectedSearchUrl);
+ await browserLoadedPromise;
+
+ // Mock the default SERP modifying the existing url
+ // so that the originalURI and currentURI differ.
+ await SpecialPowers.spawn(browser, [expectedSearchUrl], async url => {
+ content.history.pushState({}, "", url + "&pc2=firefox");
+ });
+
+ // Make the first window adopt and switch to that tab.
+ tab = gBrowser.adoptTab(tab);
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+ assertSearchStringIsInUrlbar(SEARCH_STRING);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_popup.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_popup.js
new file mode 100644
index 0000000000..d25e17d960
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_popup.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/. */
+
+// These tests check the behavior of the Urlbar when persist search terms
+// are either enabled or disabled, and a popup notification is shown.
+
+function waitForPopupNotification() {
+ let promisePopupShown = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+ PopupNotifications.show(
+ gBrowser.selectedBrowser,
+ "test-notification",
+ "This is a sample popup."
+ );
+ return promisePopupShown;
+}
+
+// The main search string used in tests.
+const SEARCH_TERM = "chocolate";
+const PREF_FEATUREGATE = "browser.urlbar.showSearchTerms.featureGate";
+let defaultTestEngine;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_FEATUREGATE, true]],
+ });
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: "MozSearch",
+ search_url: "https://www.example.com/",
+ search_url_get_params: "q={searchTerms}&pc=fake_code",
+ },
+ { setAsDefault: true }
+ );
+
+ defaultTestEngine = Services.search.getEngineByName("MozSearch");
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+async function searchWithTab(
+ searchString,
+ tab = null,
+ engine = defaultTestEngine,
+ expectedPersistedSearchTerms = true
+) {
+ if (!tab) {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ }
+
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(engine, searchString);
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedSearchUrl
+ );
+
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+ await browserLoadedPromise;
+
+ if (expectedPersistedSearchTerms) {
+ assertSearchStringIsInUrlbar(searchString);
+ }
+
+ return { tab, expectedSearchUrl };
+}
+
+// A notification should cause the urlbar to revert while
+// the search term persists.
+add_task(async function generic_popup_when_persist_is_enabled() {
+ let { tab, expectedSearchUrl } = await searchWithTab(SEARCH_TERM);
+
+ await waitForPopupNotification();
+
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "valid",
+ "Urlbar should have a valid pageproxystate."
+ );
+
+ Assert.equal(
+ gURLBar.value,
+ expectedSearchUrl,
+ "Search url should be in the urlbar."
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// Ensure the urlbar is not being reverted when a prompt is shown
+// and the persist feature is disabled.
+add_task(async function generic_popup_no_revert_when_persist_is_disabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_FEATUREGATE, false]],
+ });
+
+ let { tab } = await searchWithTab(
+ SEARCH_TERM,
+ null,
+ defaultTestEngine,
+ false
+ );
+
+ // Have a user typed value in the urlbar to make
+ // pageproxystate invalid.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: SEARCH_TERM,
+ });
+ gURLBar.blur();
+
+ await waitForPopupNotification();
+
+ // Wait a brief amount of time between when the popup is shown
+ // and when the event handler should fire if it's enabled.
+ await TestUtils.waitForTick();
+
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "invalid",
+ "Urlbar should not be reverted."
+ );
+
+ Assert.equal(
+ gURLBar.value,
+ SEARCH_TERM,
+ "User typed value should remain in urlbar."
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_revert.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_revert.js
new file mode 100644
index 0000000000..91d6ea403a
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_revert.js
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// These tests check the behavior of the Urlbar when search terms are shown
+// and the user reverts the Urlbar.
+
+let defaultTestEngine;
+
+// The main search keyword used in tests
+const SEARCH_STRING = "chocolate cake";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", true]],
+ });
+
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: "MozSearch",
+ search_url: "https://www.example.com/",
+ search_url_get_params: "q={searchTerms}&pc=fake_code",
+ },
+ { setAsDefault: true }
+ );
+ defaultTestEngine = Services.search.getEngineByName("MozSearch");
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+async function searchWithTab(
+ searchString,
+ tab = null,
+ engine = defaultTestEngine
+) {
+ if (!tab) {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ }
+
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(engine, searchString);
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedSearchUrl
+ );
+
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+ await browserLoadedPromise;
+
+ assertSearchStringIsInUrlbar(searchString);
+
+ return { tab, expectedSearchUrl };
+}
+
+function synthesizeRevert() {
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_Escape", { repeat: 2 });
+}
+
+// Users should be able to revert the URL bar
+add_task(async function revert() {
+ let { tab, expectedSearchUrl } = await searchWithTab(SEARCH_STRING);
+ synthesizeRevert();
+
+ Assert.equal(
+ gURLBar.value,
+ expectedSearchUrl,
+ `Urlbar should have the reverted URI ${expectedSearchUrl} as its value.`
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Users should be able to revert the URL bar,
+// and go to the same page.
+add_task(async function revert_and_press_enter() {
+ let { tab, expectedSearchUrl } = await searchWithTab(SEARCH_STRING);
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedSearchUrl
+ );
+
+ synthesizeRevert();
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_Enter");
+ await browserLoadedPromise;
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Users should be able to revert the URL, and then if they navigate
+// to another tab, the tab that was reverted will show the search term again.
+add_task(async function revert_and_change_tab() {
+ let { tab, expectedSearchUrl } = await searchWithTab(SEARCH_STRING);
+
+ synthesizeRevert();
+
+ Assert.notEqual(
+ gURLBar.value,
+ SEARCH_STRING,
+ `Search string ${SEARCH_STRING} should not be in the url bar`
+ );
+ Assert.equal(
+ gURLBar.value,
+ expectedSearchUrl,
+ `Urlbar should have ${expectedSearchUrl} as value.`
+ );
+
+ // Open another tab
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // Switch back to the original tab.
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+
+ // Because the urlbar is focused, the pageproxystate should be invalid.
+ assertSearchStringIsInUrlbar(SEARCH_STRING, { pageProxyState: "invalid" });
+
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+// If a user reverts a tab, and then does another search,
+// they should be able to see the search term again.
+add_task(async function revert_and_search_again() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+ synthesizeRevert();
+ await searchWithTab("another search string", tab);
+ BrowserTestUtils.removeTab(tab);
+});
+
+// If a user reverts the Urlbar while on a default SERP,
+// and they navigate away from the page by visiting another
+// link or using the back/forward buttons, the Urlbar should
+// show the search term again when returning back to the default SERP.
+add_task(async function revert_when_using_content() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+ synthesizeRevert();
+ await searchWithTab("another search string", tab);
+
+ // Revert the page, and then go back and forth in history.
+ // The search terms should show up.
+ synthesizeRevert();
+ let pageShowPromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "pageshow"
+ );
+ tab.linkedBrowser.goBack();
+ await pageShowPromise;
+ assertSearchStringIsInUrlbar(SEARCH_STRING);
+
+ pageShowPromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "pageshow"
+ );
+ tab.linkedBrowser.goForward();
+ await pageShowPromise;
+ assertSearchStringIsInUrlbar("another search string");
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_searchBar.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_searchBar.js
new file mode 100644
index 0000000000..784d8932ac
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_searchBar.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/. */
+
+// These tests check the behavior of the Urlbar when a user enables
+// the search bar and showSearchTerms is true.
+
+const { CustomizableUITestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CustomizableUITestUtils.sys.mjs"
+);
+
+const gCUITestUtils = new CustomizableUITestUtils(window);
+const SEARCH_STRING = "example_string";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.widget.inNavBar", true],
+ ["browser.urlbar.showSearchTerms.featureGate", true],
+ ],
+ });
+
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: "MozSearch",
+ search_url: "https://www.example.com/",
+ search_url_get_params: "q={searchTerms}&pc=fake_code",
+ },
+ { setAsDefault: true }
+ );
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ gCUITestUtils.removeSearchBar();
+ });
+});
+
+function assertSearchStringIsNotInUrlbar(searchString) {
+ Assert.notEqual(
+ gURLBar.value,
+ searchString,
+ `Search string ${searchString} should not be in the url bar.`
+ );
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "valid",
+ "Pageproxystate should be valid."
+ );
+ Assert.equal(
+ gBrowser.selectedBrowser.searchTerms,
+ "",
+ "searchTerms should be blank."
+ );
+}
+
+// When a user enables the search bar, and does a search in the search bar,
+// the search term should not show in the URL bar.
+add_task(async function search_bar_on() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await gCUITestUtils.addSearchBar();
+
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ `https://www.example.com/?q=${SEARCH_STRING}&pc=fake_code`
+ );
+
+ let searchBar = BrowserSearch.searchBar;
+ searchBar.value = SEARCH_STRING;
+ searchBar.focus();
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ await browserLoadedPromise;
+ assertSearchStringIsNotInUrlbar(SEARCH_STRING);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// When a user enables the search bar, and does a search in the URL bar,
+// the search term should still not show in the URL bar.
+add_task(async function search_bar_on_with_url_bar_search() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await gCUITestUtils.addSearchBar();
+
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ `https://www.example.com/?q=${SEARCH_STRING}&pc=fake_code`
+ );
+
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: SEARCH_STRING,
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ await browserLoadedPromise;
+ assertSearchStringIsNotInUrlbar(SEARCH_STRING);
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_searchMode.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_searchMode.js
new file mode 100644
index 0000000000..880b597784
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_searchMode.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/. */
+
+// These tests check the behavior of the Urlbar when using search mode
+
+let defaultTestEngine;
+
+// The main search string used in tests
+const SEARCH_STRING = "chocolate cake";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", true]],
+ });
+
+ await SearchTestUtils.installSearchExtension({
+ name: "MozSearch",
+ search_url: "https://www.example.com/",
+ search_url_get_params: "q={searchTerms}&pc=fake_code",
+ });
+ defaultTestEngine = Services.search.getEngineByName("MozSearch");
+
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: "MochiSearch",
+ search_url: "https://mochi.test:8888/",
+ search_url_get_params: "q={searchTerms}&pc=fake_code",
+ },
+ { setAsDefault: true }
+ );
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+// When a user does a search with search mode, they should
+// not see the search term in the URL bar for that engine.
+add_task(async function non_default_search() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(
+ defaultTestEngine,
+ SEARCH_STRING
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedSearchUrl
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: SEARCH_STRING,
+ });
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: defaultTestEngine.name,
+ });
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_Enter");
+ await browserLoadedPromise;
+
+ Assert.equal(gURLBar.value, expectedSearchUrl, `URL should be in URL bar`);
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "valid",
+ "Pageproxystate should be valid"
+ );
+ Assert.equal(
+ gBrowser.userTypedValue,
+ null,
+ "There should not be a userTypedValue for a search on a non-default search engine"
+ );
+ Assert.equal(
+ gBrowser.selectedBrowser.searchTerms,
+ "",
+ "searchTerms should be empty."
+ );
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_switch_tab.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_switch_tab.js
new file mode 100644
index 0000000000..77ee3e19d7
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_switch_tab.js
@@ -0,0 +1,139 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// These tests check the behavior of the Urlbar when search terms are shown
+// and the user switches between tabs.
+
+let defaultTestEngine;
+
+// The main search keyword used in tests
+const SEARCH_STRING = "chocolate cake";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", true]],
+ });
+
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: "MozSearch",
+ search_url: "https://www.example.com/",
+ search_url_get_params: "q={searchTerms}&pc=fake_code",
+ },
+ { setAsDefault: true }
+ );
+ defaultTestEngine = Services.search.getEngineByName("MozSearch");
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+async function searchWithTab(
+ searchString,
+ tab = null,
+ engine = defaultTestEngine
+) {
+ if (!tab) {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ }
+
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(engine, searchString);
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedSearchUrl
+ );
+
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+ await browserLoadedPromise;
+
+ assertSearchStringIsInUrlbar(searchString);
+
+ return { tab, expectedSearchUrl };
+}
+
+// Users should be able to search, change the tab, and come
+// back to the original tab to see the search term again
+add_task(async function change_tab() {
+ let { tab: tab1 } = await searchWithTab(SEARCH_STRING);
+ let { tab: tab2 } = await searchWithTab("another keyword");
+ let { tab: tab3 } = await searchWithTab("yet another keyword");
+
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+ assertSearchStringIsInUrlbar(SEARCH_STRING);
+
+ await BrowserTestUtils.switchTab(gBrowser, tab2);
+ assertSearchStringIsInUrlbar("another keyword");
+
+ await BrowserTestUtils.switchTab(gBrowser, tab3);
+ assertSearchStringIsInUrlbar("yet another keyword");
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+ BrowserTestUtils.removeTab(tab3);
+});
+
+// If a user types in the URL bar, and the user goes to a
+// different tab, the original tab should still contain the
+// text written by the user.
+add_task(async function user_overwrites_search_term() {
+ let { tab: tab1 } = await searchWithTab(SEARCH_STRING);
+
+ gURLBar.focus();
+ gURLBar.select();
+ EventUtils.sendString("another_word");
+
+ Assert.notEqual(
+ gURLBar.value,
+ SEARCH_STRING,
+ `Search string ${SEARCH_STRING} should not be in the url bar`
+ );
+
+ // Open a new tab, switch back to the first and
+ // check that the user typed value is still there.
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+
+ Assert.equal(
+ gURLBar.value,
+ "another_word",
+ "another_word should be in the url bar"
+ );
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+// If a user clears the URL bar, and goes to a different tab,
+// and returns to the initial tab, it should show the search term again.
+add_task(async function user_overwrites_search_term() {
+ let { tab: tab1 } = await searchWithTab(SEARCH_STRING);
+
+ gURLBar.focus();
+ gURLBar.select();
+ EventUtils.sendKey("delete");
+
+ Assert.equal(gURLBar.value, "", "Empty string should be in url bar.");
+
+ // Open a new tab, switch back to the first and check
+ // the blank string is replaced with the search string.
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+
+ assertSearchStringIsInUrlbar(SEARCH_STRING, {
+ pageProxyState: "invalid",
+ userTypedValue: "",
+ });
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_telemetry.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_telemetry.js
new file mode 100644
index 0000000000..bdad68e0ef
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_searchTerms_telemetry.js
@@ -0,0 +1,378 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * These tests check that we record the number of times search terms
+ * persist in the Urlbar, and when search terms are cleared due to a
+ * PopupNotification.
+ *
+ * This is different from existing telemetry that tracks whether users
+ * interacted with the Urlbar or made another search while the search
+ * terms were peristed.
+ */
+
+let defaultTestEngine;
+
+// The main search string used in tests
+const SEARCH_STRING = "chocolate cake";
+
+// Telemetry keys.
+const PERSISTED_VIEWED = "urlbar.persistedsearchterms.view_count";
+const PERSISTED_REVERTED = "urlbar.persistedsearchterms.revert_by_popup_count";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", true]],
+ });
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: "MozSearch",
+ search_url: "https://www.example.com/",
+ search_url_get_params: "q={searchTerms}&pc=fake_code",
+ },
+ { setAsDefault: true }
+ );
+ defaultTestEngine = Services.search.getEngineByName("MozSearch");
+ Services.telemetry.clearScalars();
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ Services.telemetry.clearScalars();
+ });
+});
+
+// Starts a search with a tab and asserts that
+// the state of the Urlbar contains the search term.
+async function searchWithTab(
+ searchString,
+ tab = null,
+ engine = defaultTestEngine,
+ assertSearchString = true
+) {
+ if (!tab) {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ }
+
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(engine, searchString);
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedSearchUrl
+ );
+
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+ await browserLoadedPromise;
+
+ if (assertSearchString) {
+ assertSearchStringIsInUrlbar(searchString);
+ }
+
+ return { tab, expectedSearchUrl };
+}
+
+add_task(async function load_page_with_persisted_search() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 1);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function load_page_without_persisted_search() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", false]],
+ });
+
+ let { tab } = await searchWithTab(
+ SEARCH_STRING,
+ null,
+ defaultTestEngine,
+ false
+ );
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, undefined);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// Multiple searches should result in the correct number of recorded views.
+add_task(async function load_page_n_times() {
+ let N = 5;
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ for (let index = 0; index < N; ++index) {
+ await searchWithTab(SEARCH_STRING, tab);
+ }
+
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, N);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+});
+
+// A persisted search view event should not be recorded when unfocusing the urlbar.
+add_task(async function focus_and_unfocus() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, false);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 1);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ gURLBar.focus();
+ gURLBar.select();
+ gURLBar.blur();
+
+ // Focusing and unfocusing the urlbar shouldn't change the persisted view count.
+ scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 1);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+});
+
+// A persisted search view event should not be recorded by a
+// pushState event after a page has been loaded.
+add_task(async function history_api() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, false);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 1);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ let url = new URL(content.window.location);
+ let someState = { value: true };
+ url.searchParams.set("pc", "fake_code_2");
+ content.history.pushState(someState, "", url);
+ someState.value = false;
+ content.history.replaceState(someState, "", url);
+ });
+
+ scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 1);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+});
+
+// A persisted search view event should be recorded when switching back to a tab
+// that contains search terms.
+add_task(async function switch_tabs() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, false);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 1);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+
+ scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 2);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+// A telemetry event should be recorded when returning to a persisted SERP via tabhistory.
+add_task(async function tabhistory() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, false);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 1);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ BrowserTestUtils.loadURIString(
+ tab.linkedBrowser,
+ "https://www.example.com/some_url"
+ );
+ await browserLoadedPromise;
+
+ let pageShowPromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "pageshow"
+ );
+ tab.linkedBrowser.goBack();
+ await pageShowPromise;
+
+ scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 2);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+});
+
+// PopupNotification's that rely on an anchor element in the urlbar should trigger
+// an increment of the revert counter.
+// This assumes the anchor element is present in the Urlbar during a valid pageproxystate.
+add_task(async function popup_in_urlbar() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+ let promisePopupShown = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+ PopupNotifications.show(
+ gBrowser.selectedBrowser,
+ "test-notification",
+ "This is a sample popup."
+ );
+ await promisePopupShown;
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 1);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, 1);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Non-persistent PopupNotifications won't re-appear if a user switches
+// tabs and returns to the tab that had the Popup.
+add_task(async function non_persistent_popup_in_urlbar_switch_tab() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+ let promisePopupShown = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+ PopupNotifications.show(
+ gBrowser.selectedBrowser,
+ "test-notification",
+ "This is a sample popup.",
+ "geo-notification-icon"
+ );
+ await promisePopupShown;
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 1);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, 1);
+
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+
+ scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 2);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, 1);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+// Persistent PopupNotifications will constantly appear to users
+// even if they switch to another tab and switch back.
+add_task(async function persistent_popup_in_urlbar_switch_tab() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+ let promisePopupShown = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+ PopupNotifications.show(
+ gBrowser.selectedBrowser,
+ "test-notification",
+ "This is a sample popup.",
+ "geo-notification-icon",
+ null,
+ null,
+ { persistent: true }
+ );
+ await promisePopupShown;
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 1);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, 1);
+
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ promisePopupShown = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+ await promisePopupShown;
+
+ scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 2);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, 2);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+// If the persist feature is not enabled, a telemetry event should not be recorded
+// if a PopupNotification uses an anchor in the Urlbar.
+add_task(async function popup_in_urlbar_without_feature() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", false]],
+ });
+
+ let { tab } = await searchWithTab(
+ SEARCH_STRING,
+ null,
+ defaultTestEngine,
+ false
+ );
+ let promisePopupShown = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+ PopupNotifications.show(
+ gBrowser.selectedBrowser,
+ "test-notification",
+ "This is a sample popup."
+ );
+ await promisePopupShown;
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, undefined);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// If the anchor element for the PopupNotification is not located in the Urlbar,
+// a telemetry event should not be recorded.
+add_task(async function popup_not_in_urlbar() {
+ let { tab } = await searchWithTab(SEARCH_STRING);
+
+ let promisePopupShown = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+ PopupNotifications.show(
+ gBrowser.selectedBrowser,
+ "test-notification",
+ "This is a sample popup that uses the unified extensions button.",
+ gUnifiedExtensions.getPopupAnchorID(gBrowser.selectedBrowser, window)
+ );
+ await promisePopupShown;
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_VIEWED, 1);
+ TelemetryTestUtils.assertScalar(scalars, PERSISTED_REVERTED, undefined);
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_setURI.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_setURI.js
new file mode 100644
index 0000000000..0fa365f8bc
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_setURI.js
@@ -0,0 +1,128 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ // avoid prompting about phishing
+ Services.prefs.setIntPref(phishyUserPassPref, 32);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(phishyUserPassPref);
+ });
+
+ nextTest();
+}
+
+const phishyUserPassPref = "network.http.phishy-userpass-length";
+
+function nextTest() {
+ let testCase = tests.shift();
+ if (testCase) {
+ testCase(function () {
+ executeSoon(nextTest);
+ });
+ } else {
+ executeSoon(finish);
+ }
+}
+
+var tests = [
+ function revert(next) {
+ loadTabInWindow(window, function (tab) {
+ gURLBar.handleRevert();
+ is(
+ gURLBar.value,
+ "example.com",
+ "URL bar had user/pass stripped after reverting"
+ );
+ gBrowser.removeTab(tab);
+ next();
+ });
+ },
+ function customize(next) {
+ // Need to wait for delayedStartup for the customization part of the test,
+ // since that's where BrowserToolboxCustomizeDone is set.
+ BrowserTestUtils.openNewBrowserWindow().then(function (win) {
+ loadTabInWindow(win, function () {
+ openToolbarCustomizationUI(function () {
+ closeToolbarCustomizationUI(function () {
+ is(
+ win.gURLBar.value,
+ "example.com",
+ "URL bar had user/pass stripped after customize"
+ );
+ win.close();
+ next();
+ }, win);
+ }, win);
+ });
+ });
+ },
+ function pageloaderror(next) {
+ loadTabInWindow(window, function (tab) {
+ // Load a new URL and then immediately stop it, to simulate a page load
+ // error.
+ BrowserTestUtils.loadURIString(
+ tab.linkedBrowser,
+ "http://test1.example.com"
+ );
+ tab.linkedBrowser.stop();
+ is(
+ gURLBar.value,
+ "example.com",
+ "URL bar had user/pass stripped after load error"
+ );
+ gBrowser.removeTab(tab);
+ next();
+ });
+ },
+];
+
+function loadTabInWindow(win, callback) {
+ info("Loading tab");
+ let url = "http://user:pass@example.com/";
+ let tab = (win.gBrowser.selectedTab = BrowserTestUtils.addTab(
+ win.gBrowser,
+ url
+ ));
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url).then(() => {
+ info("Tab loaded");
+ is(
+ win.gURLBar.value,
+ "example.com",
+ "URL bar had user/pass stripped initially"
+ );
+ callback(tab);
+ }, true);
+}
+
+function openToolbarCustomizationUI(aCallback, aBrowserWin) {
+ if (!aBrowserWin) {
+ aBrowserWin = window;
+ }
+
+ aBrowserWin.gCustomizeMode.enter();
+
+ aBrowserWin.gNavToolbox.addEventListener(
+ "customizationready",
+ function () {
+ executeSoon(function () {
+ aCallback(aBrowserWin);
+ });
+ },
+ { once: true }
+ );
+}
+
+function closeToolbarCustomizationUI(aCallback, aBrowserWin) {
+ aBrowserWin.gNavToolbox.addEventListener(
+ "aftercustomization",
+ function () {
+ executeSoon(aCallback);
+ },
+ { once: true }
+ );
+
+ aBrowserWin.gCustomizeMode.exit();
+}
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_tooltip.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_tooltip.js
new file mode 100644
index 0000000000..5ecb5e7a90
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_tooltip.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function synthesizeMouseOver(element) {
+ info("synthesize mouseover");
+ let promise = BrowserTestUtils.waitForEvent(element, "mouseover");
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {
+ type: "mouseout",
+ });
+ EventUtils.synthesizeMouseAtCenter(element, { type: "mouseover" });
+ EventUtils.synthesizeMouseAtCenter(element, { type: "mousemove" });
+ return promise;
+}
+
+function synthesizeMouseOut(element) {
+ info("synthesize mouseout");
+ let promise = BrowserTestUtils.waitForEvent(element, "mouseout");
+ EventUtils.synthesizeMouseAtCenter(element, { type: "mouseover" });
+ EventUtils.synthesizeMouseAtCenter(element, { type: "mouseout" });
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {
+ type: "mousemove",
+ });
+ return promise;
+}
+
+async function expectTooltip(text) {
+ if (!gURLBar._overflowing && !gURLBar._inOverflow) {
+ info("waiting for overflow event");
+ await BrowserTestUtils.waitForEvent(gURLBar.inputField, "overflow");
+ }
+
+ let tooltip = document.getElementById("aHTMLTooltip");
+ let element = gURLBar.inputField;
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(tooltip, "popupshown");
+ await synthesizeMouseOver(element);
+ info("awaiting for tooltip popup");
+ await popupShownPromise;
+
+ is(element.getAttribute("title"), text, "title attribute has expected text");
+ is(tooltip.textContent, text, "tooltip shows expected text");
+
+ await synthesizeMouseOut(element);
+}
+
+async function expectNoTooltip() {
+ if (gURLBar._overflowing || gURLBar._inOverflow) {
+ info("waiting for underflow event");
+ await BrowserTestUtils.waitForEvent(gURLBar.inputField, "underflow");
+ }
+
+ let element = gURLBar.inputField;
+ await synthesizeMouseOver(element);
+
+ is(element.getAttribute("title"), null, "title attribute shouldn't be set");
+
+ await synthesizeMouseOut(element);
+}
+
+add_task(async function () {
+ window.windowUtils.disableNonTestMouseEvents(true);
+ registerCleanupFunction(() => {
+ window.windowUtils.disableNonTestMouseEvents(false);
+ });
+
+ // Ensure the URL bar is neither focused nor hovered before we start.
+ gBrowser.selectedBrowser.focus();
+ await synthesizeMouseOut(gURLBar.inputField);
+
+ gURLBar.value = "short string";
+ await expectNoTooltip();
+
+ let longURL = "http://longurl.com/" + "foobar/".repeat(30);
+ gURLBar.value = longURL;
+ is(
+ gURLBar.inputField.value,
+ longURL.replace(/^http:\/\//, ""),
+ "Urlbar value has http:// stripped"
+ );
+ await expectTooltip(longURL);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_trimURLs.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_trimURLs.js
new file mode 100644
index 0000000000..d1bd46f022
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_trimURLs.js
@@ -0,0 +1,127 @@
+add_task(async function () {
+ const PREF_TRIMURLS = "browser.urlbar.trimURLs";
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ registerCleanupFunction(async function () {
+ BrowserTestUtils.removeTab(tab);
+ Services.prefs.clearUserPref(PREF_TRIMURLS);
+ gURLBar.setURI();
+ });
+
+ // Avoid search service sync init warnings due to URIFixup, when running the
+ // test alone.
+ await Services.search.init();
+
+ Services.prefs.setBoolPref(PREF_TRIMURLS, true);
+
+ testVal("http://mozilla.org/", "mozilla.org");
+ testVal("https://mozilla.org/", "https://mozilla.org");
+ testVal("http://mözilla.org/", "mözilla.org");
+ // This isn't a valid public suffix, thus we should untrim it or it would
+ // end up doing a search.
+ testVal("http://mozilla.imaginatory/");
+ testVal("http://www.mozilla.org/", "www.mozilla.org");
+ testVal("http://sub.mozilla.org/", "sub.mozilla.org");
+ testVal("http://sub1.sub2.sub3.mozilla.org/", "sub1.sub2.sub3.mozilla.org");
+ testVal("http://mozilla.org/file.ext", "mozilla.org/file.ext");
+ testVal("http://mozilla.org/sub/", "mozilla.org/sub/");
+
+ testVal("http://ftp.mozilla.org/", "ftp.mozilla.org");
+ testVal("http://ftp1.mozilla.org/", "ftp1.mozilla.org");
+ testVal("http://ftp42.mozilla.org/", "ftp42.mozilla.org");
+ testVal("http://ftpx.mozilla.org/", "ftpx.mozilla.org");
+ testVal("ftp://ftp.mozilla.org/", "ftp://ftp.mozilla.org");
+ testVal("ftp://ftp1.mozilla.org/", "ftp://ftp1.mozilla.org");
+ testVal("ftp://ftp42.mozilla.org/", "ftp://ftp42.mozilla.org");
+ testVal("ftp://ftpx.mozilla.org/", "ftp://ftpx.mozilla.org");
+
+ testVal("https://user:pass@mozilla.org/", "https://user:pass@mozilla.org");
+ testVal("https://user@mozilla.org/", "https://user@mozilla.org");
+ testVal("http://user:pass@mozilla.org/", "user:pass@mozilla.org");
+ testVal("http://user@mozilla.org/", "user@mozilla.org");
+ testVal("http://sub.mozilla.org:666/", "sub.mozilla.org:666");
+
+ testVal("https://[fe80::222:19ff:fe11:8c76]/file.ext");
+ testVal("http://[fe80::222:19ff:fe11:8c76]/", "[fe80::222:19ff:fe11:8c76]");
+ testVal("https://user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext");
+ testVal(
+ "http://user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext",
+ "user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext"
+ );
+
+ testVal("mailto:admin@mozilla.org");
+ testVal("gopher://mozilla.org/");
+ testVal("about:config");
+ testVal("jar:http://mozilla.org/example.jar!/");
+ testVal("view-source:http://mozilla.org/");
+
+ // Behaviour for hosts with no dots depends on the whitelist:
+ let fixupWhitelistPref = "browser.fixup.domainwhitelist.localhost";
+ Services.prefs.setBoolPref(fixupWhitelistPref, false);
+ testVal("http://localhost");
+ Services.prefs.setBoolPref(fixupWhitelistPref, true);
+ testVal("http://localhost", "localhost");
+ Services.prefs.clearUserPref(fixupWhitelistPref);
+
+ testVal("http:// invalid url");
+
+ testVal("http://someotherhostwithnodots");
+
+ // This host is whitelisted, it can be trimmed.
+ testVal("http://localhost/ foo bar baz", "localhost/ foo bar baz");
+
+ // This is not trimmed because it's not in the domain whitelist.
+ testVal(
+ "http://localhost.localdomain/ foo bar baz",
+ "http://localhost.localdomain/ foo bar baz"
+ );
+
+ Services.prefs.setBoolPref(PREF_TRIMURLS, false);
+
+ testVal("http://mozilla.org/");
+
+ Services.prefs.setBoolPref(PREF_TRIMURLS, true);
+
+ let promiseLoaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ "http://example.com/"
+ );
+ BrowserTestUtils.loadURIString(gBrowser, "http://example.com/");
+ await promiseLoaded;
+
+ await testCopy("example.com", "http://example.com/");
+
+ gURLBar.setPageProxyState("invalid");
+ gURLBar.valueIsTyped = true;
+ await testCopy("example.com", "example.com");
+});
+
+function testVal(originalValue, targetValue) {
+ gURLBar.value = originalValue;
+ gURLBar.valueIsTyped = false;
+ let trimmedValue = UrlbarPrefs.get("trimURLs")
+ ? BrowserUIUtils.trimURL(originalValue)
+ : originalValue;
+ Assert.equal(gURLBar.value, trimmedValue, "url bar value set");
+ // Now focus the urlbar and check the inputField value is properly set.
+ gURLBar.focus();
+ Assert.equal(
+ gURLBar.value,
+ targetValue || originalValue,
+ "Check urlbar value on focus"
+ );
+ // On blur we should trim again.
+ gURLBar.blur();
+ Assert.equal(gURLBar.value, trimmedValue, "Check urlbar value on blur");
+}
+
+function testCopy(originalValue, targetValue) {
+ return SimpleTest.promiseClipboardChange(targetValue, () => {
+ Assert.equal(gURLBar.value, originalValue, "url bar copy value set");
+ gURLBar.focus();
+ gURLBar.select();
+ goDoCommand("cmd_copy");
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_aboutHomeLoading.js b/browser/components/urlbar/tests/browser/browser_aboutHomeLoading.js
new file mode 100644
index 0000000000..1bb65c0c42
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_aboutHomeLoading.js
@@ -0,0 +1,196 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests ensures the urlbar is cleared properly when about:home is visited.
+ */
+
+"use strict";
+
+const { SessionSaver } = ChromeUtils.importESModule(
+ "resource:///modules/sessionstore/SessionSaver.sys.mjs"
+);
+const { TabStateFlusher } = ChromeUtils.importESModule(
+ "resource:///modules/sessionstore/TabStateFlusher.sys.mjs"
+);
+
+add_task(function addHomeButton() {
+ CustomizableUI.addWidgetToArea("home-button", "nav-bar");
+ registerCleanupFunction(() =>
+ CustomizableUI.removeWidgetFromArea("home-button")
+ );
+});
+
+/**
+ * Test what happens if loading a URL that should clear the
+ * location bar after a parent process URL.
+ */
+add_task(async function clearURLBarAfterParentProcessURL() {
+ let tab = await new Promise(resolve => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "about:preferences"
+ );
+ let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ newTabBrowser.addEventListener(
+ "Initialized",
+ async function () {
+ resolve(gBrowser.selectedTab);
+ },
+ { capture: true, once: true }
+ );
+ });
+ document.getElementById("home-button").click();
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ HomePage.get()
+ );
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(
+ tab.linkedBrowser.userTypedValue,
+ null,
+ "The browser should have no recorded userTypedValue"
+ );
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Same as above, but open the tab without passing the URL immediately
+ * which changes behaviour in tabbrowser.xml.
+ */
+add_task(async function clearURLBarAfterParentProcessURLInExistingTab() {
+ let tab = await new Promise(resolve => {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+ let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ newTabBrowser.addEventListener(
+ "Initialized",
+ async function () {
+ resolve(gBrowser.selectedTab);
+ },
+ { capture: true, once: true }
+ );
+ BrowserTestUtils.loadURIString(newTabBrowser, "about:preferences");
+ });
+ document.getElementById("home-button").click();
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ HomePage.get()
+ );
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(
+ tab.linkedBrowser.userTypedValue,
+ null,
+ "The browser should have no recorded userTypedValue"
+ );
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Load about:home directly from an about:newtab page. Because it is an
+ * 'initial' page, we need to treat this specially if the user actually
+ * loads a page like this from the URL bar.
+ */
+add_task(async function clearURLBarAfterManuallyLoadingAboutHome() {
+ let promiseTabOpenedAndSwitchedTo = BrowserTestUtils.switchTab(
+ gBrowser,
+ () => {}
+ );
+ // This opens about:newtab:
+ BrowserOpenTab();
+ let tab = await promiseTabOpenedAndSwitchedTo;
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null");
+
+ gURLBar.value = "about:home";
+ gURLBar.select();
+ let aboutHomeLoaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ "about:home"
+ );
+ EventUtils.sendKey("return");
+ await aboutHomeLoaded;
+
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null");
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Ensure we don't show 'about:home' in the URL bar temporarily in new tabs
+ * while we're switching remoteness (when the URL we're loading and the
+ * default content principal are different).
+ */
+add_task(async function dontTemporarilyShowAboutHome() {
+ requestLongerTimeout(2);
+
+ await SpecialPowers.pushPrefEnv({ set: [["browser.startup.page", 1]] });
+ let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+ let win = OpenBrowserWindow();
+ await windowOpenedPromise;
+ let promiseTabSwitch = BrowserTestUtils.switchTab(win.gBrowser, () => {});
+ win.BrowserOpenTab();
+ await promiseTabSwitch;
+ is(win.gBrowser.visibleTabs.length, 2, "2 tabs opened");
+ await TabStateFlusher.flush(win.gBrowser.selectedBrowser);
+ await BrowserTestUtils.closeWindow(win);
+ ok(SessionStore.getClosedWindowCount(), "Should have a closed window");
+
+ await SessionSaver.run();
+
+ windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+ win = SessionStore.undoCloseWindow(0);
+ await windowOpenedPromise;
+ let wpl = {
+ onLocationChange() {
+ is(win.gURLBar.value, "", "URL bar value should stay empty.");
+ },
+ };
+ win.gBrowser.addProgressListener(wpl);
+
+ if (win.gBrowser.visibleTabs.length < 2) {
+ await BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ }
+ let otherTab = win.gBrowser.selectedTab.previousElementSibling;
+ let tabLoaded = BrowserTestUtils.browserLoaded(
+ otherTab.linkedBrowser,
+ false,
+ "about:home"
+ );
+ await BrowserTestUtils.switchTab(win.gBrowser, otherTab);
+ await tabLoaded;
+ win.gBrowser.removeProgressListener(wpl);
+ is(win.gURLBar.value, "", "URL bar value should be empty.");
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Test that if the Home Button is clicked after a user has typed
+ * some value into the URL bar, that the URL bar is cleared if
+ * the homepage is one of the initial pages set.
+ */
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ {
+ url: "http://example.com",
+ gBrowser,
+ },
+ async browser => {
+ const TYPED_VALUE = "This string should get cleared";
+ gURLBar.value = TYPED_VALUE;
+ browser.userTypedValue = TYPED_VALUE;
+
+ document.getElementById("home-button").click();
+ await BrowserTestUtils.browserLoaded(browser, false, HomePage.get());
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(
+ browser.userTypedValue,
+ null,
+ "The browser should have no recorded userTypedValue"
+ );
+ }
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_acknowledgeFeedbackAndDismissal.js b/browser/components/urlbar/tests/browser/browser_acknowledgeFeedbackAndDismissal.js
new file mode 100644
index 0000000000..cc1ed29ceb
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_acknowledgeFeedbackAndDismissal.js
@@ -0,0 +1,304 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests feedback and dismissal acknowledgments in the view.
+ */
+
+"use strict";
+
+// The name of this command must be one that's recognized as not ending the
+// urlbar session. See `isSessionOngoing` comments for details.
+const FEEDBACK_COMMAND = "show_less_frequently";
+
+let gTestProvider;
+
+add_setup(async function () {
+ gTestProvider = new TestProvider({
+ results: [
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ url: "https://example.com/",
+ isBlockable: true,
+ blockL10n: {
+ id: "urlbar-result-menu-dismiss-firefox-suggest",
+ },
+ }
+ ),
+ ],
+ });
+
+ gTestProvider.commandCount = {};
+ UrlbarProvidersManager.registerProvider(gTestProvider);
+
+ // Add a visit so that there's one result above the test result (the
+ // heuristic) and one below (the visit) just to make sure removing the test
+ // result doesn't mess up adjacent results.
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ await UrlbarTestUtils.formHistory.clear();
+ await PlacesTestUtils.addVisits("https://example.com/aaa");
+
+ registerCleanupFunction(() => {
+ UrlbarProvidersManager.unregisterProvider(gTestProvider);
+ });
+});
+
+// Tests dismissal acknowledgment when the dismissed row is not selected.
+add_task(async function acknowledgeDismissal_rowNotSelected() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await doDismissTest({ shouldBeSelected: false });
+});
+
+// Tests dismissal acknowledgment when the dismissed row is selected.
+add_task(async function acknowledgeDismissal_rowSelected() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+
+ // Select the row.
+ let resultIndex = await getTestResultIndex();
+ while (gURLBar.view.selectedRowIndex != resultIndex) {
+ this.EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+
+ await doDismissTest({ resultIndex, shouldBeSelected: true });
+});
+
+// Tests a feedback acknowledgment command immediately followed by a dismissal
+// acknowledgment command. This makes sure that both feedback acknowledgment
+// works and a subsequent dismissal command works while the urlbar session
+// remains ongoing.
+add_task(async function acknowledgeFeedbackAndDismissal() {
+ // Trigger the suggestion.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+
+ let resultIndex = await getTestResultIndex();
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, resultIndex);
+
+ // Click the feedback command.
+ await UrlbarTestUtils.openResultMenuAndClickItem(window, FEEDBACK_COMMAND, {
+ resultIndex,
+ });
+
+ Assert.equal(
+ gTestProvider.commandCount[FEEDBACK_COMMAND],
+ 1,
+ "One feedback command should have happened"
+ );
+ gTestProvider.commandCount[FEEDBACK_COMMAND] = 0;
+
+ Assert.ok(
+ gURLBar.view.isOpen,
+ "The view should remain open clicking the command"
+ );
+ Assert.ok(
+ details.element.row.hasAttribute("feedback-acknowledgment"),
+ "Row should have feedback acknowledgment after clicking command"
+ );
+
+ info("Doing dismissal");
+ await doDismissTest({ resultIndex, shouldBeSelected: true });
+});
+
+/**
+ * Does a dismissal test:
+ *
+ * 1. Clicks the dismiss command in the test result
+ * 2. Verifies a dismissal acknowledgment tip replaces the result
+ * 3. Clicks the "Got it" button in the tip
+ * 4. Verifies the tip is dismissed
+ *
+ * @param {object} options
+ * Options object
+ * @param {boolean} options.shouldBeSelected
+ * True if the test result is expected to be selected initially. If true, this
+ * function verifies the "Got it" button in the dismissal acknowledgment tip
+ * also becomes selected.
+ * @param {number} options.resultIndex
+ * The index of the test result, if known beforehand. Leave -1 to find it
+ * automatically.
+ */
+async function doDismissTest({ shouldBeSelected, resultIndex = -1 }) {
+ if (resultIndex < 0) {
+ resultIndex = await getTestResultIndex();
+ }
+
+ let selectedElement = gURLBar.view.selectedElement;
+ Assert.ok(selectedElement, "There should be an initially selected element");
+
+ if (shouldBeSelected) {
+ Assert.equal(
+ gURLBar.view.selectedRowIndex,
+ resultIndex,
+ "The test result should be selected"
+ );
+ } else {
+ Assert.notEqual(
+ gURLBar.view.selectedRowIndex,
+ resultIndex,
+ "The test result should not be selected"
+ );
+ }
+
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+
+ // Click the dismiss command.
+ await UrlbarTestUtils.openResultMenuAndClickItem(window, "dismiss", {
+ resultIndex,
+ openByMouse: true,
+ });
+
+ Assert.equal(
+ gTestProvider.commandCount.dismiss,
+ 1,
+ "One dismissal should have happened"
+ );
+ gTestProvider.commandCount.dismiss = 0;
+
+ // The row should be a tip now.
+ Assert.ok(gURLBar.view.isOpen, "The view should remain open after dismissal");
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ resultCount,
+ "The result count should not haved changed after dismissal"
+ );
+
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, resultIndex);
+ Assert.equal(
+ details.type,
+ UrlbarUtils.RESULT_TYPE.TIP,
+ "Row should be a tip after dismissal"
+ );
+ Assert.equal(
+ details.result.payload.type,
+ "dismissalAcknowledgment",
+ "Tip type should be dismissalAcknowledgment"
+ );
+ Assert.ok(
+ !details.element.row.hasAttribute("selected"),
+ "Row should not have 'selected' attribute"
+ );
+ Assert.ok(
+ !details.element.row._content.hasAttribute("selected"),
+ "Row-inner should not have 'selected' attribute"
+ );
+ Assert.ok(
+ !details.element.row.hasAttribute("feedback-acknowledgment"),
+ "Row should not have feedback acknowledgment after dismissal"
+ );
+
+ // Get the dismissal acknowledgment's "Got it" button.
+ let gotItButton = UrlbarTestUtils.getButtonForResultIndex(
+ window,
+ "0",
+ resultIndex
+ );
+ Assert.ok(gotItButton, "Row should have a 'Got it' button");
+
+ if (shouldBeSelected) {
+ Assert.equal(
+ gURLBar.view.selectedElement,
+ gotItButton,
+ "The 'Got it' button should be selected"
+ );
+ } else {
+ Assert.notEqual(
+ gURLBar.view.selectedElement,
+ gotItButton,
+ "The 'Got it' button should not be selected"
+ );
+ Assert.equal(
+ gURLBar.view.selectedElement,
+ selectedElement,
+ "The initially selected element should remain selected"
+ );
+ }
+
+ // Click it.
+ EventUtils.synthesizeMouseAtCenter(gotItButton, {}, window);
+
+ // The view should remain open and the tip row should be gone.
+ Assert.ok(
+ gURLBar.view.isOpen,
+ "The view should remain open clicking the 'Got it' button"
+ );
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ resultCount - 1,
+ "The result count should be one less after clicking 'Got it' button"
+ );
+ for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
+ details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.ok(
+ details.type != UrlbarUtils.RESULT_TYPE.TIP &&
+ details.result.providerName != gTestProvider.name,
+ "Tip result and test result should not be present"
+ );
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window);
+}
+
+/**
+ * A provider that acknowledges feedback and dismissals.
+ */
+class TestProvider extends UrlbarTestUtils.TestProvider {
+ getResultCommands(result) {
+ return [
+ {
+ name: FEEDBACK_COMMAND,
+ l10n: {
+ id: "firefox-suggest-weather-command-inaccurate-location",
+ },
+ },
+ {
+ name: "dismiss",
+ l10n: {
+ id: "firefox-suggest-weather-command-not-interested",
+ },
+ },
+ ];
+ }
+
+ onEngagement(isPrivate, state, queryContext, details) {
+ if (details.result?.providerName == this.name) {
+ let { selType } = details;
+
+ info(`onEngagement called, selType=` + selType);
+
+ if (!this.commandCount.hasOwnProperty(selType)) {
+ this.commandCount[selType] = 0;
+ }
+ this.commandCount[selType]++;
+
+ if (selType == FEEDBACK_COMMAND) {
+ queryContext.view.acknowledgeFeedback(details.result);
+ } else if (selType == "dismiss") {
+ queryContext.view.acknowledgeDismissal(details.result);
+ }
+ }
+ }
+}
+
+async function getTestResultIndex() {
+ let index = 0;
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+ for (; index < resultCount; index++) {
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ if (details.result.providerName == gTestProvider.name) {
+ break;
+ }
+ }
+ Assert.less(index, resultCount, "The test result should be present");
+ return index;
+}
diff --git a/browser/components/urlbar/tests/browser/browser_action_searchengine.js b/browser/components/urlbar/tests/browser/browser_action_searchengine.js
new file mode 100644
index 0000000000..2520315fa2
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_action_searchengine.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that a search result has the correct attributes and visits the
+ * expected URL for the engine.
+ */
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", true],
+ ["browser.search.separatePrivateDefault", false],
+ ],
+ });
+
+ await SearchTestUtils.installSearchExtension(
+ { name: "MozSearch" },
+ { setAsDefault: true }
+ );
+ await SearchTestUtils.installSearchExtension({
+ name: "MozSearchPrivate",
+ search_url: "https://example.com/private",
+ });
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ });
+});
+
+async function testSearch(win, expectedName, expectedBaseUrl) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "open a search",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(win, 0);
+
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Should have type search"
+ );
+ Assert.deepEqual(
+ result.searchParams,
+ {
+ engine: expectedName,
+ keyword: undefined,
+ query: "open a search",
+ suggestion: undefined,
+ inPrivateWindow: undefined,
+ isPrivateEngine: undefined,
+ },
+ "Should have the correct result parameters."
+ );
+
+ Assert.equal(
+ result.image,
+ UrlbarUtils.ICON.SEARCH_GLASS,
+ "Should have the search icon image"
+ );
+
+ let tabPromise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(win, 0);
+ EventUtils.synthesizeMouseAtCenter(element, {}, win);
+ await tabPromise;
+
+ Assert.equal(
+ win.gBrowser.selectedBrowser.currentURI.spec,
+ expectedBaseUrl + "?q=open+a+search",
+ "Should have loaded the correct page"
+ );
+}
+
+add_task(async function test_search_normal_window() {
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+
+ registerCleanupFunction(async function () {
+ try {
+ BrowserTestUtils.removeTab(tab);
+ } catch (ex) {
+ /* tab may have already been closed in case of failure */
+ }
+ });
+
+ await testSearch(window, "MozSearch", "https://example.com/");
+});
+
+add_task(async function test_search_private_window_no_separate_default() {
+ const win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+ registerCleanupFunction(async function () {
+ await BrowserTestUtils.closeWindow(win);
+ });
+
+ await testSearch(win, "MozSearch", "https://example.com/");
+});
+
+add_task(async function test_search_private_window() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.separatePrivateDefault", true]],
+ });
+
+ let engine = Services.search.getEngineByName("MozSearchPrivate");
+ let originalEngine = await Services.search.getDefaultPrivate();
+ await Services.search.setDefaultPrivate(
+ engine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ registerCleanupFunction(async () => {
+ await BrowserTestUtils.closeWindow(win);
+ await Services.search.setDefaultPrivate(
+ originalEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ });
+
+ const win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+ await testSearch(win, "MozSearchPrivate", "https://example.com/private");
+});
diff --git a/browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js b/browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js
new file mode 100644
index 0000000000..b79c324a04
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that search result obtained using a search keyword gives an entry with
+ * the correct attributes and visits the expected URL for the engine.
+ */
+
+add_task(async function () {
+ await SearchTestUtils.installSearchExtension(
+ { keyword: "moz" },
+ { setAsDefault: true }
+ );
+ let engine = Services.search.getEngineByName("Example");
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+
+ // Disable autofill so mozilla.org isn't autofilled below.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.autoFill", false]],
+ });
+
+ registerCleanupFunction(async function () {
+ try {
+ BrowserTestUtils.removeTab(tab);
+ } catch (ex) {
+ /* tab may have already been closed in case of failure */
+ }
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "moz",
+ });
+ Assert.equal(gURLBar.value, "moz", "Value should be unchanged");
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "moz open a search",
+ });
+ // Wait for the second new search that starts when search mode is entered.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: engine.name,
+ entry: "typed",
+ });
+ Assert.equal(gURLBar.value, "open a search", "value should be query");
+
+ let tabPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await tabPromise;
+
+ Assert.equal(
+ gBrowser.selectedBrowser.currentURI.spec,
+ "https://example.com/?q=open+a+search",
+ "Should have loaded the correct page"
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_add_search_engine.js b/browser/components/urlbar/tests/browser/browser_add_search_engine.js
new file mode 100644
index 0000000000..f016aab3e7
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_add_search_engine.js
@@ -0,0 +1,325 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test adding engines through the Address Bar context menu.
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+const BASE_URL =
+ "http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/";
+
+add_task(async function context_none() {
+ info("Checks the context menu with a page that doesn't offer any engines.");
+ let url = "http://mochi.test:8888/";
+ await BrowserTestUtils.withNewTab(url, async () => {
+ await UrlbarTestUtils.withContextMenu(window, popup => {
+ info("The separator and the add engine item should not be present.");
+ let elt = popup.parentNode.getMenuItem("add-engine-separator");
+ Assert.ok(!!elt);
+ Assert.ok(!BrowserTestUtils.is_visible(elt));
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-menu"));
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-0"));
+ });
+ });
+});
+
+add_task(async function context_one() {
+ info("Checks the context menu with a page that offers one engine.");
+ let url = getRootDirectory(gTestPath) + "add_search_engine_one.html";
+ await BrowserTestUtils.withNewTab(url, async () => {
+ await UrlbarTestUtils.withContextMenu(window, async popup => {
+ info("The separator and the add engine item should be present.");
+ let elt = popup.parentNode.getMenuItem("add-engine-separator");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-menu"));
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-1"));
+
+ elt = popup.parentNode.getMenuItem("add-engine-0");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+ await document.l10n.translateElements([elt]);
+ Assert.ok(elt.label.includes("add_search_engine_0"));
+ Assert.ok(elt.hasAttribute("image"));
+ Assert.equal(
+ elt.getAttribute("uri"),
+ BASE_URL + "add_search_engine_0.xml"
+ );
+
+ info("Click on the menuitem");
+ let enginePromise = promiseEngine("engine-added", "add_search_engine_0");
+ popup.activateItem(elt);
+ await enginePromise;
+ Assert.equal(popup.state, "closed");
+ });
+
+ await UrlbarTestUtils.withContextMenu(window, popup => {
+ info("The separator and the add engine item should not be present.");
+ let elt = popup.parentNode.getMenuItem("add-engine-separator");
+ Assert.ok(!BrowserTestUtils.is_visible(elt));
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-menu"));
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-0"));
+ });
+
+ info("Remove the engine.");
+ let engine = await Services.search.getEngineByName("add_search_engine_0");
+ await Services.search.removeEngine(engine);
+
+ await UrlbarTestUtils.withContextMenu(window, async popup => {
+ info("The separator and the add engine item should be present again.");
+ let elt = popup.parentNode.getMenuItem("add-engine-separator");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-menu"));
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-1"));
+
+ elt = popup.parentNode.getMenuItem("add-engine-0");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+ await document.l10n.translateElements([elt]);
+ Assert.ok(elt.label.includes("add_search_engine_0"));
+ });
+ });
+});
+
+add_task(async function context_invalid() {
+ info("Checks the context menu with a page that offers an invalid engine.");
+ await SpecialPowers.pushPrefEnv({
+ set: [["prompts.contentPromptSubDialog", false]],
+ });
+
+ let url = getRootDirectory(gTestPath) + "add_search_engine_invalid.html";
+ await BrowserTestUtils.withNewTab(url, async tab => {
+ await UrlbarTestUtils.withContextMenu(window, async popup => {
+ info("The separator and the add engine item should be present.");
+ Assert.ok(popup.parentNode.getMenuItem("add-engine-separator"));
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-menu"));
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-1"));
+
+ let elt = popup.parentNode.getMenuItem("add-engine-0");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+ await document.l10n.translateElements([elt]);
+ Assert.ok(elt.label.includes("add_search_engine_404"));
+ Assert.equal(
+ elt.getAttribute("uri"),
+ BASE_URL + "add_search_engine_404.xml"
+ );
+
+ info("Click on the menuitem");
+ let promptPromise = PromptTestUtils.waitForPrompt(tab.linkedBrowser, {
+ modalType: Ci.nsIPromptService.MODAL_TYPE_CONTENT,
+ promptType: "alert",
+ });
+
+ popup.activateItem(elt);
+
+ let prompt = await promptPromise;
+ Assert.ok(
+ prompt.ui.infoBody.textContent.includes(
+ BASE_URL + "add_search_engine_404.xml"
+ ),
+ "Should have included the url in the prompt body"
+ );
+ await PromptTestUtils.handlePrompt(prompt);
+ Assert.equal(popup.state, "closed");
+ });
+ });
+});
+
+add_task(async function context_same_name() {
+ info("Checks the context menu with a page that offers same named engines.");
+ let url = getRootDirectory(gTestPath) + "add_search_engine_same_names.html";
+ await BrowserTestUtils.withNewTab(url, async () => {
+ await UrlbarTestUtils.withContextMenu(window, async popup => {
+ info("The separator and the add engine item should be present.");
+ let elt = popup.parentNode.getMenuItem("add-engine-separator");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-menu"));
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-1"));
+
+ elt = popup.parentNode.getMenuItem("add-engine-0");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+ await document.l10n.translateElements([elt]);
+ Assert.ok(elt.label.includes("add_search_engine_0"));
+ });
+ });
+});
+
+add_task(async function context_two() {
+ info("Checks the context menu with a page that offers two engines.");
+ let url = getRootDirectory(gTestPath) + "add_search_engine_two.html";
+ await BrowserTestUtils.withNewTab(url, async () => {
+ await UrlbarTestUtils.withContextMenu(window, async popup => {
+ info("The separator and the add engine item should be present.");
+ let elt = popup.parentNode.getMenuItem("add-engine-separator");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-menu"));
+
+ elt = popup.parentNode.getMenuItem("add-engine-0");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+ await document.l10n.translateElements([elt]);
+ Assert.ok(elt.label.includes("add_search_engine_0"));
+ elt = popup.parentNode.getMenuItem("add-engine-1");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+ await document.l10n.translateElements([elt]);
+ Assert.ok(elt.label.includes("add_search_engine_1"));
+ });
+ });
+});
+
+add_task(async function context_many() {
+ info("Checks the context menu with a page that offers many engines.");
+ let url = getRootDirectory(gTestPath) + "add_search_engine_many.html";
+ await BrowserTestUtils.withNewTab(url, async () => {
+ await UrlbarTestUtils.withContextMenu(window, async popup => {
+ info("The separator and the add engine menu should be present.");
+ let separator = popup.parentNode.getMenuItem("add-engine-separator");
+ Assert.ok(BrowserTestUtils.is_visible(separator));
+
+ info("Engines should appear in sub menu");
+ let menu = popup.parentNode.getMenuItem("add-engine-menu");
+ Assert.ok(BrowserTestUtils.is_visible(menu));
+ Assert.ok(
+ !menu.nextElementSibling
+ ?.getAttribute("anonid")
+ .startsWith("add-engine")
+ );
+ Assert.ok(menu.hasAttribute("image"), "Menu should have an icon");
+ Assert.ok(
+ !menu.label.includes("add-engine"),
+ "Menu should not contain an engine name"
+ );
+
+ info("Open the submenu");
+ let popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ menu.openMenu(true);
+ await popupShown;
+ for (let i = 0; i < 4; ++i) {
+ let elt = popup.parentNode.getMenuItem(`add-engine-${i}`);
+ Assert.equal(elt.parentNode, menu.menupopup);
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+ }
+
+ info("Click on the first engine to install it");
+ let enginePromise = promiseEngine("engine-added", "add_search_engine_0");
+ let elt = popup.parentNode.getMenuItem("add-engine-0");
+
+ elt.closest("menupopup").activateItem(elt);
+ await enginePromise;
+ Assert.equal(popup.state, "closed");
+ });
+
+ await UrlbarTestUtils.withContextMenu(window, async popup => {
+ info("Check the installed engine has been removed");
+ // We're below the limit of engines for the menu now.
+ Assert.ok(!!popup.parentNode.getMenuItem("add-engine-separator"));
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-menu"));
+
+ for (let i = 0; i < 3; ++i) {
+ let elt = popup.parentNode.getMenuItem(`add-engine-${i}`);
+ Assert.equal(elt.parentNode, popup);
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+ await document.l10n.translateElements([elt]);
+ Assert.ok(elt.label.includes(`add_search_engine_${i + 1}`));
+ }
+ });
+
+ info("Remove the engine.");
+ let engine = await Services.search.getEngineByName("add_search_engine_0");
+ await Services.search.removeEngine(engine);
+
+ await UrlbarTestUtils.withContextMenu(window, async popup => {
+ info("The separator and the add engine menu should be present.");
+ let separator = popup.parentNode.getMenuItem("add-engine-separator");
+ Assert.ok(BrowserTestUtils.is_visible(separator));
+
+ info("Engines should appear in sub menu");
+ let menu = popup.parentNode.getMenuItem("add-engine-menu");
+ Assert.ok(BrowserTestUtils.is_visible(menu));
+ Assert.ok(
+ !menu.nextElementSibling
+ ?.getAttribute("anonid")
+ .startsWith("add-engine")
+ );
+
+ info("Open the submenu");
+ let popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ menu.openMenu(true);
+ await popupShown;
+ for (let i = 0; i < 4; ++i) {
+ let elt = popup.parentNode.getMenuItem(`add-engine-${i}`);
+ Assert.equal(elt.parentNode, menu.menupopup);
+ if (
+ AppConstants.platform != "macosx" ||
+ !Services.prefs.getBoolPref(
+ "widget.macos.native-context-menus",
+ false
+ )
+ ) {
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+ }
+ }
+ });
+ });
+});
+
+add_task(async function context_after_customize() {
+ info("Checks the context menu after customization.");
+ let url = getRootDirectory(gTestPath) + "add_search_engine_one.html";
+ await BrowserTestUtils.withNewTab(url, async () => {
+ await UrlbarTestUtils.withContextMenu(window, async popup => {
+ info("The separator and the add engine item should be present.");
+ let elt = popup.parentNode.getMenuItem("add-engine-separator");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-menu"));
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-1"));
+
+ elt = popup.parentNode.getMenuItem("add-engine-0");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+ await document.l10n.translateElements([elt]);
+ Assert.ok(elt.label.includes("add_search_engine_0"));
+ });
+
+ let promise = BrowserTestUtils.waitForEvent(
+ gNavToolbox,
+ "customizationready"
+ );
+ gCustomizeMode.enter();
+ await promise;
+ promise = BrowserTestUtils.waitForEvent(gNavToolbox, "aftercustomization");
+ gCustomizeMode.exit();
+ await promise;
+
+ await UrlbarTestUtils.withContextMenu(window, async popup => {
+ info("The separator and the add engine item should be present.");
+ let elt = popup.parentNode.getMenuItem("add-engine-separator");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-menu"));
+ Assert.ok(!popup.parentNode.getMenuItem("add-engine-1"));
+
+ elt = popup.parentNode.getMenuItem("add-engine-0");
+ Assert.ok(BrowserTestUtils.is_visible(elt));
+ await document.l10n.translateElements([elt]);
+ Assert.ok(elt.label.includes("add_search_engine_0"));
+ });
+ });
+});
+
+function promiseEngine(expectedData, expectedEngineName) {
+ info(`Waiting for engine ${expectedData}`);
+ return TestUtils.topicObserved(
+ "browser-search-engine-modified",
+ (engine, data) => {
+ info(`Got engine ${engine.wrappedJSObject.name} ${data}`);
+ return (
+ expectedData == data &&
+ expectedEngineName == engine.wrappedJSObject.name
+ );
+ }
+ ).then(([engine, data]) => engine);
+}
diff --git a/browser/components/urlbar/tests/browser/browser_autoFill_backspaced.js b/browser/components/urlbar/tests/browser/browser_autoFill_backspaced.js
new file mode 100644
index 0000000000..65f533c0fe
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_backspaced.js
@@ -0,0 +1,268 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This test ensures that backspacing autoFilled values still allows to
+ * confirm the remaining value.
+ */
+
+"use strict";
+
+async function test_autocomplete(data) {
+ let { desc, typed, autofilled, modified, keys, type, onAutoFill } = data;
+ info(desc);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typed,
+ fireInputEvent: true,
+ });
+ Assert.equal(gURLBar.value, autofilled, "autofilled value is as expected");
+ if (onAutoFill) {
+ onAutoFill();
+ }
+
+ info("Synthesizing keys");
+ for (let key of keys) {
+ let args = Array.isArray(key) ? key : [key];
+ EventUtils.synthesizeKey(...args);
+ }
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ Assert.equal(gURLBar.value, modified, "backspaced value is as expected");
+
+ Assert.greater(
+ UrlbarTestUtils.getResultCount(window),
+ 0,
+ "Should get at least 1 result"
+ );
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+
+ Assert.equal(result.type, type, "Should have the correct result type");
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ gURLBar.blur();
+}
+
+add_task(async function () {
+ registerCleanupFunction(async function () {
+ Services.prefs.clearUserPref("browser.urlbar.autoFill");
+ Services.prefs.clearUserPref("browser.urlbar.suggest.quickactions");
+ gURLBar.handleRevert();
+ await PlacesUtils.history.clear();
+ });
+ Services.prefs.setBoolPref("browser.urlbar.autoFill", true);
+ Services.prefs.setBoolPref("browser.urlbar.suggest.quickactions", false);
+
+ await PlacesTestUtils.addVisits([
+ "http://example.com/",
+ "http://example.com/foo",
+ ]);
+ // Bookmark the page so it ignores autofill threshold and doesn't risk to
+ // not be autofilled.
+ let bm = await PlacesUtils.bookmarks.insert({
+ url: "http://example.com/",
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ });
+ registerCleanupFunction(async function () {
+ await PlacesUtils.bookmarks.remove(bm);
+ });
+
+ await test_autocomplete({
+ desc: "DELETE the autofilled part should search",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "exam",
+ keys: ["KEY_Delete"],
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ });
+ await test_autocomplete({
+ desc: "DELETE the final slash should visit",
+ typed: "example.com",
+ autofilled: "example.com/",
+ modified: "example.com",
+ keys: ["KEY_Delete"],
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ });
+
+ await test_autocomplete({
+ desc: "BACK_SPACE the autofilled part should search",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "exam",
+ keys: ["KEY_Backspace"],
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ });
+ await test_autocomplete({
+ desc: "BACK_SPACE the final slash should visit",
+ typed: "example.com",
+ autofilled: "example.com/",
+ modified: "example.com",
+ keys: ["KEY_Backspace"],
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ });
+
+ await test_autocomplete({
+ desc: "DELETE the autofilled part, then BACK_SPACE, should search",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "exa",
+ keys: ["KEY_Delete", "KEY_Backspace"],
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ });
+ await test_autocomplete({
+ desc: "DELETE the final slash, then BACK_SPACE, should search",
+ typed: "example.com",
+ autofilled: "example.com/",
+ modified: "example.co",
+ keys: ["KEY_Delete", "KEY_Backspace"],
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ });
+
+ await test_autocomplete({
+ desc: "BACK_SPACE the autofilled part, then BACK_SPACE, should search",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "exa",
+ keys: ["KEY_Backspace", "KEY_Backspace"],
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ });
+ await test_autocomplete({
+ desc: "BACK_SPACE the final slash, then BACK_SPACE, should search",
+ typed: "example.com",
+ autofilled: "example.com/",
+ modified: "example.co",
+ keys: ["KEY_Backspace", "KEY_Backspace"],
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ });
+
+ await test_autocomplete({
+ desc: "BACK_SPACE after blur should search",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "e",
+ keys: ["KEY_Backspace"],
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ onAutoFill: () => {
+ gURLBar.blur();
+ gURLBar.focus();
+ Assert.equal(
+ gURLBar.selectionStart,
+ gURLBar.value.length,
+ "blur/focus should not change selection"
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ gURLBar.value.length,
+ "blur/focus should not change selection"
+ );
+ },
+ });
+ await test_autocomplete({
+ desc: "DELETE after blur should search",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "e",
+ keys: ["KEY_ArrowLeft", "KEY_Delete"],
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ onAutoFill: () => {
+ gURLBar.blur();
+ gURLBar.focus();
+ Assert.equal(
+ gURLBar.selectionStart,
+ gURLBar.value.length,
+ "blur/focus should not change selection"
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ gURLBar.value.length,
+ "blur/focus should not change selection"
+ );
+ },
+ });
+ await test_autocomplete({
+ desc: "double BACK_SPACE after blur should search",
+ typed: "exa",
+ autofilled: "example.com/",
+ modified: "e",
+ keys: ["KEY_Backspace", "KEY_Backspace"],
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ onAutoFill: () => {
+ gURLBar.blur();
+ gURLBar.focus();
+ Assert.equal(
+ gURLBar.selectionStart,
+ gURLBar.value.length,
+ "blur/focus should not change selection"
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ gURLBar.value.length,
+ "blur/focus should not change selection"
+ );
+ },
+ });
+
+ await test_autocomplete({
+ desc: "Right arrow key and then backspace should delete the backslash and not re-trigger autofill",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "example.com",
+ keys: ["KEY_ArrowRight", "KEY_Backspace"],
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ });
+
+ await test_autocomplete({
+ desc: "Right arrow key, selecting the last few characters using the keyboard, and then backspace should delete the characters and not re-trigger autofill",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "example.c",
+ keys: [
+ "KEY_ArrowRight",
+ ["KEY_ArrowLeft", { shiftKey: true }],
+ ["KEY_ArrowLeft", { shiftKey: true }],
+ ["KEY_ArrowLeft", { shiftKey: true }],
+ "KEY_Backspace",
+ ],
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ });
+
+ await test_autocomplete({
+ desc: "End and then backspace should delete the backslash and not re-trigger autofill",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "example.com",
+ keys: [
+ AppConstants.platform == "macosx"
+ ? ["KEY_ArrowRight", { metaKey: true }]
+ : "KEY_End",
+ "KEY_Backspace",
+ ],
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ });
+
+ await test_autocomplete({
+ desc: "Clicking in the input after the text and then backspace should delete the backslash and not re-trigger autofill",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "example.com",
+ keys: ["KEY_Backspace"],
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ onAutoFill: () => {
+ // This assumes that the center of the input is to the right of the end
+ // of the text, so the caret is placed at the end of the text on click.
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ },
+ });
+
+ await test_autocomplete({
+ desc: "Selecting the next result and then backspace should delete the last character and not re-trigger autofill",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "example.com/fo",
+ keys: ["KEY_ArrowDown", "KEY_Backspace"],
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ });
+
+ await PlacesUtils.history.clear();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autoFill_canonize.js b/browser/components/urlbar/tests/browser/browser_autoFill_canonize.js
new file mode 100644
index 0000000000..af6a2eb08b
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_canonize.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This test ensures that pressing ctrl+enter bypasses the autoFilled
+ * value, and only considers what the user typed (but not just enter).
+ */
+
+async function test_autocomplete(data) {
+ let { desc, typed, autofilled, modified, waitForUrl, keys } = data;
+ info(desc);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typed,
+ });
+ Assert.equal(gURLBar.value, autofilled, "autofilled value is as expected");
+
+ let promiseLoad = BrowserTestUtils.waitForDocLoadAndStopIt(
+ waitForUrl,
+ gBrowser.selectedBrowser
+ );
+
+ keys.forEach(([key, mods]) => EventUtils.synthesizeKey(key, mods));
+
+ Assert.equal(gURLBar.value, modified, "value is as expected");
+
+ await promiseLoad;
+ gURLBar.blur();
+}
+
+add_task(async function () {
+ registerCleanupFunction(async function () {
+ Services.prefs.clearUserPref("browser.urlbar.autoFill");
+ gURLBar.handleRevert();
+ await PlacesUtils.history.clear();
+ });
+ Services.prefs.setBoolPref("browser.urlbar.autoFill", true);
+
+ // Add a typed visit, so it will be autofilled.
+ await PlacesTestUtils.addVisits({
+ uri: "http://example.com/",
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ });
+
+ await test_autocomplete({
+ desc: "CTRL+ENTER on the autofilled part should use autofill",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "example.com",
+ waitForUrl: "http://example.com/",
+ keys: [["KEY_Enter"]],
+ });
+
+ await test_autocomplete({
+ desc: "CTRL+ENTER on the autofilled part should bypass autofill",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "https://www.exam.com",
+ waitForUrl: "https://www.exam.com/",
+ keys: [["KEY_Enter", { ctrlKey: true }]],
+ });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autoFill_caretNotAtEnd.js b/browser/components/urlbar/tests/browser/browser_autoFill_caretNotAtEnd.js
new file mode 100644
index 0000000000..570a1c2c8c
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_caretNotAtEnd.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function noAutofillWhenCaretNotAtEnd() {
+ gURLBar.focus();
+
+ // Add a visit that can be autofilled.
+ await PlacesUtils.history.clear();
+ await PlacesTestUtils.addVisits([
+ {
+ uri: "http://example.com/",
+ },
+ ]);
+
+ // Fill the input with xample.
+ gURLBar.inputField.value = "xample";
+
+ // Move the caret to the beginning and type e.
+ gURLBar.selectionStart = 0;
+ gURLBar.selectionEnd = 0;
+ EventUtils.sendString("e");
+
+ // Check the first result and input.
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(!result.autofill, "The first result should not be autofill");
+
+ Assert.equal(gURLBar.value, "example");
+ Assert.equal(gURLBar.selectionStart, 1);
+ Assert.equal(gURLBar.selectionEnd, 1);
+
+ await PlacesUtils.history.clear();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autoFill_firstResult.js b/browser/components/urlbar/tests/browser/browser_autoFill_firstResult.js
new file mode 100644
index 0000000000..b3fb932c4c
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_firstResult.js
@@ -0,0 +1,200 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test makes sure that autofilling the first result of a new search works
+// correctly: autofill happens when it should and doesn't when it shouldn't.
+
+"use strict";
+
+add_setup(async function () {
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+ await PlacesTestUtils.addVisits(["http://example.com/"]);
+
+ // Disable placeholder completion. The point of this test is to make sure the
+ // first result is autofilled (or not) correctly. Autofilling the placeholder
+ // before the search starts interferes with that.
+ gURLBar._enableAutofillPlaceholder = false;
+ registerCleanupFunction(async () => {
+ gURLBar._enableAutofillPlaceholder = true;
+ });
+});
+
+// The first result should be autofilled when all conditions are met. This also
+// does a sanity check to make sure that placeholder autofill is correctly
+// disabled, which is helpful for all tasks here and is why this one is first.
+add_task(async function successfulAutofill() {
+ // Do a simple search that should autofill. This will also set up the
+ // autofill placeholder string, which next we make sure is *not* autofilled.
+ await doInitialAutofillSearch();
+
+ // As a sanity check, do another search to make sure the placeholder is *not*
+ // autofilled. Make sure it's not autofilled by checking the input value and
+ // selection *before* the search completes. If placeholder autofill was not
+ // correctly disabled, then these assertions will fail.
+
+ gURLBar.value = "exa";
+ UrlbarTestUtils.fireInputEvent(window);
+
+ // before the search completes: no autofill
+ Assert.equal(gURLBar.value, "exa");
+ Assert.equal(gURLBar.selectionStart, "exa".length);
+ Assert.equal(gURLBar.selectionEnd, "exa".length);
+
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ // after the search completes: successful autofill
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "example.com/");
+ Assert.equal(gURLBar.selectionStart, "exa".length);
+ Assert.equal(gURLBar.selectionEnd, "example.com/".length);
+});
+
+// The first result should not be autofilled when it's not an autofill result.
+add_task(async function firstResultNotAutofill() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(!details.autofill);
+ Assert.equal(gURLBar.value, "foo");
+ Assert.equal(gURLBar.selectionStart, "foo".length);
+ Assert.equal(gURLBar.selectionEnd, "foo".length);
+});
+
+// The first result should *not* be autofilled when the placeholder is not
+// selected, the selection is empty, and the caret is *not* at the end of the
+// search string.
+add_task(async function caretNotAtEndOfSearchString() {
+ // To set up the placeholder, do an initial search that triggers autofill.
+ await doInitialAutofillSearch();
+
+ // Now do another search but set the caret to somewhere else besides the end
+ // of the new search string.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ selectionStart: "exa".length,
+ selectionEnd: "exa".length,
+ fireInputEvent: false,
+ });
+
+ // The first result should be an autofill result, but it should not have been
+ // autofilled.
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "exam");
+ Assert.equal(gURLBar.selectionStart, "exa".length);
+ Assert.equal(gURLBar.selectionEnd, "exa".length);
+
+ await cleanUp();
+});
+
+// The first result should *not* be autofilled when the placeholder is not
+// selected, the selection is *not* empty, and the caret is at the end of the
+// search string.
+add_task(async function selectionNotEmpty() {
+ // To set up the placeholder, do an initial search that triggers autofill.
+ await doInitialAutofillSearch();
+
+ // Now do another search. Set the selection end at the end of the search
+ // string, but make the selection non-empty.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ selectionStart: "exa".length,
+ selectionEnd: "exam".length,
+ });
+
+ // The first result should be an autofill result, but it should not have been
+ // autofilled.
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "exam");
+ Assert.equal(gURLBar.selectionStart, "exa".length);
+ Assert.equal(gURLBar.selectionEnd, "exam".length);
+
+ await cleanUp();
+});
+
+// The first result should be autofilled when the placeholder is not selected,
+// the selection is empty, and the caret is at the end of the search string.
+add_task(async function successfulAutofillAfterSettingPlaceholder() {
+ // To set up the placeholder, do an initial search that triggers autofill.
+ await doInitialAutofillSearch();
+
+ // Now do another search.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ selectionStart: "exam".length,
+ selectionEnd: "exam".length,
+ });
+
+ // It should be autofilled.
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "example.com/");
+ Assert.equal(gURLBar.selectionStart, "exam".length);
+ Assert.equal(gURLBar.selectionEnd, "example.com/".length);
+
+ await cleanUp();
+});
+
+// The first result should be autofilled when the placeholder *is* selected --
+// more precisely, when the portion of the placeholder after the new search
+// string is selected.
+add_task(async function successfulAutofillPlaceholderSelected() {
+ // To set up the placeholder, do an initial search that triggers autofill.
+ await doInitialAutofillSearch();
+
+ // Now do another search and select the portion of the placeholder after the
+ // new search string.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ selectionStart: "exam".length,
+ selectionEnd: "example.com/".length,
+ });
+
+ // It should be autofilled.
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "example.com/");
+ Assert.equal(gURLBar.selectionStart, "exam".length);
+ Assert.equal(gURLBar.selectionEnd, "example.com/".length);
+
+ await cleanUp();
+});
+
+async function doInitialAutofillSearch() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ex",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "example.com/");
+ Assert.equal(gURLBar.selectionStart, "ex".length);
+ Assert.equal(gURLBar.selectionEnd, "example.com/".length);
+}
+
+async function cleanUp() {
+ // In some cases above, a test task searches for "exam" at the end, and then
+ // the next task searches for "ex". Autofill results will not be allowed in
+ // the next task in that case since the old search string starts with the new
+ // search string. To prevent one task from interfering with the next, do a
+ // search that changes the search string. Also close the popup while we're
+ // here, although that's not really necessary.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "reset last search string",
+ });
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_Escape");
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_autoFill_paste.js b/browser/components/urlbar/tests/browser/browser_autoFill_paste.js
new file mode 100644
index 0000000000..47d92cb7d3
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_paste.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test checks we don't autofill on paste.
+
+"use strict";
+
+add_task(async function test() {
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+ await PlacesTestUtils.addVisits(["http://example.com/"]);
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+
+ // Search for "e". It should autofill to example.com/.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "e",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "example.com/");
+ Assert.equal(gURLBar.selectionStart, "e".length);
+ Assert.equal(gURLBar.selectionEnd, "example.com/".length);
+
+ // Now paste.
+ await selectAndPaste("ex");
+
+ // Nothing should have been autofilled.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(!details.autofill);
+ Assert.equal(gURLBar.value, "ex");
+ Assert.equal(gURLBar.selectionStart, "ex".length);
+ Assert.equal(gURLBar.selectionEnd, "ex".length);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autoFill_placeholder.js b/browser/components/urlbar/tests/browser/browser_autoFill_placeholder.js
new file mode 100644
index 0000000000..6fcd664de0
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_placeholder.js
@@ -0,0 +1,1017 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test makes sure that the autofill placeholder value is autofilled
+// correctly. The placeholder is a string that we immediately autofill when a
+// search starts and before its first result arrives in order to prevent text
+// flicker in the input.
+//
+// Because this test specifically checks autofill *before* searches complete, we
+// can't use promiseAutocompleteResultPopup() or other helpers that wait for
+// searches to complete. Instead the test uses fireInputEvent() to trigger
+// placeholder autofill and then immediately checks autofill status.
+
+"use strict";
+
+// Allow more time for verify mode.
+requestLongerTimeout(5);
+
+add_setup(async function () {
+ await cleanUp();
+});
+
+// Basic origin autofill test.
+add_task(async function origin() {
+ await addVisits("http://example.com/");
+
+ await search({
+ searchString: "ex",
+ valueBefore: "ex",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+ await search({
+ searchString: "exa",
+ valueBefore: "example.com/",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+ await search({
+ searchString: "EXAM",
+ valueBefore: "EXAMple.com/",
+ valueAfter: "EXAMple.com/",
+ placeholderAfter: "EXAMple.com/",
+ });
+ await search({
+ searchString: "eXaMp",
+ valueBefore: "eXaMple.com/",
+ valueAfter: "eXaMple.com/",
+ placeholderAfter: "eXaMple.com/",
+ });
+ await search({
+ searchString: "exampL",
+ valueBefore: "exampLe.com/",
+ valueAfter: "exampLe.com/",
+ placeholderAfter: "exampLe.com/",
+ });
+ await search({
+ searchString: "example.com",
+ valueBefore: "example.com/",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+ await search({
+ searchString: "example.com/",
+ valueBefore: "example.com/",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+
+ await cleanUp();
+});
+
+// Basic URL autofill test.
+add_task(async function url() {
+ await addVisits("http://example.com/aaa/bbb/ccc");
+
+ await search({
+ searchString: "example.com/a",
+ valueBefore: "example.com/a",
+ valueAfter: "example.com/aaa/",
+ placeholderAfter: "example.com/aaa/",
+ });
+ await search({
+ searchString: "EXAmple.com/aA",
+ valueBefore: "EXAmple.com/aAa/",
+ valueAfter: "EXAmple.com/aAa/",
+ placeholderAfter: "EXAmple.com/aAa/",
+ });
+ await search({
+ searchString: "example.com/aAa",
+ valueBefore: "example.com/aAa/",
+ valueAfter: "example.com/aAa/",
+ placeholderAfter: "example.com/aAa/",
+ });
+ await search({
+ searchString: "example.com/aaa/",
+ valueBefore: "example.com/aaa/",
+ valueAfter: "example.com/aaa/",
+ placeholderAfter: "example.com/aaa/",
+ });
+ await search({
+ searchString: "example.com/aaa/b",
+ valueBefore: "example.com/aaa/b",
+ valueAfter: "example.com/aaa/bbb/",
+ placeholderAfter: "example.com/aaa/bbb/",
+ });
+ await search({
+ searchString: "example.com/aAa/bB",
+ valueBefore: "example.com/aAa/bBb/",
+ valueAfter: "example.com/aAa/bBb/",
+ placeholderAfter: "example.com/aAa/bBb/",
+ });
+ await search({
+ searchString: "example.com/aAa/bBb",
+ valueBefore: "example.com/aAa/bBb/",
+ valueAfter: "example.com/aAa/bBb/",
+ placeholderAfter: "example.com/aAa/bBb/",
+ });
+ await search({
+ searchString: "example.com/aaa/bbb/",
+ valueBefore: "example.com/aaa/bbb/",
+ valueAfter: "example.com/aaa/bbb/",
+ placeholderAfter: "example.com/aaa/bbb/",
+ });
+ await search({
+ searchString: "example.com/aaa/bbb/c",
+ valueBefore: "example.com/aaa/bbb/c",
+ valueAfter: "example.com/aaa/bbb/ccc",
+ placeholderAfter: "example.com/aaa/bbb/ccc",
+ });
+ await search({
+ searchString: "example.com/aAa/bBb/cC",
+ valueBefore: "example.com/aAa/bBb/cCc",
+ valueAfter: "example.com/aAa/bBb/cCc",
+ placeholderAfter: "example.com/aAa/bBb/cCc",
+ });
+ await search({
+ searchString: "example.com/aaa/bbb/ccc",
+ valueBefore: "example.com/aaa/bbb/ccc",
+ valueAfter: "example.com/aaa/bbb/ccc",
+ placeholderAfter: "example.com/aaa/bbb/ccc",
+ });
+
+ await cleanUp();
+});
+
+// Basic adaptive history autofill test.
+add_task(async function adaptiveHistory() {
+ UrlbarPrefs.set("autoFill.adaptiveHistory.enabled", true);
+
+ await addVisits("http://example.com/test");
+ await UrlbarUtils.addToInputHistory("http://example.com/test", "exa");
+
+ await search({
+ searchString: "exa",
+ valueBefore: "exa",
+ valueAfter: "example.com/test",
+ placeholderAfter: "example.com/test",
+ });
+ await search({
+ searchString: "EXAM",
+ valueBefore: "EXAMple.com/test",
+ valueAfter: "EXAMple.com/test",
+ placeholderAfter: "EXAMple.com/test",
+ });
+ await search({
+ searchString: "eXaMpLe",
+ valueBefore: "eXaMpLe.com/test",
+ valueAfter: "eXaMpLe.com/test",
+ placeholderAfter: "eXaMpLe.com/test",
+ });
+ await search({
+ searchString: "example.",
+ valueBefore: "example.com/test",
+ valueAfter: "example.com/test",
+ placeholderAfter: "example.com/test",
+ });
+ await search({
+ searchString: "example.c",
+ valueBefore: "example.com/test",
+ valueAfter: "example.com/test",
+ placeholderAfter: "example.com/test",
+ });
+ await search({
+ searchString: "example.com",
+ valueBefore: "example.com/test",
+ valueAfter: "example.com/test",
+ placeholderAfter: "example.com/test",
+ });
+ await search({
+ searchString: "example.com/",
+ valueBefore: "example.com/test",
+ valueAfter: "example.com/test",
+ placeholderAfter: "example.com/test",
+ });
+ await search({
+ searchString: "example.com/T",
+ valueBefore: "example.com/Test",
+ valueAfter: "example.com/Test",
+ placeholderAfter: "example.com/Test",
+ });
+ await search({
+ searchString: "eXaMple.com/tE",
+ valueBefore: "eXaMple.com/tEst",
+ valueAfter: "eXaMple.com/tEst",
+ placeholderAfter: "eXaMple.com/tEst",
+ });
+ await search({
+ searchString: "example.com/tes",
+ valueBefore: "example.com/test",
+ valueAfter: "example.com/test",
+ placeholderAfter: "example.com/test",
+ });
+ await search({
+ searchString: "example.com/test",
+ valueBefore: "example.com/test",
+ valueAfter: "example.com/test",
+ placeholderAfter: "example.com/test",
+ });
+
+ UrlbarPrefs.clear("autoFill.adaptiveHistory.enabled");
+ await cleanUp();
+});
+
+// Search engine token alias test (aliases that start with "@").
+add_task(async function tokenAlias() {
+ // We have built-in engine aliases that may conflict with the one we choose
+ // here in terms of autofill, so be careful and choose a weird alias.
+ await SearchTestUtils.installSearchExtension({ keyword: "@__example" });
+
+ await search({
+ searchString: "@__ex",
+ valueBefore: "@__ex",
+ valueAfter: "@__example ",
+ placeholderAfter: "@__example ",
+ });
+ await search({
+ searchString: "@__exa",
+ valueBefore: "@__example ",
+ valueAfter: "@__example ",
+ placeholderAfter: "@__example ",
+ });
+ await search({
+ searchString: "@__EXAM",
+ valueBefore: "@__EXAMple ",
+ valueAfter: "@__EXAMple ",
+ placeholderAfter: "@__EXAMple ",
+ });
+ await search({
+ searchString: "@__eXaMp",
+ valueBefore: "@__eXaMple ",
+ valueAfter: "@__eXaMple ",
+ placeholderAfter: "@__eXaMple ",
+ });
+ await search({
+ searchString: "@__exampl",
+ valueBefore: "@__example ",
+ valueAfter: "@__example ",
+ placeholderAfter: "@__example ",
+ });
+
+ await cleanUp();
+});
+
+// The placeholder should not be used for a search that does not autofill, and
+// it should be cleared after the search completes.
+add_task(async function noAutofill() {
+ await addVisits("http://example.com/");
+
+ // Do an initial search that triggers autofill so that the placeholder has an
+ // initial value.
+ await search({
+ searchString: "ex",
+ valueBefore: "ex",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+
+ // Search with a string that does not match the placeholder. Placeholder
+ // autofill shouldn't happen.
+ await search({
+ searchString: "moz",
+ valueBefore: "moz",
+ valueAfter: "moz",
+ placeholderAfter: "",
+ });
+
+ // Search for "ex" again. It should be autofilled when the search completes
+ // but the placeholder will not be autofilled.
+ await search({
+ searchString: "ex",
+ valueBefore: "ex",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+
+ // Continue with a series of searches that should all use the placeholder.
+ await search({
+ searchString: "exa",
+ valueBefore: "example.com/",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+ await search({
+ searchString: "EXAM",
+ valueBefore: "EXAMple.com/",
+ valueAfter: "EXAMple.com/",
+ placeholderAfter: "EXAMple.com/",
+ });
+ await search({
+ searchString: "eXaMp",
+ valueBefore: "eXaMple.com/",
+ valueAfter: "eXaMple.com/",
+ placeholderAfter: "eXaMple.com/",
+ });
+ await search({
+ searchString: "exampl",
+ valueBefore: "example.com/",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+
+ await cleanUp();
+});
+
+// The placeholder should not be used for a search that autofills a different
+// value.
+add_task(async function differentAutofill() {
+ await addVisits("http://mozilla.org/", "http://example.com/");
+
+ // Do an initial search that triggers autofill so that the placeholder has an
+ // initial value.
+ await search({
+ searchString: "moz",
+ valueBefore: "moz",
+ valueAfter: "mozilla.org/",
+ placeholderAfter: "mozilla.org/",
+ });
+
+ // Search with a string that does not match the placeholder but does trigger
+ // autofill. Placeholder autofill shouldn't happen.
+ await search({
+ searchString: "ex",
+ valueBefore: "ex",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+
+ // Continue with a series of searches that should all use the placeholder.
+ await search({
+ searchString: "exa",
+ valueBefore: "example.com/",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+ await search({
+ searchString: "EXAm",
+ valueBefore: "EXAmple.com/",
+ valueAfter: "EXAmple.com/",
+ placeholderAfter: "EXAmple.com/",
+ });
+
+ // Search for "moz" again. It should be autofilled. Placeholder autofill
+ // shouldn't happen.
+ await search({
+ searchString: "moz",
+ valueBefore: "moz",
+ valueAfter: "mozilla.org/",
+ placeholderAfter: "mozilla.org/",
+ });
+
+ // Continue with a series of searches that should all use the placeholder.
+ await search({
+ searchString: "mozi",
+ valueBefore: "mozilla.org/",
+ valueAfter: "mozilla.org/",
+ placeholderAfter: "mozilla.org/",
+ });
+ await search({
+ searchString: "MOZil",
+ valueBefore: "MOZilla.org/",
+ valueAfter: "MOZilla.org/",
+ placeholderAfter: "MOZilla.org/",
+ });
+
+ await cleanUp();
+});
+
+// The placeholder should not be used for a search that uses a bookmark keyword
+// even when the keyword matches the placeholder, and the placeholder should be
+// cleared after the search completes.
+add_task(async function bookmarkKeyword() {
+ // Add a visit to example.com.
+ await addVisits("https://example.com/");
+
+ // Add a bookmark keyword that is a prefix of example.com.
+ await PlacesUtils.keywords.insert({
+ keyword: "ex",
+ url: "https://somekeyword.com/",
+ });
+
+ // Do an initial search that triggers autofill for the visit so that the
+ // placeholder has an initial value of "example.com/".
+ await search({
+ searchString: "e",
+ valueBefore: "e",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+
+ // Do a search that matches the bookmark keyword. The placeholder from the
+ // search above should be autofilled since the autofill placeholder
+ // ("example.com/") starts with the keyword ("ex"), but then when the bookmark
+ // result arrives, the autofilled value and placeholder should be cleared.
+ await search({
+ searchString: "ex",
+ valueBefore: "example.com/",
+ valueAfter: "ex",
+ placeholderAfter: "",
+ });
+
+ // Do another search that simulates the user continuing to type "example". No
+ // placeholder should be autofilled, but once the autofill result arrives for
+ // the visit, "example.com/" should be autofilled.
+ await search({
+ searchString: "exa",
+ valueBefore: "exa",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+
+ await PlacesUtils.keywords.remove("ex");
+ await cleanUp();
+});
+
+// The placeholder should not be used for a search that doesn't match its URI
+// fragment. This task uses a URL whose path is "/".
+add_task(async function noURIFragmentMatch1() {
+ await addVisits("https://example.com/#TEST");
+
+ const testData = [
+ {
+ desc: "Autofill example.com/#TEST then search for example.com/#Te",
+ searches: [
+ {
+ searchString: "example.com/#T",
+ valueBefore: "example.com/#T",
+ valueAfter: "example.com/#TEST",
+ placeholderAfter: "example.com/#TEST",
+ },
+ {
+ searchString: "example.com/#Te",
+ valueBefore: "example.com/#Te",
+ valueAfter: "example.com/#Te",
+ placeholderAfter: "",
+ },
+ ],
+ },
+ {
+ desc: "Autofill https://example.com/#TEST then search for https://example.com/#Te",
+ searches: [
+ {
+ searchString: "https://example.com/#T",
+ valueBefore: "https://example.com/#T",
+ valueAfter: "https://example.com/#TEST",
+ placeholderAfter: "https://example.com/#TEST",
+ },
+ {
+ searchString: "https://example.com/#Te",
+ valueBefore: "https://example.com/#Te",
+ valueAfter: "https://example.com/#Te",
+ placeholderAfter: "",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/#TEST then search for example.com/",
+ searches: [
+ {
+ searchString: "example.com/#T",
+ valueBefore: "example.com/#T",
+ valueAfter: "example.com/#TEST",
+ placeholderAfter: "example.com/#TEST",
+ },
+ {
+ searchString: "example.com/",
+ valueBefore: "example.com/",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ },
+ ],
+ },
+ ];
+
+ for (const { desc, searches } of testData) {
+ info("Running subtest: " + desc);
+
+ for (let i = 0; i < searches.length; i++) {
+ info("Doing search at index " + i);
+ await search(searches[i]);
+ }
+
+ // Clear the placeholder for the next subtest.
+ info("Doing extra search to clear placeholder");
+ await search({
+ searchString: "no match",
+ valueBefore: "no match",
+ valueAfter: "no match",
+ placeholderAfter: "",
+ });
+ }
+
+ await cleanUp();
+});
+
+// The placeholder should not be used for a search that doesn't match its URI
+// fragment. This task uses a URL whose path is "/foo".
+add_task(async function noURIFragmentMatch2() {
+ await addVisits("https://example.com/foo#TEST");
+
+ const testData = [
+ {
+ desc: "Autofill example.com/foo#TEST then search for example.com/foo#Te",
+ searches: [
+ {
+ searchString: "example.com/foo#T",
+ valueBefore: "example.com/foo#T",
+ valueAfter: "example.com/foo#TEST",
+ placeholderAfter: "example.com/foo#TEST",
+ },
+ {
+ searchString: "example.com/foo#Te",
+ valueBefore: "example.com/foo#Te",
+ valueAfter: "example.com/foo#Te",
+ placeholderAfter: "",
+ },
+ ],
+ },
+ {
+ desc: "Autofill https://example.com/foo#TEST then search for https://example.com/foo#Te",
+ searches: [
+ {
+ searchString: "https://example.com/foo#T",
+ valueBefore: "https://example.com/foo#T",
+ valueAfter: "https://example.com/foo#TEST",
+ placeholderAfter: "https://example.com/foo#TEST",
+ },
+ {
+ searchString: "https://example.com/foo#Te",
+ valueBefore: "https://example.com/foo#Te",
+ valueAfter: "https://example.com/foo#Te",
+ placeholderAfter: "",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/foo#TEST then search for example.com/",
+ searches: [
+ {
+ searchString: "example.com/foo#T",
+ valueBefore: "example.com/foo#T",
+ valueAfter: "example.com/foo#TEST",
+ placeholderAfter: "example.com/foo#TEST",
+ },
+ {
+ searchString: "example.com/",
+ valueBefore: "example.com/",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ },
+ ],
+ },
+ ];
+
+ for (const { desc, searches } of testData) {
+ info("Running subtest: " + desc);
+
+ for (let i = 0; i < searches.length; i++) {
+ info("Doing search at index " + i);
+ await search(searches[i]);
+ }
+
+ // Clear the placeholder for the next subtest.
+ info("Doing extra search to clear placeholder");
+ await search({
+ searchString: "no match",
+ valueBefore: "no match",
+ valueAfter: "no match",
+ placeholderAfter: "",
+ });
+ }
+
+ await cleanUp();
+});
+
+// The placeholder should not be used for a search that does not autofill its
+// URL path.
+add_task(async function noPathMatch() {
+ await addVisits("http://example.com/shallow/deep/file");
+
+ const testData = [
+ {
+ desc: "Autofill example.com/shallow/ then search for exam",
+ searches: [
+ {
+ searchString: "example.com/s",
+ valueBefore: "example.com/s",
+ valueAfter: "example.com/shallow/",
+ placeholderAfter: "example.com/shallow/",
+ },
+ {
+ searchString: "exam",
+ valueBefore: "exam",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/shallow/ then search for example.com/",
+ searches: [
+ {
+ searchString: "example.com/s",
+ valueBefore: "example.com/s",
+ valueAfter: "example.com/shallow/",
+ placeholderAfter: "example.com/shallow/",
+ },
+ {
+ searchString: "example.com/",
+ valueBefore: "example.com/",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/shallow/deep/ then search for exam",
+ searches: [
+ {
+ searchString: "example.com/shallow/d",
+ valueBefore: "example.com/shallow/d",
+ valueAfter: "example.com/shallow/deep/",
+ placeholderAfter: "example.com/shallow/deep/",
+ },
+ {
+ searchString: "exam",
+ valueBefore: "exam",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/shallow/deep/ then search for example.com/",
+ searches: [
+ {
+ searchString: "example.com/shallow/d",
+ valueBefore: "example.com/shallow/d",
+ valueAfter: "example.com/shallow/deep/",
+ placeholderAfter: "example.com/shallow/deep/",
+ },
+ {
+ searchString: "example.com/",
+ valueBefore: "example.com/",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/shallow/deep/ then search for example.com/s",
+ searches: [
+ {
+ searchString: "example.com/shallow/d",
+ valueBefore: "example.com/shallow/d",
+ valueAfter: "example.com/shallow/deep/",
+ placeholderAfter: "example.com/shallow/deep/",
+ },
+ {
+ searchString: "example.com/s",
+ valueBefore: "example.com/s",
+ valueAfter: "example.com/shallow/",
+ placeholderAfter: "example.com/shallow/",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/shallow/deep/ then search for example.com/shallow/",
+ searches: [
+ {
+ searchString: "example.com/shallow/d",
+ valueBefore: "example.com/shallow/d",
+ valueAfter: "example.com/shallow/deep/",
+ placeholderAfter: "example.com/shallow/deep/",
+ },
+ {
+ searchString: "example.com/shallow/",
+ valueBefore: "example.com/shallow/",
+ valueAfter: "example.com/shallow/",
+ placeholderAfter: "example.com/shallow/",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/shallow/deep/file then search for exam",
+ searches: [
+ {
+ searchString: "example.com/shallow/deep/f",
+ valueBefore: "example.com/shallow/deep/f",
+ valueAfter: "example.com/shallow/deep/file",
+ placeholderAfter: "example.com/shallow/deep/file",
+ },
+ {
+ searchString: "exam",
+ valueBefore: "exam",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/shallow/deep/file then search for example.com/",
+ searches: [
+ {
+ searchString: "example.com/shallow/deep/f",
+ valueBefore: "example.com/shallow/deep/f",
+ valueAfter: "example.com/shallow/deep/file",
+ placeholderAfter: "example.com/shallow/deep/file",
+ },
+ {
+ searchString: "example.com/",
+ valueBefore: "example.com/",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/shallow/deep/file then search for example.com/s",
+ searches: [
+ {
+ searchString: "example.com/shallow/deep/f",
+ valueBefore: "example.com/shallow/deep/f",
+ valueAfter: "example.com/shallow/deep/file",
+ placeholderAfter: "example.com/shallow/deep/file",
+ },
+ {
+ searchString: "example.com/s",
+ valueBefore: "example.com/s",
+ valueAfter: "example.com/shallow/",
+ placeholderAfter: "example.com/shallow/",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/shallow/deep/file then search for example.com/shallow/",
+ searches: [
+ {
+ searchString: "example.com/shallow/deep/f",
+ valueBefore: "example.com/shallow/deep/f",
+ valueAfter: "example.com/shallow/deep/file",
+ placeholderAfter: "example.com/shallow/deep/file",
+ },
+ {
+ searchString: "example.com/shallow/",
+ valueBefore: "example.com/shallow/",
+ valueAfter: "example.com/shallow/",
+ placeholderAfter: "example.com/shallow/",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/shallow/deep/file then search for example.com/shallow/d",
+ searches: [
+ {
+ searchString: "example.com/shallow/deep/f",
+ valueBefore: "example.com/shallow/deep/f",
+ valueAfter: "example.com/shallow/deep/file",
+ placeholderAfter: "example.com/shallow/deep/file",
+ },
+ {
+ searchString: "example.com/shallow/d",
+ valueBefore: "example.com/shallow/d",
+ valueAfter: "example.com/shallow/deep/",
+ placeholderAfter: "example.com/shallow/deep/",
+ },
+ ],
+ },
+ {
+ desc: "Autofill example.com/shallow/deep/file then search for example.com/shallow/deep/",
+ searches: [
+ {
+ searchString: "example.com/shallow/deep/f",
+ valueBefore: "example.com/shallow/deep/f",
+ valueAfter: "example.com/shallow/deep/file",
+ placeholderAfter: "example.com/shallow/deep/file",
+ },
+ {
+ searchString: "example.com/shallow/deep/fi",
+ valueBefore: "example.com/shallow/deep/file",
+ valueAfter: "example.com/shallow/deep/file",
+ placeholderAfter: "example.com/shallow/deep/file",
+ },
+ {
+ searchString: "example.com/shallow/deep/",
+ valueBefore: "example.com/shallow/deep/",
+ valueAfter: "example.com/shallow/deep/",
+ placeholderAfter: "example.com/shallow/deep/",
+ },
+ ],
+ },
+ ];
+
+ for (const { desc, searches } of testData) {
+ info("Running subtest: " + desc);
+
+ for (let i = 0; i < searches.length; i++) {
+ info("Doing search at index " + i);
+ await search(searches[i]);
+ }
+
+ // Clear the placeholder for the next subtest.
+ info("Doing extra search to clear placeholder");
+ await search({
+ searchString: "no match",
+ valueBefore: "no match",
+ valueAfter: "no match",
+ placeholderAfter: "",
+ });
+ }
+
+ await cleanUp();
+});
+
+// An adaptive history placeholder should not be used for a search that does not
+// autofill it.
+add_task(async function noAdaptiveHistoryMatch() {
+ UrlbarPrefs.set("autoFill.adaptiveHistory.enabled", true);
+
+ await addVisits("http://example.com/test");
+ await UrlbarUtils.addToInputHistory("http://example.com/test", "exam");
+
+ // Search for a longer string than the adaptive history input. Adaptive
+ // history autofill should be triggered.
+ await search({
+ searchString: "example",
+ valueBefore: "example",
+ valueAfter: "example.com/test",
+ placeholderAfter: "example.com/test",
+ });
+
+ // Search for the same string as the adaptive history input. The placeholder
+ // from the previous search should be used and adaptive history autofill
+ // should be triggered.
+ await search({
+ searchString: "exam",
+ valueBefore: "example.com/test",
+ valueAfter: "example.com/test",
+ placeholderAfter: "example.com/test",
+ });
+
+ // Search for a shorter string than the adaptive history input. The
+ // placeholder from the previous search should not be used since the search
+ // string is shorter than the adaptive history input.
+ await search({
+ searchString: "ex",
+ valueBefore: "ex",
+ valueAfter: "example.com/",
+ placeholderAfter: "example.com/",
+ });
+
+ UrlbarPrefs.clear("autoFill.adaptiveHistory.enabled");
+ await cleanUp();
+});
+
+/**
+ * This function does the following:
+ *
+ * 1. Starts a search with `searchString` but doesn't wait for it to complete.
+ * 2. Compares the input value to `valueBefore`. If anything is autofilled at
+ * this point, it will be due to the placeholder.
+ * 3. Waits for the search to complete.
+ * 4. Compares the input value to `valueAfter`. If anything is autofilled at
+ * this point, it will be due to the autofill result fetched by the search.
+ * 5. Compares the placeholder to `placeholderAfter`.
+ *
+ * @param {object} options
+ * The options object.
+ * @param {string} options.searchString
+ * The search string.
+ * @param {string} options.valueBefore
+ * The expected input value before the search completes.
+ * @param {string} options.valueAfter
+ * The expected input value after the search completes.
+ * @param {string} options.placeholderAfter
+ * The expected placeholder value after the search completes.
+ * @returns {Promise}
+ */
+async function search({
+ searchString,
+ valueBefore,
+ valueAfter,
+ placeholderAfter,
+}) {
+ info(
+ "Searching: " +
+ JSON.stringify({
+ searchString,
+ valueBefore,
+ valueAfter,
+ placeholderAfter,
+ })
+ );
+
+ await SimpleTest.promiseFocus(window);
+ gURLBar.inputField.focus();
+
+ // Set the input value and move the caret to the end to simulate the user
+ // typing. It's important the caret is at the end because otherwise autofill
+ // won't happen.
+ gURLBar.value = searchString;
+ gURLBar.inputField.setSelectionRange(
+ searchString.length,
+ searchString.length
+ );
+
+ // Placeholder autofill is done on input, so fire an input event. We can't use
+ // `promiseAutocompleteResultPopup()` or other helpers that wait for the
+ // search to complete because we are specifically checking placeholder
+ // autofill before the search completes.
+ UrlbarTestUtils.fireInputEvent(window);
+
+ // Check the input value and selection immediately, before waiting on the
+ // search to complete.
+ Assert.equal(
+ gURLBar.value,
+ valueBefore,
+ "gURLBar.value before the search completes"
+ );
+ Assert.equal(
+ gURLBar.selectionStart,
+ searchString.length,
+ "gURLBar.selectionStart before the search completes"
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ valueBefore.length,
+ "gURLBar.selectionEnd before the search completes"
+ );
+
+ // Wait for the search to complete.
+ info("Waiting for the search to complete");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ // Check the final value after the results arrived.
+ Assert.equal(
+ gURLBar.value,
+ valueAfter,
+ "gURLBar.value after the search completes"
+ );
+ Assert.equal(
+ gURLBar.selectionStart,
+ searchString.length,
+ "gURLBar.selectionStart after the search completes"
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ valueAfter.length,
+ "gURLBar.selectionEnd after the search completes"
+ );
+
+ // Check the placeholder.
+ if (placeholderAfter) {
+ Assert.ok(
+ gURLBar._autofillPlaceholder,
+ "gURLBar._autofillPlaceholder exists after the search completes"
+ );
+ Assert.strictEqual(
+ gURLBar._autofillPlaceholder.value,
+ placeholderAfter,
+ "gURLBar._autofillPlaceholder.value after the search completes"
+ );
+ } else {
+ Assert.strictEqual(
+ gURLBar._autofillPlaceholder,
+ null,
+ "gURLBar._autofillPlaceholder does not exist after the search completes"
+ );
+ }
+
+ // Check the first result.
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ !!details.autofill,
+ !!placeholderAfter,
+ "First result is an autofill result iff a placeholder is expected"
+ );
+}
+
+/**
+ * Adds enough visits to URLs so their origins start autofilling.
+ *
+ * @param {...string} urls The URLs to add visits to.
+ */
+async function addVisits(...urls) {
+ for (let url of urls) {
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits(url);
+ }
+ }
+}
+
+async function cleanUp() {
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+}
diff --git a/browser/components/urlbar/tests/browser/browser_autoFill_preserve.js b/browser/components/urlbar/tests/browser/browser_autoFill_preserve.js
new file mode 100644
index 0000000000..a197be8bf1
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_preserve.js
@@ -0,0 +1,257 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test makes sure that a few of aspects of autofill are correctly
+// preserved:
+//
+// * Autofill should preserve the user's case. If you type ExA, it should be
+// autofilled to ExAmple.com/, not example.com/.
+// * When you key down and then back up to the autofill result, autofill should
+// be restored, with the text selection and the user's case both preserved.
+// * When you key down/up so that no result is selected, the value that the
+// user typed to trigger autofill should be restored in the input.
+
+"use strict";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ // The example.com engine can interfere with this test.
+ set: [["browser.urlbar.suggest.engines", false]],
+ });
+ await cleanUp();
+});
+
+add_task(async function origin() {
+ await PlacesTestUtils.addVisits([
+ "http://example.com/",
+ "http://mozilla.org/example",
+ ]);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ExA",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "ExAmple.com/");
+ Assert.equal(gURLBar.selectionStart, "ExA".length);
+ Assert.equal(gURLBar.selectionEnd, "ExAmple.com/".length);
+ checkKeys([
+ ["KEY_ArrowDown", "http://mozilla.org/example", 1],
+ ["KEY_ArrowDown", "ExA", -1],
+ ["KEY_ArrowUp", "http://mozilla.org/example", 1],
+ ["KEY_ArrowUp", "ExAmple.com/", 0],
+ ["KEY_ArrowUp", "ExA", -1],
+ ["KEY_ArrowDown", "ExAmple.com/", 0],
+ ]);
+ await cleanUp();
+});
+
+add_task(async function originPort() {
+ await PlacesTestUtils.addVisits([
+ "http://example.com:8888/",
+ "http://mozilla.org/example",
+ ]);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ExA",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "ExAmple.com:8888/");
+ Assert.equal(gURLBar.selectionStart, "ExA".length);
+ Assert.equal(gURLBar.selectionEnd, "ExAmple.com:8888/".length);
+ checkKeys([
+ ["KEY_ArrowDown", "http://mozilla.org/example", 1],
+ ["KEY_ArrowDown", "ExA", -1],
+ ["KEY_ArrowUp", "http://mozilla.org/example", 1],
+ ["KEY_ArrowUp", "ExAmple.com:8888/", 0],
+ ["KEY_ArrowUp", "ExA", -1],
+ ["KEY_ArrowDown", "ExAmple.com:8888/", 0],
+ ]);
+ await cleanUp();
+});
+
+add_task(async function originScheme() {
+ await PlacesTestUtils.addVisits([
+ "http://example.com/",
+ "http://mozilla.org/example",
+ ]);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "http://ExA",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "http://ExAmple.com/");
+ Assert.equal(gURLBar.selectionStart, "http://ExA".length);
+ Assert.equal(gURLBar.selectionEnd, "http://ExAmple.com/".length);
+ checkKeys([
+ ["KEY_ArrowDown", "http://mozilla.org/example", 1],
+ ["KEY_ArrowDown", "http://ExA", -1],
+ ["KEY_ArrowUp", "http://mozilla.org/example", 1],
+ ["KEY_ArrowUp", "http://ExAmple.com/", 0],
+ ["KEY_ArrowUp", "http://ExA", -1],
+ ["KEY_ArrowDown", "http://ExAmple.com/", 0],
+ ]);
+ await cleanUp();
+});
+
+add_task(async function originPortScheme() {
+ await PlacesTestUtils.addVisits([
+ "http://example.com:8888/",
+ "http://mozilla.org/example",
+ ]);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "http://ExA",
+ fireInputEvents: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "http://ExAmple.com:8888/");
+ Assert.equal(gURLBar.selectionStart, "http://ExA".length);
+ Assert.equal(gURLBar.selectionEnd, "http://ExAmple.com:8888/".length);
+ checkKeys([
+ ["KEY_ArrowDown", "http://mozilla.org/example", 1],
+ ["KEY_ArrowDown", "http://ExA", -1],
+ ["KEY_ArrowUp", "http://mozilla.org/example", 1],
+ ["KEY_ArrowUp", "http://ExAmple.com:8888/", 0],
+ ["KEY_ArrowUp", "http://ExA", -1],
+ ["KEY_ArrowDown", "http://ExAmple.com:8888/", 0],
+ ]);
+ await cleanUp();
+});
+
+add_task(async function url() {
+ await PlacesTestUtils.addVisits([
+ "http://example.com/foo",
+ "http://example.com/foo",
+ "http://example.com/fff",
+ ]);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ExAmple.com/f",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "ExAmple.com/foo");
+ Assert.equal(gURLBar.selectionStart, "ExAmple.com/f".length);
+ Assert.equal(gURLBar.selectionEnd, "ExAmple.com/foo".length);
+ checkKeys([
+ ["KEY_ArrowDown", "http://example.com/fff", 1],
+ ["KEY_ArrowDown", "ExAmple.com/f", -1],
+ ["KEY_ArrowUp", "http://example.com/fff", 1],
+ ["KEY_ArrowUp", "ExAmple.com/foo", 0],
+ ["KEY_ArrowUp", "ExAmple.com/f", -1],
+ ["KEY_ArrowDown", "ExAmple.com/foo", 0],
+ ]);
+ await cleanUp();
+});
+
+add_task(async function urlPort() {
+ await PlacesTestUtils.addVisits([
+ "http://example.com:8888/foo",
+ "http://example.com:8888/foo",
+ "http://example.com:8888/fff",
+ ]);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ExAmple.com:8888/f",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "ExAmple.com:8888/foo");
+ Assert.equal(gURLBar.selectionStart, "ExAmple.com:8888/f".length);
+ Assert.equal(gURLBar.selectionEnd, "ExAmple.com:8888/foo".length);
+ checkKeys([
+ ["KEY_ArrowDown", "http://example.com:8888/fff", 1],
+ ["KEY_ArrowDown", "ExAmple.com:8888/f", -1],
+ ["KEY_ArrowUp", "http://example.com:8888/fff", 1],
+ ["KEY_ArrowUp", "ExAmple.com:8888/foo", 0],
+ ["KEY_ArrowUp", "ExAmple.com:8888/f", -1],
+ ["KEY_ArrowDown", "ExAmple.com:8888/foo", 0],
+ ]);
+ await cleanUp();
+});
+
+add_task(async function tokenAlias() {
+ await SearchTestUtils.installSearchExtension({ keyword: "@example" });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@ExA",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "@ExAmple ");
+ Assert.equal(gURLBar.selectionStart, "@ExA".length);
+ Assert.equal(gURLBar.selectionEnd, "@ExAmple ".length);
+ // Token aliases (1) hide the one-off buttons and (2) show only a single
+ // result, the "Search with" result for the alias's engine, so there's no way
+ // to key up/down to change the selection, so this task doesn't check key
+ // presses like the others do.
+ await cleanUp();
+});
+
+// This test is a little different from the others. It backspaces over the
+// autofilled substring and checks that autofill is *not* preserved.
+add_task(async function backspaceNoAutofill() {
+ await PlacesTestUtils.addVisits([
+ "http://example.com/",
+ "http://example.com/",
+ "http://mozilla.org/example",
+ ]);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ExA",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "ExAmple.com/");
+ Assert.equal(gURLBar.selectionStart, "ExA".length);
+ Assert.equal(gURLBar.selectionEnd, "ExAmple.com/".length);
+
+ EventUtils.synthesizeKey("KEY_Backspace");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(!details.autofill);
+ Assert.equal(gURLBar.value, "ExA");
+ Assert.equal(gURLBar.selectionStart, "ExA".length);
+ Assert.equal(gURLBar.selectionEnd, "ExA".length);
+
+ let heuristicValue = "ExA";
+
+ checkKeys([
+ ["KEY_ArrowDown", "http://example.com/", 1],
+ ["KEY_ArrowDown", "http://mozilla.org/example", 2],
+ ["KEY_ArrowDown", "ExA", -1],
+ ["KEY_ArrowUp", "http://mozilla.org/example", 2],
+ ["KEY_ArrowUp", "http://example.com/", 1],
+ ["KEY_ArrowUp", heuristicValue, 0],
+ ["KEY_ArrowUp", "ExA", -1],
+ ["KEY_ArrowDown", heuristicValue, 0],
+ ]);
+
+ await cleanUp();
+});
+
+function checkKeys(testTuples) {
+ for (let [key, value, selectedIndex] of testTuples) {
+ EventUtils.synthesizeKey(key);
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), selectedIndex);
+ Assert.equal(gURLBar.untrimmedValue, value);
+ }
+}
+
+async function cleanUp() {
+ EventUtils.synthesizeKey("KEY_Escape");
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+}
diff --git a/browser/components/urlbar/tests/browser/browser_autoFill_trimURLs.js b/browser/components/urlbar/tests/browser/browser_autoFill_trimURLs.js
new file mode 100644
index 0000000000..3e068d52a4
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_trimURLs.js
@@ -0,0 +1,183 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test ensures that autoFilled values are not trimmed, unless the user
+// selects from the autocomplete popup.
+
+"use strict";
+
+add_setup(async function () {
+ const PREF_TRIMURL = "browser.urlbar.trimURLs";
+ const PREF_AUTOFILL = "browser.urlbar.autoFill";
+
+ registerCleanupFunction(async function () {
+ Services.prefs.clearUserPref(PREF_TRIMURL);
+ Services.prefs.clearUserPref(PREF_AUTOFILL);
+ await PlacesUtils.history.clear();
+ gURLBar.handleRevert();
+ });
+ Services.prefs.setBoolPref(PREF_TRIMURL, true);
+ Services.prefs.setBoolPref(PREF_AUTOFILL, true);
+
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+
+ // Adding a tab would hit switch-to-tab, so it's safer to just add a visit.
+ await PlacesTestUtils.addVisits([
+ {
+ uri: "http://www.autofilltrimurl.com/whatever",
+ },
+ {
+ uri: "https://www.secureautofillurl.com/whatever",
+ },
+ ]);
+});
+
+async function promiseSearch(searchtext) {
+ gURLBar.focus();
+ gURLBar.inputField.value = searchtext.substr(0, searchtext.length - 1);
+ EventUtils.sendString(searchtext.substr(-1, 1));
+ await UrlbarTestUtils.promiseSearchComplete(window);
+}
+
+async function promiseTestResult(test) {
+ info(`Searching for '${test.search}'`);
+
+ await promiseSearch(test.search);
+
+ Assert.equal(
+ gURLBar.inputField.value,
+ test.autofilledValue,
+ `Autofilled value is as expected for search '${test.search}'`
+ );
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+
+ Assert.equal(
+ result.displayed.title,
+ test.resultListDisplayTitle,
+ `Autocomplete result should have displayed title as expected for search '${test.search}'`
+ );
+
+ Assert.equal(
+ result.displayed.action,
+ test.resultListActionText,
+ `Autocomplete action text should be as expected for search '${test.search}'`
+ );
+
+ Assert.equal(
+ result.type,
+ test.resultListType,
+ `Autocomplete result should have searchengine for the type for search '${test.search}'`
+ );
+
+ Assert.equal(
+ !!result.searchParams,
+ !!test.searchParams,
+ "Should have search params if expected"
+ );
+ if (test.searchParams) {
+ let definedParams = {};
+ for (let [k, v] of Object.entries(result.searchParams)) {
+ if (v !== undefined) {
+ definedParams[k] = v;
+ }
+ }
+ Assert.deepEqual(
+ definedParams,
+ test.searchParams,
+ "Shoud have the correct search params"
+ );
+ } else {
+ Assert.equal(
+ result.url,
+ test.finalCompleteValue,
+ "Should have the correct URL/finalCompleteValue"
+ );
+ }
+}
+
+const tests = [
+ {
+ search: "http://",
+ autofilledValue: "http://",
+ resultListDisplayTitle: "http://",
+ resultListActionText: "Search with Google",
+ resultListType: UrlbarUtils.RESULT_TYPE.SEARCH,
+ searchParams: {
+ engine: "Google",
+ query: "http://",
+ },
+ },
+ {
+ search: "https://",
+ autofilledValue: "https://",
+ resultListDisplayTitle: "https://",
+ resultListActionText: "Search with Google",
+ resultListType: UrlbarUtils.RESULT_TYPE.SEARCH,
+ searchParams: {
+ engine: "Google",
+ query: "https://",
+ },
+ },
+ {
+ search: "au",
+ autofilledValue: "autofilltrimurl.com/",
+ resultListDisplayTitle: "www.autofilltrimurl.com",
+ resultListActionText: "Visit",
+ resultListType: UrlbarUtils.RESULT_TYPE.URL,
+ finalCompleteValue: "http://www.autofilltrimurl.com/",
+ },
+ {
+ search: "http://au",
+ autofilledValue: "http://autofilltrimurl.com/",
+ resultListDisplayTitle: "www.autofilltrimurl.com",
+ resultListActionText: "Visit",
+ resultListType: UrlbarUtils.RESULT_TYPE.URL,
+ finalCompleteValue: "http://www.autofilltrimurl.com/",
+ },
+ {
+ search: "sec",
+ autofilledValue: "secureautofillurl.com/",
+ resultListDisplayTitle: "https://www.secureautofillurl.com",
+ resultListActionText: "Visit",
+ resultListType: UrlbarUtils.RESULT_TYPE.URL,
+ finalCompleteValue: "https://www.secureautofillurl.com/",
+ },
+ {
+ search: "https://sec",
+ autofilledValue: "https://secureautofillurl.com/",
+ resultListDisplayTitle: "https://www.secureautofillurl.com",
+ resultListActionText: "Visit",
+ resultListType: UrlbarUtils.RESULT_TYPE.URL,
+ finalCompleteValue: "https://www.secureautofillurl.com/",
+ },
+];
+
+add_task(async function autofill_tests() {
+ for (let test of tests) {
+ await promiseTestResult(test);
+ }
+});
+
+add_task(async function autofill_complete_domain() {
+ await promiseSearch("http://www.autofilltrimurl.com");
+ Assert.equal(
+ gURLBar.inputField.value,
+ "http://www.autofilltrimurl.com/",
+ "Should have the correct autofill value"
+ );
+
+ // Now ensure selecting from the popup correctly trims.
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "Should have the correct matches"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(
+ gURLBar.inputField.value,
+ "www.autofilltrimurl.com/whatever",
+ "Should have applied trim correctly"
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autoFill_typed.js b/browser/components/urlbar/tests/browser/browser_autoFill_typed.js
new file mode 100644
index 0000000000..371e73c400
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_typed.js
@@ -0,0 +1,172 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test makes sure that autofill works as expected when typing, character
+// by character.
+
+"use strict";
+
+add_setup(async function () {
+ await cleanUp();
+});
+
+add_task(async function origin() {
+ await PlacesTestUtils.addVisits(["http://example.com/"]);
+ // all lowercase
+ await typeAndCheck([
+ ["e", "example.com/"],
+ ["x", "example.com/"],
+ ["a", "example.com/"],
+ ["m", "example.com/"],
+ ["p", "example.com/"],
+ ["l", "example.com/"],
+ ["e", "example.com/"],
+ [".", "example.com/"],
+ ["c", "example.com/"],
+ ["o", "example.com/"],
+ ["m", "example.com/"],
+ ["/", "example.com/"],
+ ]);
+ gURLBar.value = "";
+ // mixed case
+ await typeAndCheck([
+ ["E", "Example.com/"],
+ ["x", "Example.com/"],
+ ["A", "ExAmple.com/"],
+ ["m", "ExAmple.com/"],
+ ["P", "ExAmPle.com/"],
+ ["L", "ExAmPLe.com/"],
+ ["e", "ExAmPLe.com/"],
+ [".", "ExAmPLe.com/"],
+ ["C", "ExAmPLe.Com/"],
+ ["o", "ExAmPLe.Com/"],
+ ["M", "ExAmPLe.CoM/"],
+ ["/", "ExAmPLe.CoM/"],
+ ]);
+ await cleanUp();
+});
+
+add_task(async function url() {
+ await PlacesTestUtils.addVisits(["http://example.com/foo/bar"]);
+ // all lowercase
+ await typeAndCheck([
+ ["e", "example.com/"],
+ ["x", "example.com/"],
+ ["a", "example.com/"],
+ ["m", "example.com/"],
+ ["p", "example.com/"],
+ ["l", "example.com/"],
+ ["e", "example.com/"],
+ [".", "example.com/"],
+ ["c", "example.com/"],
+ ["o", "example.com/"],
+ ["m", "example.com/"],
+ ["/", "example.com/"],
+ ["f", "example.com/foo/"],
+ ["o", "example.com/foo/"],
+ ["o", "example.com/foo/"],
+ ["/", "example.com/foo/"],
+ ["b", "example.com/foo/bar"],
+ ["a", "example.com/foo/bar"],
+ ["r", "example.com/foo/bar"],
+ ]);
+ gURLBar.value = "";
+ // mixed case
+ await typeAndCheck([
+ ["E", "Example.com/"],
+ ["x", "Example.com/"],
+ ["A", "ExAmple.com/"],
+ ["m", "ExAmple.com/"],
+ ["P", "ExAmPle.com/"],
+ ["L", "ExAmPLe.com/"],
+ ["e", "ExAmPLe.com/"],
+ [".", "ExAmPLe.com/"],
+ ["C", "ExAmPLe.Com/"],
+ ["o", "ExAmPLe.Com/"],
+ ["M", "ExAmPLe.CoM/"],
+ ["/", "ExAmPLe.CoM/"],
+ ["f", "ExAmPLe.CoM/foo/"],
+ ["o", "ExAmPLe.CoM/foo/"],
+ ["o", "ExAmPLe.CoM/foo/"],
+ ["/", "ExAmPLe.CoM/foo/"],
+ ["b", "ExAmPLe.CoM/foo/bar"],
+ ["a", "ExAmPLe.CoM/foo/bar"],
+ ["r", "ExAmPLe.CoM/foo/bar"],
+ ]);
+ await cleanUp();
+});
+
+add_task(async function tokenAlias() {
+ // We have built-in engine aliases that may conflict with the one we choose
+ // here in terms of autofill, so be careful and choose a weird alias.
+ await SearchTestUtils.installSearchExtension({ keyword: "@__example" });
+ // all lowercase
+ await typeAndCheck([
+ ["@", "@"],
+ ["_", "@__example "],
+ ["_", "@__example "],
+ ["e", "@__example "],
+ ["x", "@__example "],
+ ["a", "@__example "],
+ ["m", "@__example "],
+ ["p", "@__example "],
+ ["l", "@__example "],
+ ["e", "@__example "],
+ ]);
+ gURLBar.value = "";
+ // mixed case
+ await typeAndCheck([
+ ["@", "@"],
+ ["_", "@__example "],
+ ["_", "@__example "],
+ ["E", "@__Example "],
+ ["x", "@__Example "],
+ ["A", "@__ExAmple "],
+ ["m", "@__ExAmple "],
+ ["P", "@__ExAmPle "],
+ ["L", "@__ExAmPLe "],
+ ["e", "@__ExAmPLe "],
+ ]);
+ await cleanUp();
+});
+
+async function typeAndCheck(values) {
+ gURLBar.focus();
+ for (let i = 0; i < values.length; i++) {
+ let [char, expectedInputValue] = values[i];
+ info(
+ `Typing: i=${i} char=${char} ` +
+ `substring="${expectedInputValue.substring(0, i + 1)}"`
+ );
+ EventUtils.synthesizeKey(char);
+ if (i == 0 && char == "@") {
+ // A single "@" doesn't trigger autofill, so skip the checks below. (It
+ // shows all the @ aliases.)
+ continue;
+ }
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ let restIsSpaces = !expectedInputValue.substring(i + 1).trim();
+ Assert.equal(gURLBar.value, expectedInputValue);
+ Assert.equal(gURLBar.selectionStart, i + 1);
+ Assert.equal(gURLBar.selectionEnd, expectedInputValue.length);
+ if (restIsSpaces) {
+ // Autofilled @ aliases have a trailing space. We should check that the
+ // space is autofilled when each preceding character is typed, but once
+ // the final non-space char is typed, autofill actually stops and the
+ // trailing space is not autofilled. (Which is maybe not the way it
+ // should work...) Skip the check below.
+ continue;
+ }
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ }
+}
+
+async function cleanUp() {
+ gURLBar.value = "";
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_Escape");
+ });
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+}
diff --git a/browser/components/urlbar/tests/browser/browser_autoFill_undo.js b/browser/components/urlbar/tests/browser/browser_autoFill_undo.js
new file mode 100644
index 0000000000..c233da80f2
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_undo.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test checks the behavior of text undo (Ctrl-Z, cmd_undo) in regard to
+// autofill.
+
+"use strict";
+
+add_task(async function test() {
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+ await PlacesTestUtils.addVisits(["http://example.com/"]);
+
+ // Search for "ex". It should autofill to example.com/.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ex",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(gURLBar.value, "example.com/");
+ Assert.equal(gURLBar.selectionStart, "ex".length);
+ Assert.equal(gURLBar.selectionEnd, "example.com/".length);
+
+ // Type an x.
+ EventUtils.synthesizeKey("x");
+
+ // Nothing should have been autofilled.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(!details.autofill);
+ Assert.equal(gURLBar.value, "exx");
+ Assert.equal(gURLBar.selectionStart, "exx".length);
+ Assert.equal(gURLBar.selectionEnd, "exx".length);
+
+ // Undo the typed x.
+ goDoCommand("cmd_undo");
+
+ // The text should be restored to ex[ample.com/] (with the part in brackets
+ // autofilled and selected).
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(gURLBar.value, "example.com/");
+ Assert.ok(!details.autofill, "Autofill should not be set.");
+ Assert.equal(gURLBar.selectionStart, "ex".length);
+ Assert.equal(gURLBar.selectionEnd, "example.com/".length);
+
+ await PlacesUtils.history.clear();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autoOpen.js b/browser/components/urlbar/tests/browser/browser_autoOpen.js
new file mode 100644
index 0000000000..8ed7e8e402
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autoOpen.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function checkOpensOnFocus(win = window) {
+ // The view should not open when the input is focused programmatically.
+ win.gURLBar.blur();
+ win.gURLBar.focus();
+ Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open");
+ win.gURLBar.blur();
+
+ // Check the keyboard shortcut.
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ await UrlbarTestUtils.promisePopupClose(win, () => {
+ win.gURLBar.blur();
+ });
+
+ // Focus with the mouse.
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {}, win);
+ });
+ await UrlbarTestUtils.promisePopupClose(win, () => {
+ win.gURLBar.blur();
+ });
+}
+
+add_setup(async function () {
+ // Add some history for the empty panel.
+ await PlacesTestUtils.addVisits([
+ {
+ uri: "http://mochi.test:8888/",
+ transition: PlacesUtils.history.TRANSITIONS.TYPED,
+ },
+ ]);
+ registerCleanupFunction(() => PlacesUtils.history.clear());
+});
+
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async browser => {
+ await checkOpensOnFocus();
+ }
+ );
+});
+
+add_task(async function newtabAndHome() {
+ for (let url of ["about:newtab", "about:home"]) {
+ // withNewTab randomly hangs on these pages when waitForLoad = true (the
+ // default), so pass false.
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url, waitForLoad: false },
+ async browser => {
+ // We don't wait for load, but we must ensure to be on the expected url.
+ await TestUtils.waitForCondition(
+ () => gBrowser.currentURI.spec == url,
+ "Ensure we're on the expected page"
+ );
+ await checkOpensOnFocus();
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "http://example.com/" },
+ async otherBrowser => {
+ await checkOpensOnFocus();
+ // Switch back to about:newtab/home.
+ await BrowserTestUtils.switchTab(
+ gBrowser,
+ gBrowser.getTabForBrowser(browser)
+ );
+ await checkOpensOnFocus();
+ // Switch back to example.com.
+ await BrowserTestUtils.switchTab(
+ gBrowser,
+ gBrowser.getTabForBrowser(otherBrowser)
+ );
+ await checkOpensOnFocus();
+ }
+ );
+ // After example.com closes, about:newtab/home is selected again.
+ await checkOpensOnFocus();
+ // Load example.com in the same tab.
+ BrowserTestUtils.loadURIString(
+ gBrowser.selectedBrowser,
+ "http://example.com/"
+ );
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ await checkOpensOnFocus();
+ }
+ );
+ }
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js b/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js
new file mode 100644
index 0000000000..ead026244e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js
@@ -0,0 +1,185 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test ensures that we produce good labels for a11y purposes.
+ */
+
+const { CommonUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs"
+);
+
+const SUGGEST_ALL_PREF = "browser.search.suggest.enabled";
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+let accService;
+
+async function getResultText(element, expectedValue, description = "") {
+ await BrowserTestUtils.waitForCondition(
+ () => {
+ let accessible = accService.getAccessibleFor(element);
+ return accessible !== null && accessible.name === expectedValue;
+ },
+ description,
+ 200
+ );
+}
+
+/**
+ * Initializes the accessibility service and registers a cleanup function to
+ * shut it down. If it's not shut down properly, it can crash the current tab
+ * and cause the test to fail, especially in verify mode.
+ *
+ * This function is adapted from from tests in accessible/tests/browser and its
+ * helper functions are adapted or copied from functions of the same names in
+ * the same directory.
+ */
+async function initAccessibilityService() {
+ const [a11yInitObserver, a11yInit] = initAccService();
+ await a11yInitObserver;
+ accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+ await a11yInit;
+
+ registerCleanupFunction(async () => {
+ const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
+ await a11yShutdownObserver;
+ accService = null;
+ forceGC();
+ await a11yShutdownPromise;
+ });
+}
+
+// Adapted from `initAccService()` in accessible/tests/browser/head.js
+function initAccService() {
+ return [
+ CommonUtils.addAccServiceInitializedObserver(),
+ CommonUtils.observeAccServiceInitialized(),
+ ];
+}
+
+// Adapted from `shutdownAccService()` in accessible/tests/browser/head.js
+function shutdownAccService() {
+ return [
+ CommonUtils.addAccServiceShutdownObserver(),
+ CommonUtils.observeAccServiceShutdown(),
+ ];
+}
+
+// Copied from accessible/tests/browser/shared-head.js
+function forceGC() {
+ SpecialPowers.gc();
+ SpecialPowers.forceShrinkingGC();
+ SpecialPowers.forceCC();
+ SpecialPowers.gc();
+ SpecialPowers.forceShrinkingGC();
+ SpecialPowers.forceCC();
+}
+
+add_setup(async function () {
+ await initAccessibilityService();
+});
+
+add_task(async function switchToTab() {
+ let tab = BrowserTestUtils.addTab(gBrowser, "about:robots");
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "% robots",
+ });
+
+ let index = 0;
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
+ "Should have a switch tab result"
+ );
+
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ index
+ );
+ // The a11y text will include the "Firefox Suggest" pseudo-element label shown
+ // before the result.
+ await getResultText(
+ element._content,
+ "Firefox Suggest about:robots — Switch to Tab",
+ "Result a11y text is correct"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ gURLBar.handleRevert();
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function searchSuggestions() {
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ setAsDefault: true,
+ });
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+ registerCleanupFunction(async function () {
+ Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ let length = await UrlbarTestUtils.getResultCount(window);
+ // Don't assume that the search doesn't match history or bookmarks left around
+ // by earlier tests.
+ Assert.greaterOrEqual(
+ length,
+ 3,
+ "Should get at least heuristic result + two search suggestions"
+ );
+ // The first expected search is the search term itself since the heuristic
+ // result will come before the search suggestions.
+ let searchTerm = "foo";
+ let expectedSearches = [searchTerm, "foofoo", "foobar"];
+ for (let i = 0; i < length; i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ if (result.type === UrlbarUtils.RESULT_TYPE.SEARCH) {
+ Assert.greaterOrEqual(
+ expectedSearches.length,
+ 0,
+ "Should still have expected searches remaining"
+ );
+
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ i
+ );
+
+ // Select the row so we see the expanded text.
+ gURLBar.view.selectedRowIndex = i;
+
+ if (result.searchParams.inPrivateWindow) {
+ await getResultText(
+ element._content,
+ searchTerm + " — Search in a Private Window",
+ "Check result label for search in private window"
+ );
+ } else {
+ let suggestion = expectedSearches.shift();
+ await getResultText(
+ element._content,
+ suggestion +
+ " — Search with browser_searchSuggestionEngine searchSuggestionEngine.xml",
+ "Check result label for non-private search"
+ );
+ }
+ }
+ }
+ Assert.ok(!expectedSearches.length);
+
+ await UrlbarTestUtils.promisePopupClose(window);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autocomplete_autoselect.js b/browser/components/urlbar/tests/browser/browser_autocomplete_autoselect.js
new file mode 100644
index 0000000000..ef3da56ef0
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_autoselect.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that the first item is correctly autoselected and some navigation
+ * around the results list.
+ */
+
+function repeat(limit, func) {
+ for (let i = 0; i < limit; i++) {
+ func(i);
+ }
+}
+
+function assertSelected(index) {
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ index,
+ "Should have selected the correct item"
+ );
+
+ // This is true because although both the listbox and the one-offs can have
+ // selections, the test doesn't check that.
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtons(window).selectedButton,
+ null,
+ "A result is selected, so the one-offs should not have a selection"
+ );
+}
+
+function assertSelected_one_off(index) {
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtons(window).selectedButtonIndex,
+ index,
+ "Expected one-off button should be selected"
+ );
+
+ // This is true because although both the listbox and the one-offs can have
+ // selections, the test doesn't check that.
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ -1,
+ "A one-off is selected, so the listbox should not have a selection"
+ );
+}
+
+add_task(async function () {
+ let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ BrowserTestUtils.removeTab(tab);
+ });
+
+ let visits = [];
+ repeat(maxResults, i => {
+ visits.push({
+ uri: makeURI("http://example.com/autocomplete/?" + i),
+ });
+ });
+ await PlacesTestUtils.addVisits(visits);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "example.com/autocomplete",
+ fireInputEvent: true,
+ });
+
+ let resultCount = await UrlbarTestUtils.getResultCount(window);
+
+ Assert.equal(
+ resultCount,
+ maxResults,
+ "Should get the expected amount of results"
+ );
+ assertSelected(0);
+
+ info("Key Down to select the next item");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ assertSelected(1);
+
+ info("Key Down maxResults-1 times should select the first one-off");
+ repeat(maxResults - 1, () => EventUtils.synthesizeKey("KEY_ArrowDown"));
+ assertSelected_one_off(0);
+
+ info("Key Down numButtons-1 should select the last one-off");
+ let numButtons =
+ UrlbarTestUtils.getOneOffSearchButtons(window).getSelectableButtons(
+ true
+ ).length;
+ repeat(numButtons - 1, () => EventUtils.synthesizeKey("KEY_ArrowDown"));
+ assertSelected_one_off(numButtons - 1);
+
+ info("Key Down twice more should select the second result");
+ repeat(2, () => EventUtils.synthesizeKey("KEY_ArrowDown"));
+ assertSelected(1);
+
+ info("Key Down maxResults + numButtons times should wrap around");
+ repeat(maxResults + numButtons, () =>
+ EventUtils.synthesizeKey("KEY_ArrowDown")
+ );
+ assertSelected(1);
+
+ info("Key Up maxResults + numButtons times should wrap around the other way");
+ repeat(maxResults + numButtons, () =>
+ EventUtils.synthesizeKey("KEY_ArrowUp")
+ );
+ assertSelected(1);
+
+ info("Page Up will go up the list, but not wrap");
+ EventUtils.synthesizeKey("KEY_PageUp");
+ assertSelected(0);
+
+ info("Page Up again will wrap around to the end of the list");
+ EventUtils.synthesizeKey("KEY_PageUp");
+ assertSelected(maxResults - 1);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autocomplete_cursor.js b/browser/components/urlbar/tests/browser/browser_autocomplete_cursor.js
new file mode 100644
index 0000000000..5e0081a92c
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_cursor.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests the cursor remains in the right place when a new window is opened.
+ */
+
+add_task(async function test_windowSwitch() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "www.mozilla.org",
+ });
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+
+ gURLBar.focus();
+ gURLBar.inputField.setSelectionRange(4, 4);
+
+ let newWindow = await BrowserTestUtils.openNewBrowserWindow();
+
+ await BrowserTestUtils.closeWindow(newWindow);
+
+ Assert.equal(
+ document.activeElement,
+ gURLBar.inputField,
+ "URL Bar should be focused"
+ );
+ Assert.equal(gURLBar.selectionStart, 4, "Should not have moved the cursor");
+ Assert.equal(gURLBar.selectionEnd, 4, "Should not have selected anything");
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autocomplete_edit_completed.js b/browser/components/urlbar/tests/browser/browser_autocomplete_edit_completed.js
new file mode 100644
index 0000000000..c17949eb9e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_edit_completed.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests selecting a result, and editing the value of that autocompleted result.
+ */
+
+add_task(async function () {
+ await PlacesUtils.history.clear();
+
+ await PlacesTestUtils.addVisits([
+ { uri: makeURI("http://example.com/foo") },
+ { uri: makeURI("http://example.com/foo/bar") },
+ ]);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ registerCleanupFunction(async function () {
+ BrowserTestUtils.removeTab(tab);
+ await PlacesUtils.history.clear();
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "http://example.com",
+ });
+
+ const initialIndex = UrlbarTestUtils.getSelectedRowIndex(window);
+
+ info("Key Down to select the next item.");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+
+ let nextIndex = initialIndex + 1;
+ let nextResult = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ nextIndex
+ );
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ nextIndex,
+ "Should have selected the next item"
+ );
+ Assert.equal(
+ gURLBar.untrimmedValue,
+ nextResult.url,
+ "Should have completed the URL"
+ );
+
+ info("Press backspace");
+ EventUtils.synthesizeKey("KEY_Backspace");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ let editedValue = gURLBar.value;
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ initialIndex,
+ "Should have selected the initialIndex again"
+ );
+ Assert.notEqual(editedValue, nextResult.url, "The URL has changed.");
+
+ let docLoad = BrowserTestUtils.waitForDocLoadAndStopIt(
+ "http://" + editedValue,
+ gBrowser.selectedBrowser
+ );
+
+ info("Press return to load edited URL.");
+
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_Enter");
+ });
+
+ await docLoad;
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autocomplete_enter_race.js b/browser/components/urlbar/tests/browser/browser_autocomplete_enter_race.js
new file mode 100644
index 0000000000..a684c60e5b
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_enter_race.js
@@ -0,0 +1,193 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests what happens when the enter key is pressed quickly after entering text.
+ */
+
+// The order of these tests matters!
+
+add_setup(async function () {
+ let bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/?q=%s",
+ title: "test",
+ });
+ registerCleanupFunction(async function () {
+ await PlacesUtils.bookmarks.remove(bm);
+ await PlacesUtils.history.clear();
+ });
+ // Needs at least one success.
+ ok(true, "Setup complete");
+});
+
+add_task(
+ taskWithNewTab(async function test_loadSite() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.autofill", false]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "example.co",
+ });
+ gURLBar.focus();
+ EventUtils.sendString("m");
+ EventUtils.synthesizeKey("KEY_Enter");
+ info("wait for the page to load");
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedTab.linkedBrowser,
+ false,
+ "http://example.com/"
+ );
+ await SpecialPowers.popPrefEnv();
+ })
+);
+
+add_task(
+ taskWithNewTab(async function test_sametext() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "example.com",
+ fireInputEvent: true,
+ });
+
+ // Simulate re-entering the same text searched the last time. This may happen
+ // through a copy paste, but clipboard handling is not much reliable, so just
+ // fire an input event.
+ info("synthesize input event");
+ let event = document.createEvent("Events");
+ event.initEvent("input", true, true);
+ gURLBar.inputField.dispatchEvent(event);
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ info("wait for the page to load");
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedTab.linkedBrowser,
+ false,
+ "http://example.com/"
+ );
+ })
+);
+
+add_task(
+ taskWithNewTab(async function test_after_empty_search() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ gURLBar.focus();
+ gURLBar.value = "e";
+ EventUtils.synthesizeKey("x");
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ info("wait for the page to load");
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedTab.linkedBrowser,
+ false,
+ "http://example.com/"
+ );
+ })
+);
+
+add_task(
+ taskWithNewTab(async function test_disabled_ac() {
+ // Disable autocomplete.
+ let suggestHistory = Preferences.get("browser.urlbar.suggest.history");
+ Preferences.set("browser.urlbar.suggest.history", false);
+ let suggestBookmarks = Preferences.get("browser.urlbar.suggest.bookmark");
+ Preferences.set("browser.urlbar.suggest.bookmark", false);
+ let suggestOpenPages = Preferences.get("browser.urlbar.suggest.openpage");
+ Preferences.set("browser.urlbar.suggest.openpage", false);
+
+ await SearchTestUtils.installSearchExtension();
+
+ let engine = Services.search.getEngineByName("Example");
+ let originalEngine = await Services.search.getDefault();
+ await Services.search.setDefault(
+ engine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ async function cleanup() {
+ Preferences.set("browser.urlbar.suggest.history", suggestHistory);
+ Preferences.set("browser.urlbar.suggest.bookmark", suggestBookmarks);
+ Preferences.set("browser.urlbar.suggest.openpage", suggestOpenPages);
+
+ await Services.search.setDefault(
+ originalEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ }
+ registerCleanupFunction(cleanup);
+
+ gURLBar.focus();
+ gURLBar.value = "e";
+ EventUtils.sendString("x");
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ info("wait for the page to load");
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedTab.linkedBrowser,
+ false,
+ "https://example.com/?q=ex"
+ );
+ await cleanup();
+ })
+);
+
+// Tests that setting a high value for browser.urlbar.delay does not delay the
+// fetching of heuristic results.
+add_task(
+ taskWithNewTab(async function test_delay() {
+ // This is needed to clear the current value, otherwise autocomplete may think
+ // the user removed text from the end.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ // Set a large delay.
+ const TIMEOUT = 3000;
+ let delay = UrlbarPrefs.get("delay");
+ UrlbarPrefs.set("delay", TIMEOUT);
+ registerCleanupFunction(function () {
+ UrlbarPrefs.set("delay", delay);
+ });
+
+ gURLBar.focus();
+ gURLBar.value = "e";
+ let recievedResult = new Promise(resolve => {
+ gURLBar.controller.addQueryListener({
+ onQueryResults(queryContext) {
+ gURLBar.controller.removeQueryListener(this);
+ Assert.ok(
+ queryContext.heuristicResult,
+ "Recieved a heuristic result."
+ );
+ Assert.equal(
+ queryContext.searchString,
+ "ex",
+ "The heuristic result is based on the correct search string."
+ );
+ resolve();
+ },
+ });
+ });
+ let start = Cu.now();
+ EventUtils.sendString("x");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await recievedResult;
+ Assert.ok(Cu.now() - start < TIMEOUT);
+ })
+);
+
+// The main reason for running each test task in a new tab that's closed when
+// the task finishes is to avoid switch-to-tab results.
+function taskWithNewTab(fn) {
+ return async function () {
+ await BrowserTestUtils.withNewTab("about:blank", fn);
+ };
+}
diff --git a/browser/components/urlbar/tests/browser/browser_autocomplete_no_title.js b/browser/components/urlbar/tests/browser/browser_autocomplete_no_title.js
new file mode 100644
index 0000000000..fa30a7608a
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_no_title.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test ensures that we display just the domain name when a URL result doesn't
+ * have a title.
+ */
+
+add_task(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+ await PlacesUtils.history.clear();
+ const uri = "http://bug1060642.example.com/beards/are/pretty/great";
+ await PlacesTestUtils.addVisits([{ uri, title: "" }]);
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ BrowserTestUtils.removeTab(tab);
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "bug1060642",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ result.displayed.title,
+ "bug1060642.example.com",
+ "Result title should be as expected"
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autocomplete_readline_navigation.js b/browser/components/urlbar/tests/browser/browser_autocomplete_readline_navigation.js
new file mode 100644
index 0000000000..36f990503e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_readline_navigation.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests navigation between results using ctrl-n/p.
+ */
+
+function repeat(limit, func) {
+ for (let i = 0; i < limit; i++) {
+ func(i);
+ }
+}
+
+function assertSelected(index) {
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ index,
+ "Should have the correct item selected"
+ );
+
+ // This is true because although both the listbox and the one-offs can have
+ // selections, the test doesn't check that.
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtons(window).selectedButton,
+ null,
+ "A result is selected, so the one-offs should not have a selection"
+ );
+}
+
+add_task(async function () {
+ let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ BrowserTestUtils.removeTab(tab);
+ });
+
+ let visits = [];
+ repeat(maxResults, i => {
+ visits.push({
+ uri: makeURI("http://example.com/autocomplete/?" + i),
+ });
+ });
+ await PlacesTestUtils.addVisits(visits);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "example.com/autocomplete",
+ });
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, maxResults - 1);
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ maxResults,
+ "Should get maxResults=" + maxResults + " results"
+ );
+ assertSelected(0);
+
+ info("Ctrl-n to select the next item");
+ EventUtils.synthesizeKey("n", { ctrlKey: true });
+ assertSelected(1);
+
+ info("Ctrl-p to select the previous item");
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ assertSelected(0);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_autocomplete_tag_star_visibility.js b/browser/components/urlbar/tests/browser/browser_autocomplete_tag_star_visibility.js
new file mode 100644
index 0000000000..10e26f6f71
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_tag_star_visibility.js
@@ -0,0 +1,167 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests for the bookmark star being correct displayed for results matching
+ * tags.
+ */
+
+add_task(async function () {
+ registerCleanupFunction(async function () {
+ await PlacesUtils.bookmarks.eraseEverything();
+ });
+
+ async function addTagItem(tagName) {
+ let url = `http://example.com/this/is/tagged/${tagName}`;
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url,
+ title: `test ${tagName}`,
+ });
+ PlacesUtils.tagging.tagURI(Services.io.newURI(url), [tagName]);
+ await PlacesTestUtils.addVisits({
+ uri: url,
+ title: `Test page with tag ${tagName}`,
+ });
+ }
+
+ // We use different tags for each part of the test, as otherwise the
+ // autocomplete code tries to be smart by using the previously cached element
+ // without updating it (since all parameters it knows about are the same).
+
+ let testcases = [
+ {
+ description: "Test with suggest.bookmark=true",
+ tagName: "tagtest1",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "tagtest1",
+ expected: {
+ typeImageVisible: true,
+ },
+ },
+ {
+ description: "Test with suggest.bookmark=false",
+ tagName: "tagtest2",
+ prefs: {
+ "suggest.bookmark": false,
+ },
+ input: "tagtest2",
+ expected: {
+ typeImageVisible: false,
+ },
+ },
+ {
+ description: "Test with suggest.bookmark=true (again)",
+ tagName: "tagtest3",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "tagtest3",
+ expected: {
+ typeImageVisible: true,
+ },
+ },
+ {
+ description: "Test with bookmark restriction token",
+ tagName: "tagtest4",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "* tagtest4",
+ expected: {
+ typeImageVisible: true,
+ },
+ },
+ {
+ description: "Test with history restriction token",
+ tagName: "tagtest5",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "^ tagtest5",
+ expected: {
+ typeImageVisible: false,
+ },
+ },
+ {
+ description: "Test partial tag and casing",
+ tagName: "tagtest6",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "TeSt6",
+ expected: {
+ typeImageVisible: true,
+ },
+ },
+ ];
+
+ for (let testcase of testcases) {
+ info(`Test case: ${testcase.description}`);
+
+ await addTagItem(testcase.tagName);
+ for (let prefName of Object.keys(testcase.prefs)) {
+ Services.prefs.setBoolPref(
+ `browser.urlbar.${prefName}`,
+ testcase.prefs[prefName]
+ );
+ }
+
+ let context = await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: testcase.input,
+ });
+
+ // If testcase.input triggers local search mode, there won't be a heuristic.
+ let resultIndex =
+ context.searchMode && !context.searchMode.engineName ? 0 : 1;
+
+ Assert.greaterOrEqual(
+ UrlbarTestUtils.getResultCount(window),
+ resultIndex + 1,
+ `Should be at least ${resultIndex + 1} results`
+ );
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ resultIndex
+ );
+
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.URL,
+ "Should have a URL result type"
+ );
+ // The Quantum Bar differs from the legacy urlbar in the fact that, if
+ // bookmarks are filtered out, it won't show tags for history results.
+ let expected_tags = !testcase.expected.typeImageVisible
+ ? []
+ : [testcase.tagName];
+ Assert.deepEqual(
+ result.tags,
+ expected_tags,
+ "Should have the expected tag"
+ );
+
+ if (testcase.expected.typeImageVisible) {
+ Assert.equal(
+ result.displayed.typeIcon,
+ 'url("chrome://browser/skin/bookmark-12.svg")',
+ "Should have the star image displayed or not as expected"
+ );
+ } else {
+ Assert.equal(
+ result.displayed.typeIcon,
+ "none",
+ "Should have the star image displayed or not as expected"
+ );
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ gURLBar.handleRevert();
+ }
+});
diff --git a/browser/components/urlbar/tests/browser/browser_bestMatch.js b/browser/components/urlbar/tests/browser/browser_bestMatch.js
new file mode 100644
index 0000000000..1e384e389f
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_bestMatch.js
@@ -0,0 +1,229 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests best match rows in the view. See also:
+//
+// browser_quicksuggest_bestMatch.js
+// UI test for quick suggest best matches specifically
+// test_quicksuggest_bestMatch.js
+// Tests triggering quick suggest best matches and things that don't depend on
+// the view
+
+"use strict";
+
+// Tests a non-sponsored best match row.
+add_task(async function nonsponsored() {
+ let result = makeBestMatchResult();
+ await withProvider(result, async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkBestMatchRow({ result });
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+// Tests a non-sponsored best match row with a help button.
+add_task(async function nonsponsoredHelpButton() {
+ let result = makeBestMatchResult({ helpUrl: "https://example.com/help" });
+ await withProvider(result, async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkBestMatchRow({ result, hasHelpUrl: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+// Tests a sponsored best match row.
+add_task(async function sponsored() {
+ let result = makeBestMatchResult({ isSponsored: true });
+ await withProvider(result, async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkBestMatchRow({ result, isSponsored: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+// Tests a sponsored best match row with a help button.
+add_task(async function sponsoredHelpButton() {
+ let result = makeBestMatchResult({
+ isSponsored: true,
+ helpUrl: "https://example.com/help",
+ });
+ await withProvider(result, async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkBestMatchRow({ result, isSponsored: true, hasHelpUrl: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+// Tests keyboard selection.
+add_task(async function keySelection() {
+ let result = makeBestMatchResult({
+ isSponsored: true,
+ helpUrl: "https://example.com/help",
+ });
+
+ await withProvider(result, async () => {
+ // Ordered list of class names of the elements that should be selected.
+ let expectedClassNames = [
+ "urlbarView-row-inner",
+ UrlbarPrefs.get("resultMenu")
+ ? "urlbarView-button-menu"
+ : "urlbarView-button-help",
+ ];
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkBestMatchRow({
+ result,
+ isSponsored: true,
+ hasHelpUrl: true,
+ });
+
+ // Test with the tab key in order vs. reverse order.
+ for (let reverse of [false, true]) {
+ info("Doing TAB key selection: " + JSON.stringify({ reverse }));
+
+ let classNames = [...expectedClassNames];
+ if (reverse) {
+ classNames.reverse();
+ }
+
+ let sendKey = () => {
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: reverse });
+ };
+
+ // Move selection through each expected element.
+ for (let className of classNames) {
+ info("Expecting selection: " + className);
+ sendKey();
+ Assert.ok(gURLBar.view.isOpen, "View remains open");
+ let { selectedElement } = gURLBar.view;
+ Assert.ok(selectedElement, "Selected element exists");
+ Assert.ok(
+ selectedElement.classList.contains(className),
+ "Expected element is selected"
+ );
+ }
+ sendKey();
+ Assert.ok(
+ gURLBar.view.isOpen,
+ "View remains open after keying through best match row"
+ );
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+async function checkBestMatchRow({
+ result,
+ isSponsored = false,
+ hasHelpUrl = false,
+}) {
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 1,
+ "One result is present"
+ );
+
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ let { row } = details.element;
+
+ Assert.equal(row.getAttribute("type"), "bestmatch", "row[type] is bestmatch");
+
+ let favicon = row._elements.get("favicon");
+ Assert.ok(favicon, "Row has a favicon");
+
+ let title = row._elements.get("title");
+ Assert.ok(title, "Row has a title");
+ Assert.ok(title.textContent, "Row title has non-empty textContext");
+ Assert.equal(title.textContent, result.payload.title, "Row title is correct");
+
+ let url = row._elements.get("url");
+ Assert.ok(url, "Row has a URL");
+ Assert.ok(url.textContent, "Row URL has non-empty textContext");
+ Assert.equal(
+ url.textContent,
+ result.payload.displayUrl,
+ "Row URL is correct"
+ );
+
+ let bottom = row._elements.get("bottom");
+ Assert.ok(bottom, "Row has a bottom");
+ Assert.equal(
+ !!result.payload.isSponsored,
+ isSponsored,
+ "Sanity check: Row's expected isSponsored matches result's"
+ );
+ if (isSponsored) {
+ Assert.equal(
+ bottom.textContent,
+ "Sponsored",
+ "Sponsored row bottom has Sponsored textContext"
+ );
+ } else {
+ Assert.equal(
+ bottom.textContent,
+ "",
+ "Non-sponsored row bottom has empty textContext"
+ );
+ }
+
+ let button = row._buttons.get(
+ UrlbarPrefs.get("resultMenu") ? "menu" : "help"
+ );
+ Assert.equal(
+ !!result.payload.helpUrl,
+ hasHelpUrl,
+ "Sanity check: Row's expected hasHelpUrl matches result"
+ );
+ if (hasHelpUrl) {
+ Assert.ok(button, "Row with helpUrl has a help or menu button");
+ } else {
+ Assert.ok(
+ !button,
+ "Row without helpUrl does not have a help or menu button"
+ );
+ }
+}
+
+async function withProvider(result, callback) {
+ let provider = new UrlbarTestUtils.TestProvider({
+ results: [result],
+ priority: Infinity,
+ });
+ UrlbarProvidersManager.registerProvider(provider);
+ try {
+ await callback();
+ } finally {
+ UrlbarProvidersManager.unregisterProvider(provider);
+ }
+}
+
+function makeBestMatchResult(payloadExtra = {}) {
+ return Object.assign(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.SEARCH,
+ ...UrlbarResult.payloadAndSimpleHighlights([], {
+ title: "Test best match",
+ url: "https://example.com/best-match",
+ ...payloadExtra,
+ })
+ ),
+ { isBestMatch: true }
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/browser_blanking.js b/browser/components/urlbar/tests/browser/browser_blanking.js
new file mode 100644
index 0000000000..eabaa2575d
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_blanking.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URL = `${TEST_BASE_URL}file_blank_but_not_blank.html`;
+
+add_task(async function () {
+ for (let page of gInitialPages) {
+ if (page == "about:newtab") {
+ // New tab preloading makes this a pain to test, so skip
+ continue;
+ }
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, page);
+ ok(
+ !gURLBar.value,
+ "The URL bar should be empty if we load a plain " + page + " page."
+ );
+ BrowserTestUtils.removeTab(tab);
+ }
+});
+
+add_task(async function () {
+ // The test was originally to check that reloading of a javascript: URL could
+ // throw an error and empty the URL bar. This situation can no longer happen
+ // as in bug 836567 we set document.URL to active document's URL on navigation
+ // to a javascript: URL; reloading after that will simply reload the original
+ // active document rather than the javascript: URL itself. But we can still
+ // verify that the URL bar's value is correct.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+ is(gURLBar.value, TEST_URL, "The URL bar should match the URI");
+ let browserLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.document.querySelector("a").click();
+ });
+ await browserLoaded;
+ is(
+ gURLBar.value,
+ TEST_URL,
+ "The URL bar should be the previous active document's URI."
+ );
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ // This is sync, so by the time we return we should have changed the URL bar.
+ content.location.reload();
+ }).catch(e => {
+ // Ignore expected exception.
+ });
+ is(
+ gURLBar.value,
+ TEST_URL,
+ "The URL bar should still be the previous active document's URI."
+ );
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_bufferer_onQueryResults.js b/browser/components/urlbar/tests/browser/browser_bufferer_onQueryResults.js
new file mode 100644
index 0000000000..7325d44b2c
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_bufferer_onQueryResults.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This test covers a race condition of input events followed by Enter.
+// The test is putting the event bufferer in a situation where a new query has
+// already results in the context object, but onQueryResults has not been
+// invoked yet. The EventBufferer should wait for onQueryResults to proceed,
+// otherwise the view cannot yet contain the updated query string and we may
+// end up searching for a partial string.
+
+add_setup(async function () {
+ sandbox = sinon.createSandbox();
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ setAsDefault: true,
+ });
+ // To reproduce the race condition it's important to disable any provider
+ // having `deferUserSelection` == true;
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.engines", false]],
+ });
+ await PlacesUtils.history.clear();
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ sandbox.restore();
+ });
+});
+
+add_task(async function test() {
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "about:robots",
+ });
+
+ let defer = PromiseUtils.defer();
+ let waitFirstSearchResults = PromiseUtils.defer();
+ let count = 0;
+ let original = gURLBar.controller.notify;
+ sandbox.stub(gURLBar.controller, "notify").callsFake(async (msg, context) => {
+ if (context?.deferUserSelectionProviders.size) {
+ Assert.ok(false, "Any provider deferring selection should be disabled");
+ }
+ if (msg == "onQueryResults") {
+ waitFirstSearchResults.resolve();
+ count++;
+ }
+ // Delay any events after the second onQueryResults call.
+ if (count >= 2) {
+ await defer.promise;
+ }
+ return original.call(gURLBar.controller, msg, context);
+ });
+
+ gURLBar.focus();
+ gURLBar.select();
+ EventUtils.synthesizeKey("t", {});
+ await waitFirstSearchResults.promise;
+ EventUtils.synthesizeKey("e", {});
+
+ let promiseLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter", {});
+
+ let context = await UrlbarTestUtils.promiseSearchComplete(window);
+ await TestUtils.waitForCondition(
+ () => context.results.length,
+ "Waiting for any result in the QueryContext"
+ );
+ info("Simulate a request to replay deferred events at this point");
+ gURLBar.eventBufferer.replayDeferredEvents(true);
+
+ defer.resolve();
+ await promiseLoaded;
+
+ let expectedURL = UrlbarPrefs.isPersistedSearchTermsEnabled()
+ ? "http://mochi.test:8888/?terms=" + gURLBar.value
+ : gURLBar.untrimmedValue;
+ Assert.equal(gBrowser.selectedBrowser.currentURI.spec, expectedURL);
+
+ BrowserTestUtils.removeTab(tab);
+ sandbox.restore();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_calculator.js b/browser/components/urlbar/tests/browser/browser_calculator.js
new file mode 100644
index 0000000000..899cbc6d5b
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_calculator.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FORMULA = "8 * 8";
+const RESULT = "64";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.calculator", true]],
+ });
+});
+
+add_task(async function test_calculator() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: FORMULA,
+ });
+
+ let result = (await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1))
+ .result;
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.DYNAMIC);
+ Assert.equal(result.payload.input, FORMULA);
+ Assert.equal(result.payload.value, RESULT);
+
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+
+ // Ensure the RESULT get written to the clipboard when selected.
+ await SimpleTest.promiseClipboardChange(RESULT, () => {
+ EventUtils.synthesizeKey("KEY_Enter");
+ });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_canonizeURL.js b/browser/components/urlbar/tests/browser/browser_canonizeURL.js
new file mode 100644
index 0000000000..04f153e7d2
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_canonizeURL.js
@@ -0,0 +1,277 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests turning non-url-looking values typed in the input field into proper URLs.
+ */
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+add_task(async function checkCtrlWorks() {
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ });
+
+ let defaultEngine = await Services.search.getDefault();
+ let testcases = [
+ ["example", "https://www.example.com/", { ctrlKey: true }],
+ // Check that a direct load is not overwritten by a previous canonization.
+ ["http://example.com/test/", "http://example.com/test/", {}],
+ ["ex-ample", "https://www.ex-ample.com/", { ctrlKey: true }],
+ [" example ", "https://www.example.com/", { ctrlKey: true }],
+ [" example/foo ", "https://www.example.com/foo", { ctrlKey: true }],
+ [
+ " example/foo bar ",
+ "https://www.example.com/foo%20bar",
+ { ctrlKey: true },
+ ],
+ ["example.net", "http://example.net/", { ctrlKey: true }],
+ ["http://example", "http://example/", { ctrlKey: true }],
+ ["example:8080", "http://example:8080/", { ctrlKey: true }],
+ ["ex-ample.foo", "http://ex-ample.foo/", { ctrlKey: true }],
+ ["example.foo/bar ", "http://example.foo/bar", { ctrlKey: true }],
+ ["1.1.1.1", "http://1.1.1.1/", { ctrlKey: true }],
+ ["ftp.example.bar", "http://ftp.example.bar/", { ctrlKey: true }],
+ [
+ "ex ample",
+ defaultEngine.getSubmission("ex ample", null, "keyword").uri.spec,
+ { ctrlKey: true },
+ ],
+ ];
+
+ // Disable autoFill for this test, since it could mess up the results.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.autoFill", false],
+ ["browser.urlbar.ctrlCanonizesURLs", true],
+ ],
+ });
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ for (let [inputValue, expectedURL, options] of testcases) {
+ info(`Testing input string: "${inputValue}" - expected: "${expectedURL}"`);
+ let promiseLoad = BrowserTestUtils.waitForDocLoadAndStopIt(
+ expectedURL,
+ win.gBrowser.selectedBrowser
+ );
+ let promiseStopped = BrowserTestUtils.browserStopped(
+ win.gBrowser.selectedBrowser,
+ undefined,
+ true
+ );
+ win.gURLBar.focus();
+ win.gURLBar.inputField.value = inputValue.slice(0, -1);
+ EventUtils.sendString(inputValue.slice(-1), win);
+ EventUtils.synthesizeKey("KEY_Enter", options, win);
+ await Promise.all([promiseLoad, promiseStopped]);
+ }
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function checkPrefTurnsOffCanonize() {
+ // Add a dummy search engine to avoid hitting the network.
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ setAsDefault: true,
+ });
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ // Ensure we don't end up loading something in the current tab becuase it's empty:
+ let initialTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser: win.gBrowser,
+ opening: "about:mozilla",
+ });
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.ctrlCanonizesURLs", false]],
+ });
+
+ let newURL = "http://mochi.test:8888/?terms=example";
+ // On MacOS CTRL+Enter is not supposed to open in a new tab, because it uses
+ // CMD+Enter for that.
+ let promiseLoaded =
+ AppConstants.platform == "macosx"
+ ? BrowserTestUtils.browserLoaded(
+ win.gBrowser.selectedBrowser,
+ false,
+ newURL
+ )
+ : BrowserTestUtils.waitForNewTab(win.gBrowser);
+
+ win.gURLBar.focus();
+ win.gURLBar.selectionStart = win.gURLBar.selectionEnd =
+ win.gURLBar.inputField.value.length;
+ win.gURLBar.inputField.value = "exampl";
+ EventUtils.sendString("e", win);
+ EventUtils.synthesizeKey("KEY_Enter", { ctrlKey: true }, win);
+
+ await promiseLoaded;
+ if (AppConstants.platform == "macosx") {
+ Assert.equal(
+ initialTab.linkedBrowser.currentURI.spec,
+ newURL,
+ "Original tab should have navigated"
+ );
+ } else {
+ Assert.equal(
+ initialTab.linkedBrowser.currentURI.spec,
+ "about:mozilla",
+ "Original tab shouldn't have navigated"
+ );
+ Assert.equal(
+ win.gBrowser.selectedBrowser.currentURI.spec,
+ newURL,
+ "New tab should have navigated"
+ );
+ }
+ while (win.gBrowser.tabs.length > 1) {
+ win.gBrowser.removeTab(win.gBrowser.selectedTab, { animate: false });
+ }
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function autofill() {
+ // Re-enable autofill and canonization.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.autoFill", true],
+ ["browser.urlbar.ctrlCanonizesURLs", true],
+ ],
+ });
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ // Quantumbar automatically disables autofill when the old search string
+ // starts with the new search string, so to make sure that doesn't happen and
+ // that earlier tests don't conflict with this one, start a new search for
+ // some other string.
+ win.gURLBar.select();
+ EventUtils.sendString("blah", win);
+
+ // Add a visit that will be autofilled.
+ await PlacesUtils.history.clear();
+ await PlacesTestUtils.addVisits([
+ {
+ uri: "http://example.com/",
+ transition: PlacesUtils.history.TRANSITIONS.TYPED,
+ },
+ ]);
+
+ let testcases = [
+ ["ex", "https://www.ex.com/", { ctrlKey: true }],
+ // Check that a direct load is not overwritten by a previous canonization.
+ ["ex", "http://example.com/", {}],
+ // search alias
+ ["@goo", "https://www.goo.com/", { ctrlKey: true }],
+ ];
+
+ function promiseAutofill() {
+ return BrowserTestUtils.waitForEvent(win.gURLBar.inputField, "select");
+ }
+
+ for (let [inputValue, expectedURL, options] of testcases) {
+ let promiseLoad = BrowserTestUtils.waitForDocLoadAndStopIt(
+ expectedURL,
+ win.gBrowser.selectedBrowser
+ );
+ win.gURLBar.select();
+ let autofillPromise = promiseAutofill();
+ EventUtils.sendString(inputValue, win);
+ await autofillPromise;
+ EventUtils.synthesizeKey("KEY_Enter", options, win);
+ await promiseLoad;
+
+ // Here again, make sure autofill isn't disabled for the next search. See
+ // the comment above.
+ win.gURLBar.select();
+ EventUtils.sendString("blah", win);
+ }
+
+ await PlacesUtils.history.clear();
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function () {
+ info(
+ "Test whether canonization is disabled until the ctrl key is releasing if the key was used to paste text into urlbar"
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.ctrlCanonizesURLs", true]],
+ });
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ info("Paste the word to the urlbar");
+ const testWord = "example";
+ simulatePastingToUrlbar(testWord, win);
+ is(win.gURLBar.value, testWord, "Paste the test word correctly");
+
+ info("Send enter key while pressing the ctrl key");
+ EventUtils.synthesizeKey("VK_RETURN", { ctrlKey: true }, win);
+ await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ is(
+ win.gBrowser.selectedBrowser.documentURI.spec,
+ `http://mochi.test:8888/?terms=${testWord}`,
+ "The loaded url is not canonized"
+ );
+ EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" }, win);
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function () {
+ info("Test whether canonization is enabled again after releasing the ctrl");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.ctrlCanonizesURLs", true]],
+ });
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ info("Paste the word to the urlbar");
+ const testWord = "example";
+ simulatePastingToUrlbar(testWord, win);
+ is(win.gURLBar.value, testWord, "Paste the test word correctly");
+
+ info("Release the ctrl key befoer typing Enter key");
+ EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" }, win);
+
+ info("Send enter key with the ctrl");
+ const onLoad = BrowserTestUtils.waitForDocLoadAndStopIt(
+ `https://www.${testWord}.com/`,
+ win.gBrowser.selectedBrowser
+ );
+ const onStop = BrowserTestUtils.browserStopped(
+ win.gBrowser.selectedBrowser,
+ undefined,
+ true
+ );
+ EventUtils.synthesizeKey("VK_RETURN", { ctrlKey: true }, win);
+ await Promise.all([onLoad, onStop]);
+ info("The loaded url is canonized");
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+function simulatePastingToUrlbar(text, win) {
+ win.gURLBar.focus();
+
+ const keyForPaste = win.document
+ .getElementById("key_paste")
+ .getAttribute("key")
+ .toLowerCase();
+ EventUtils.synthesizeKey(
+ keyForPaste,
+ { type: "keydown", ctrlKey: true },
+ win
+ );
+
+ win.gURLBar.select();
+ EventUtils.sendString(text, win);
+}
diff --git a/browser/components/urlbar/tests/browser/browser_caret_position.js b/browser/components/urlbar/tests/browser/browser_caret_position.js
new file mode 100644
index 0000000000..35eee55efe
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_caret_position.js
@@ -0,0 +1,359 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const LARGE_DATA_URL =
+ "data:text/plain," + [...Array(1000)].map(() => "0123456789").join("");
+
+// Tests for the caret position after gURLBar.setURI().
+add_task(async function setURI() {
+ const testData = [
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.com/test",
+ initialSelectionStart: 0,
+ initialSelectionEnd: 0,
+ expectedSelectionStart: 0,
+ expectedSelectionEnd: 0,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.com/test",
+ initialSelectionStart: 20,
+ initialSelectionEnd: 20,
+ expectedSelectionStart: 20,
+ expectedSelectionEnd: 20,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.com/test",
+ initialSelectionStart: 1,
+ initialSelectionEnd: 20,
+ expectedSelectionStart: 1,
+ expectedSelectionEnd: 20,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.com/test",
+ initialSelectionStart: "https://example.com/test".length,
+ initialSelectionEnd: "https://example.com/test".length,
+ expectedSelectionStart: "https://example.com/test".length,
+ expectedSelectionEnd: "https://example.com/test".length,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.com/test",
+ initialSelectionStart: 0,
+ initialSelectionEnd: "https://example.com/test".length,
+ expectedSelectionStart: 0,
+ expectedSelectionEnd: "https://example.com/test".length,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.org/test",
+ initialSelectionStart: 0,
+ initialSelectionEnd: 0,
+ expectedSelectionStart: 0,
+ expectedSelectionEnd: 0,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.org/test",
+ initialSelectionStart: 20,
+ initialSelectionEnd: 20,
+ expectedSelectionStart: 20,
+ expectedSelectionEnd: 20,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.org/test",
+ initialSelectionStart: 1,
+ initialSelectionEnd: 10,
+ expectedSelectionStart: 1,
+ expectedSelectionEnd: 10,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.org/test",
+ initialSelectionStart: "https://example.".length,
+ initialSelectionEnd: "https://example.c".length,
+ expectedSelectionStart: "https://example.c".length,
+ expectedSelectionEnd: "https://example.c".length,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.org/test",
+ initialSelectionStart: "https://example.com/test".length,
+ initialSelectionEnd: "https://example.com/test".length,
+ expectedSelectionStart: "https://example.org/test".length,
+ expectedSelectionEnd: "https://example.org/test".length,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.org/test",
+ initialSelectionStart: 0,
+ initialSelectionEnd: "https://example.com/test".length,
+ expectedSelectionStart: "https://example.org/test".length,
+ expectedSelectionEnd: "https://example.org/test".length,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.com/longer",
+ initialSelectionStart: "https://example.com/test".length,
+ initialSelectionEnd: "https://example.com/test".length,
+ expectedSelectionStart: "https://example.com/longer".length,
+ expectedSelectionEnd: "https://example.com/longer".length,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "https://example.com/longer",
+ initialSelectionStart: 20,
+ initialSelectionEnd: 20,
+ expectedSelectionStart: 20,
+ expectedSelectionEnd: 20,
+ },
+ {
+ firstURL: "https://example.com/longer",
+ secondURL: "https://example.com/test",
+ initialSelectionStart: 0,
+ initialSelectionEnd: "https://example.com/longer".length,
+ expectedSelectionStart: "https://example.com/test".length,
+ expectedSelectionEnd: "https://example.com/test".length,
+ },
+ {
+ firstURL: "https://example.com/longer",
+ secondURL: "https://example.com/test",
+ initialSelectionStart: "https://example.com/longer".length,
+ initialSelectionEnd: "https://example.com/longer".length,
+ expectedSelectionStart: "https://example.com/test".length,
+ expectedSelectionEnd: "https://example.com/test".length,
+ },
+ {
+ firstURL: "https://example.com/longer",
+ secondURL: "https://example.com/test",
+ initialSelectionStart: "https://example.com/longer".length - 1,
+ initialSelectionEnd: "https://example.com/longer".length - 1,
+ expectedSelectionStart: "https://example.com/test".length,
+ expectedSelectionEnd: "https://example.com/test".length,
+ },
+ {
+ firstURL: "https://example.com/longer",
+ secondURL: "https://example.com/test",
+ initialSelectionStart: 0,
+ initialSelectionEnd: "https://example.com/longer".length - 1,
+ expectedSelectionStart: "https://example.com/test".length,
+ expectedSelectionEnd: "https://example.com/test".length,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "about:blank",
+ initialSelectionStart: 0,
+ initialSelectionEnd: 0,
+ expectedSelectionStart: 0,
+ expectedSelectionEnd: 0,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "about:blank",
+ initialSelectionStart: 0,
+ initialSelectionEnd: "https://example.com/test".length,
+ expectedSelectionStart: 0,
+ expectedSelectionEnd: 0,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "about:blank",
+ initialSelectionStart: 3,
+ initialSelectionEnd: 4,
+ expectedSelectionStart: 0,
+ expectedSelectionEnd: 0,
+ },
+ {
+ firstURL: "https://example.com/test",
+ secondURL: "about:blank",
+ initialSelectionStart: "https://example.com/test".length,
+ initialSelectionEnd: "https://example.com/test".length,
+ expectedSelectionStart: 0,
+ expectedSelectionEnd: 0,
+ },
+ {
+ firstURL: "about:blank",
+ secondURL: "https://example.com/test",
+ initialSelectionStart: 0,
+ initialSelectionEnd: 0,
+ expectedSelectionStart: 0,
+ expectedSelectionEnd: 0,
+ },
+ {
+ firstURL: "about:blank",
+ secondURL: LARGE_DATA_URL,
+ initialSelectionStart: 0,
+ initialSelectionEnd: 0,
+ expectedSelectionStart: 0,
+ expectedSelectionEnd: 0,
+ },
+ {
+ firstURL: "about:telemetry",
+ secondURL: LARGE_DATA_URL,
+ initialSelectionStart: "about:telemetry".length,
+ initialSelectionEnd: "about:telemetry".length,
+ expectedSelectionStart: LARGE_DATA_URL.length,
+ expectedSelectionEnd: LARGE_DATA_URL.length,
+ },
+ ];
+
+ for (const data of testData) {
+ info(
+ `Test for ${data.firstURL} -> ${data.secondURL} with initial selection: ${data.initialSelectionStart}, ${data.initialSelectionEnd}`
+ );
+ info("Check the caret position after setting second URL");
+ gURLBar.setURI(makeURI(data.firstURL));
+ gURLBar.selectionStart = data.initialSelectionStart;
+ gURLBar.selectionEnd = data.initialSelectionEnd;
+
+ // The change of the scroll amount dependent on the selection change will be
+ // ignored if the previous processing is unfinished yet. Therefore, make the
+ // processing finalize explicitly here.
+ await flushScrollStyle();
+
+ gURLBar.focus();
+ gURLBar.setURI(makeURI(data.secondURL));
+ await flushScrollStyle();
+
+ Assert.equal(gURLBar.selectionStart, data.expectedSelectionStart);
+ Assert.equal(gURLBar.selectionEnd, data.expectedSelectionEnd);
+ if (data.secondURL.length === data.expectedSelectionStart) {
+ // If the caret is at the end of url, the input field shows the end of
+ // text.
+ Assert.equal(
+ gURLBar.inputField.scrollLeft,
+ gURLBar.inputField.scrollLeftMax
+ );
+ }
+
+ info("Check the caret position while the input is not focused");
+ gURLBar.setURI(makeURI(data.firstURL));
+ gURLBar.selectionStart = data.initialSelectionStart;
+ gURLBar.selectionEnd = data.initialSelectionEnd;
+
+ await flushScrollStyle();
+
+ gURLBar.blur();
+ gURLBar.setURI(makeURI(data.secondURL));
+ await flushScrollStyle();
+
+ if (data.firstURL === data.secondURL) {
+ Assert.equal(gURLBar.selectionStart, data.initialSelectionStart);
+ Assert.equal(gURLBar.selectionEnd, data.initialSelectionEnd);
+ } else {
+ Assert.equal(gURLBar.selectionStart, gURLBar.value.length);
+ Assert.equal(gURLBar.selectionEnd, gURLBar.value.length);
+ }
+ Assert.equal(gURLBar.inputField.scrollLeft, 0);
+ }
+});
+
+// Tests that up and down keys move the caret on certain platforms, and that
+// opening the popup doesn't change the caret position.
+add_task(async function navigation() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "This is a generic sentence",
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ const INITIAL_SELECTION_START = 3;
+ const INITIAL_SELECTION_END = 10;
+ gURLBar.selectionStart = INITIAL_SELECTION_START;
+ gURLBar.selectionEnd = INITIAL_SELECTION_END;
+
+ if (AppConstants.platform == "macosx" || AppConstants.platform == "linux") {
+ await checkCaretMoves(
+ "KEY_ArrowDown",
+ gURLBar.value.length,
+ "Caret should have moved to the end",
+ window
+ );
+ await checkPopupOpens("KEY_ArrowDown", window);
+
+ await checkCaretMoves(
+ "KEY_ArrowUp",
+ 0,
+ "Caret should have moved to the start",
+ window
+ );
+ await checkPopupOpens("KEY_ArrowUp", window);
+ } else {
+ await checkPopupOpens("KEY_ArrowDown", window);
+ await checkPopupOpens("KEY_ArrowUp", window);
+ }
+});
+
+async function checkCaretMoves(key, pos, msg, win) {
+ checkIfKeyStartsQuery(key, false, win);
+ Assert.equal(
+ UrlbarTestUtils.isPopupOpen(win),
+ false,
+ `${key}: Popup shouldn't be open`
+ );
+ Assert.equal(
+ win.gURLBar.selectionStart,
+ win.gURLBar.selectionEnd,
+ `${key}: Input selection should be empty`
+ );
+ Assert.equal(win.gURLBar.selectionStart, pos, `${key}: ${msg}`);
+}
+
+async function checkPopupOpens(key, win) {
+ // Store current selection and check it doesn't change.
+ let selectionStart = win.gURLBar.selectionStart;
+ let selectionEnd = win.gURLBar.selectionEnd;
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ checkIfKeyStartsQuery(key, true, win);
+ });
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(win),
+ 0,
+ `${key}: Heuristic result should be selected`
+ );
+ Assert.equal(
+ win.gURLBar.selectionStart,
+ selectionStart,
+ `${key}: Input selection start should not change`
+ );
+ Assert.equal(
+ win.gURLBar.selectionEnd,
+ selectionEnd,
+ `${key}: Input selection end should not change`
+ );
+ await UrlbarTestUtils.promisePopupClose(win);
+}
+
+function checkIfKeyStartsQuery(key, shouldStartQuery, win) {
+ let queryStarted = false;
+ let queryListener = {
+ onQueryStarted() {
+ queryStarted = true;
+ },
+ };
+ win.gURLBar.controller.addQueryListener(queryListener);
+ EventUtils.synthesizeKey(key, {}, win);
+ win.gURLBar.eventBufferer.replayDeferredEvents(false);
+ win.gURLBar.controller.removeQueryListener(queryListener);
+ Assert.equal(
+ queryStarted,
+ shouldStartQuery,
+ `${key}: Should${shouldStartQuery ? "" : "n't"} have started a query`
+ );
+}
+
+async function flushScrollStyle() {
+ // Flush pending notifications for the style.
+ /* eslint-disable no-unused-expressions */
+ gURLBar.inputField.scrollLeft;
+ // Ensure to apply the style.
+ await new Promise(resolve =>
+ gURLBar.inputField.ownerGlobal.requestAnimationFrame(resolve)
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/browser_click_row_border.js b/browser/components/urlbar/tests/browser/browser_click_row_border.js
new file mode 100644
index 0000000000..59915ed3b1
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_click_row_border.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URL = "https://example.com/autocomplete";
+
+add_setup(async function () {
+ await PlacesTestUtils.addVisits(TEST_URL);
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+add_task(async function test_click_row_border() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "example.com/autocomplete",
+ });
+ let resultRow = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+ let loaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ TEST_URL
+ );
+ info("Clicking on the result's top pixel row");
+ EventUtils.synthesizeMouse(
+ resultRow,
+ parseInt(getComputedStyle(resultRow).borderTopLeftRadius) * 2,
+ 1,
+ {}
+ );
+ info("Waiting for page to load");
+ await loaded;
+ ok(true, "Page loaded");
+});
diff --git a/browser/components/urlbar/tests/browser/browser_closePanelOnClick.js b/browser/components/urlbar/tests/browser/browser_closePanelOnClick.js
new file mode 100644
index 0000000000..2bbe412acb
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_closePanelOnClick.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/. */
+
+/**
+ * This tests that the urlbar panel closes when clicking certain ui elements.
+ */
+
+"use strict";
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab("about:robots", async () => {
+ for (let elt of [
+ gBrowser.selectedBrowser,
+ gBrowser.tabContainer,
+ document.querySelector("#nav-bar toolbarspring"),
+ ]) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: "dummy",
+ });
+ // Must have at least one test.
+ Assert.ok(!!elt, "Found a valid element: " + (elt.id || elt.localName));
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeNativeMouseEvent({
+ type: "click",
+ target: elt,
+ atCenter: true,
+ })
+ );
+ }
+ });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_content_opener.js b/browser/components/urlbar/tests/browser/browser_content_opener.js
new file mode 100644
index 0000000000..0cf4865ad7
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_content_opener.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ TEST_BASE_URL + "dummy_page.html",
+ async function (browser) {
+ let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+ await SpecialPowers.spawn(browser, [], function () {
+ content.window.open("", "_BLANK", "toolbar=no,height=300,width=500");
+ });
+ let newWin = await windowOpenedPromise;
+ is(
+ newWin.gURLBar.value,
+ "about:blank",
+ "Should be displaying about:blank for the opened window."
+ );
+ await BrowserTestUtils.closeWindow(newWin);
+ }
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_contextualsearch.js b/browser/components/urlbar/tests/browser/browser_contextualsearch.js
new file mode 100644
index 0000000000..5de1673e6a
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_contextualsearch.js
@@ -0,0 +1,119 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { UrlbarProviderContextualSearch } = ChromeUtils.importESModule(
+ "resource:///modules/UrlbarProviderContextualSearch.sys.mjs"
+);
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.contextualSearch.enabled", true]],
+ });
+});
+
+add_task(async function test_selectContextualSearchResult_already_installed() {
+ await SearchTestUtils.installSearchExtension({
+ name: "Contextual",
+ search_url: "https://example.com/browser",
+ });
+
+ const ENGINE_TEST_URL = "https://example.com/";
+ let onLoaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ ENGINE_TEST_URL
+ );
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, ENGINE_TEST_URL);
+ await onLoaded;
+
+ const query = "search";
+ let engine = Services.search.getEngineByName("Contextual");
+ const [expectedUrl] = UrlbarUtils.getSearchQueryUrl(engine, query);
+
+ Assert.ok(
+ expectedUrl.includes(`?q=${query}`),
+ "Expected URL should be a search URL"
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: query,
+ });
+ const resultIndex = UrlbarTestUtils.getResultCount(window) - 1;
+ const result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ resultIndex
+ );
+
+ is(
+ result.dynamicType,
+ "contextualSearch",
+ "Second last result is a contextual search result"
+ );
+
+ info("Focus and select the contextual search result");
+ UrlbarTestUtils.setSelectedRowIndex(window, resultIndex);
+ let onLoad = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ expectedUrl
+ );
+ EventUtils.synthesizeKey("KEY_Enter");
+ await onLoad;
+
+ Assert.equal(
+ gBrowser.selectedBrowser.currentURI.spec,
+ expectedUrl,
+ "Selecting the contextual search result opens the search URL"
+ );
+});
+
+add_task(async function test_selectContextualSearchResult_not_installed() {
+ const ENGINE_TEST_URL =
+ "http://mochi.test:8888/browser/browser/components/search/test/browser/opensearch.html";
+ const EXPECTED_URL =
+ "http://mochi.test:8888/browser/browser/components/search/test/browser/?search&test=search";
+ let onLoaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ ENGINE_TEST_URL
+ );
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, ENGINE_TEST_URL);
+ await onLoaded;
+
+ const query = "search";
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: query,
+ });
+ const resultIndex = UrlbarTestUtils.getResultCount(window) - 1;
+ const result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ resultIndex
+ );
+
+ Assert.equal(
+ result.dynamicType,
+ "contextualSearch",
+ "Second last result is a contextual search result"
+ );
+
+ info("Focus and select the contextual search result");
+ UrlbarTestUtils.setSelectedRowIndex(window, resultIndex);
+ let onLoad = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ EXPECTED_URL
+ );
+ EventUtils.synthesizeKey("KEY_Enter");
+ await onLoad;
+
+ Assert.equal(
+ gBrowser.selectedBrowser.currentURI.spec,
+ EXPECTED_URL,
+ "Selecting the contextual search result opens the search URL"
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_copy_during_load.js b/browser/components/urlbar/tests/browser/browser_copy_during_load.js
new file mode 100644
index 0000000000..4a81ff08be
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_copy_during_load.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that copying from the urlbar page works correctly after a result is
+// confirmed but takes a while to load.
+
+add_task(async function () {
+ const SLOW_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://www.example.com"
+ ) + "slow-page.sjs";
+
+ await BrowserTestUtils.withNewTab(gBrowser, async tab => {
+ gURLBar.focus();
+ gURLBar.value = SLOW_PAGE;
+ let promise = TestUtils.waitForCondition(
+ () => gURLBar.getAttribute("pageproxystate") == "invalid"
+ );
+ EventUtils.synthesizeKey("KEY_Enter");
+ info("wait for the initial conditions");
+ await promise;
+
+ info("Copy the whole url");
+ await SimpleTest.promiseClipboardChange(SLOW_PAGE, () => {
+ gURLBar.select();
+ goDoCommand("cmd_copy");
+ });
+
+ info("Copy the initial part of the url, as a different valid url");
+ await SimpleTest.promiseClipboardChange(
+ SLOW_PAGE.substring(0, SLOW_PAGE.indexOf("slow-page.sjs")),
+ () => {
+ gURLBar.selectionStart = 0;
+ gURLBar.selectionEnd = gURLBar.value.indexOf("slow-page.sjs");
+ goDoCommand("cmd_copy");
+ }
+ );
+
+ // This is apparently necessary to avoid a timeout on mochitest shutdown(!?)
+ let browserStoppedPromise = BrowserTestUtils.browserStopped(
+ gBrowser,
+ null,
+ true
+ );
+ BrowserStop();
+ await browserStoppedPromise;
+ });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_copying.js b/browser/components/urlbar/tests/browser/browser_copying.js
new file mode 100644
index 0000000000..9c32115fb4
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_copying.js
@@ -0,0 +1,416 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function getUrl(hostname, file) {
+ return (
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ hostname
+ ) + file
+ );
+}
+
+add_task(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+ gURLBar.setURI();
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.trimURLs", true],
+ // avoid prompting about phishing
+ ["network.http.phishy-userpass-length", 32],
+ ],
+ });
+
+ for (let testCase of tests) {
+ if (testCase.setup) {
+ await testCase.setup();
+ }
+
+ if (testCase.loadURL) {
+ info(`Loading : ${testCase.loadURL}`);
+ let expectedLoad = testCase.expectedLoad || testCase.loadURL;
+ BrowserTestUtils.loadURIString(
+ gBrowser.selectedBrowser,
+ testCase.loadURL
+ );
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ expectedLoad
+ );
+ } else if (testCase.setURL) {
+ gURLBar.value = testCase.setURL;
+ }
+ if (testCase.setURL || testCase.loadURL) {
+ gURLBar.valueIsTyped = !!testCase.setURL;
+ is(
+ gURLBar.value,
+ testCase.expectedURL,
+ "url bar value set to " + gURLBar.value
+ );
+ }
+
+ gURLBar.focus();
+ if (testCase.expectedValueOnFocus) {
+ Assert.equal(
+ gURLBar.value,
+ testCase.expectedValueOnFocus,
+ "Check value on focus"
+ );
+ }
+ await testCopy(testCase.copyVal, testCase.copyExpected);
+ gURLBar.blur();
+
+ if (testCase.cleanup) {
+ await testCase.cleanup();
+ }
+ }
+});
+
+var tests = [
+ // pageproxystate="invalid"
+ {
+ setURL: "http://example.com/",
+ expectedURL: "example.com",
+ copyExpected: "example.com",
+ },
+ {
+ copyVal: "<e>xample.com",
+ copyExpected: "e",
+ },
+ {
+ copyVal: "<e>x<a>mple.com",
+ copyExpected: "ea",
+ },
+ {
+ copyVal: "<e><xa>mple.com",
+ copyExpected: "exa",
+ },
+ {
+ copyVal: "<e><xa>mple.co<m>",
+ copyExpected: "exam",
+ },
+ {
+ copyVal: "<e><xample.co><m>",
+ copyExpected: "example.com",
+ },
+
+ // pageproxystate="valid" from this point on (due to the load)
+ {
+ loadURL: "http://example.com/",
+ expectedURL: "example.com",
+ copyExpected: "http://example.com/",
+ },
+ {
+ copyVal: "<example.co>m",
+ copyExpected: "example.co",
+ },
+ {
+ copyVal: "e<x>ample.com",
+ copyExpected: "x",
+ },
+ {
+ copyVal: "<e>xample.com",
+ copyExpected: "e",
+ },
+ {
+ copyVal: "<e>xample.co<m>",
+ copyExpected: "em",
+ },
+ {
+ copyVal: "<exam><ple.com>",
+ copyExpected: "example.com",
+ },
+
+ {
+ loadURL: "http://example.com/foo",
+ expectedURL: "example.com/foo",
+ copyExpected: "http://example.com/foo",
+ },
+ {
+ copyVal: "<example.com>/foo",
+ copyExpected: "http://example.com",
+ },
+ {
+ copyVal: "<example>.com/foo",
+ copyExpected: "example",
+ },
+ // Test that partially selected URL is copied with encoded spaces
+ {
+ loadURL: "http://example.com/%20space/test",
+ expectedURL: "example.com/ space/test",
+ copyExpected: "http://example.com/%20space/test",
+ },
+ {
+ copyVal: "<example.com/ space>/test",
+ copyExpected: "http://example.com/%20space",
+ },
+ {
+ copyVal: "<example.com/ space/test>",
+ copyExpected: "http://example.com/%20space/test",
+ },
+ {
+ loadURL: "http://example.com/%20foo%20bar%20baz/",
+ expectedURL: "example.com/ foo bar baz/",
+ copyExpected: "http://example.com/%20foo%20bar%20baz/",
+ },
+ {
+ copyVal: "<example.com/ foo bar> baz/",
+ copyExpected: "http://example.com/%20foo%20bar",
+ },
+ {
+ copyVal: "example.<com/ foo bar> baz/",
+ copyExpected: "com/ foo bar",
+ },
+
+ // Test that userPass is stripped out
+ {
+ loadURL: getUrl(
+ "http://user:pass@mochi.test:8888",
+ "authenticate.sjs?user=user&pass=pass"
+ ),
+ expectedURL: getUrl(
+ "mochi.test:8888",
+ "authenticate.sjs?user=user&pass=pass"
+ ),
+ copyExpected: getUrl(
+ "http://mochi.test:8888",
+ "authenticate.sjs?user=user&pass=pass"
+ ),
+ },
+
+ // Test escaping
+ {
+ loadURL: "http://example.com/()%28%29%C3%A9",
+ expectedURL: "example.com/()()\xe9",
+ copyExpected: "http://example.com/()%28%29%C3%A9",
+ },
+ {
+ copyVal: "<example.com/(>)()\xe9",
+ copyExpected: "http://example.com/(",
+ },
+ {
+ copyVal: "e<xample.com/(>)()\xe9",
+ copyExpected: "xample.com/(",
+ },
+
+ {
+ loadURL: "http://example.com/%C3%A9%C3%A9",
+ expectedURL: "example.com/\xe9\xe9",
+ copyExpected: "http://example.com/%C3%A9%C3%A9",
+ },
+ {
+ copyVal: "e<xample.com/\xe9>\xe9",
+ copyExpected: "xample.com/\xe9",
+ },
+ {
+ copyVal: "<example.com/\xe9>\xe9",
+ copyExpected: "http://example.com/%C3%A9",
+ },
+ {
+ // Note: it seems BrowserTestUtils.loadURI fails for unicode domains
+ loadURL: "http://sub2.xn--lt-uia.mochi.test:8888/foo",
+ expectedURL: "sub2.ält.mochi.test:8888/foo",
+ copyExpected: "http://sub2.ält.mochi.test:8888/foo",
+ },
+ {
+ copyVal: "s<ub2.ält.mochi.test:8888/f>oo",
+ copyExpected: "ub2.ält.mochi.test:8888/f",
+ },
+ {
+ copyVal: "<sub2.ält.mochi.test:8888/f>oo",
+ copyExpected: "http://sub2.%C3%A4lt.mochi.test:8888/f",
+ },
+
+ {
+ loadURL: "http://example.com/?%C3%B7%C3%B7",
+ expectedURL: "example.com/?\xf7\xf7",
+ copyExpected: "http://example.com/?%C3%B7%C3%B7",
+ },
+ {
+ copyVal: "e<xample.com/?\xf7>\xf7",
+ copyExpected: "xample.com/?\xf7",
+ },
+ {
+ copyVal: "<example.com/?\xf7>\xf7",
+ copyExpected: "http://example.com/?%C3%B7",
+ },
+ {
+ loadURL: "http://example.com/a%20test",
+ expectedURL: "example.com/a test",
+ copyExpected: "http://example.com/a%20test",
+ },
+ {
+ loadURL: "http://example.com/a%E3%80%80test",
+ expectedURL: "example.com/a%E3%80%80test",
+ copyExpected: "http://example.com/a%E3%80%80test",
+ },
+ {
+ loadURL: "http://example.com/a%20%C2%A0test",
+ expectedURL: "example.com/a %C2%A0test",
+ copyExpected: "http://example.com/a%20%C2%A0test",
+ },
+ {
+ loadURL: "http://example.com/%20%20%20",
+ expectedURL: "example.com/%20%20%20",
+ copyExpected: "http://example.com/%20%20%20",
+ },
+ {
+ loadURL: "http://example.com/%E3%80%80%E3%80%80",
+ expectedURL: "example.com/%E3%80%80%E3%80%80",
+ copyExpected: "http://example.com/%E3%80%80%E3%80%80",
+ },
+
+ // Loading of javascript: URI results in previous URI, so if the previous
+ // entry changes, change this one too!
+ {
+ loadURL: "javascript:('%C3%A9%20%25%50')",
+ expectedLoad: "http://example.com/%E3%80%80%E3%80%80",
+ expectedURL: "example.com/%E3%80%80%E3%80%80",
+ copyExpected: "http://example.com/%E3%80%80%E3%80%80",
+ },
+
+ // data: URIs shouldn't be encoded
+ {
+ loadURL: "data:text/html,(%C3%A9%20%25%50)",
+ expectedURL: "data:text/html,(%C3%A9 %25P)",
+ copyExpected: "data:text/html,(%C3%A9 %25P)",
+ },
+ {
+ copyVal: "<data:text/html,(>%C3%A9 %25P)",
+ copyExpected: "data:text/html,(",
+ },
+ {
+ copyVal: "<data:text/html,(%C3%A9 %25P>)",
+ copyExpected: "data:text/html,(%C3%A9 %25P",
+ },
+
+ {
+ async setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.decodeURLsOnCopy", true]],
+ });
+ },
+ async cleanup() {
+ await SpecialPowers.popPrefEnv();
+ },
+ loadURL:
+ "http://example.com/%D0%B1%D0%B8%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%8F",
+ expectedURL: "example.com/биография",
+ copyExpected: "http://example.com/биография",
+ },
+ {
+ copyVal: "<example.com/би>ография",
+ copyExpected: "http://example.com/%D0%B1%D0%B8",
+ },
+
+ {
+ async setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.decodeURLsOnCopy", true]],
+ });
+ // Setup a valid intranet url that resolves but is not yet known.
+ const proxyService = Cc[
+ "@mozilla.org/network/protocol-proxy-service;1"
+ ].getService(Ci.nsIProtocolProxyService);
+ let proxyInfo = proxyService.newProxyInfo(
+ "http",
+ "localhost",
+ 8888,
+ "",
+ "",
+ 0,
+ 4096,
+ null
+ );
+ this._proxyFilter = {
+ applyFilter(channel, defaultProxyInfo, callback) {
+ callback.onProxyFilterResult(
+ channel.URI.host === "mytest" ? proxyInfo : defaultProxyInfo
+ );
+ },
+ };
+ proxyService.registerChannelFilter(this._proxyFilter, 0);
+ registerCleanupFunction(() => {
+ if (this._proxyFilter) {
+ proxyService.unregisterChannelFilter(this._proxyFilter);
+ }
+ });
+ },
+ async cleanup() {
+ await SpecialPowers.popPrefEnv();
+ const proxyService = Cc[
+ "@mozilla.org/network/protocol-proxy-service;1"
+ ].getService(Ci.nsIProtocolProxyService);
+ proxyService.unregisterChannelFilter(this._proxyFilter);
+ this._proxyFilter = null;
+ },
+ loadURL: "http://mytest/",
+ expectedURL: "mytest",
+ expectedValueOnFocus: "http://mytest/",
+ copyExpected: "http://mytest/",
+ },
+
+ {
+ async setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.decodeURLsOnCopy", true]],
+ });
+ },
+ async cleanup() {
+ await SpecialPowers.popPrefEnv();
+ },
+ loadURL: "https://example.com/",
+ expectedURL: "https://example.com",
+ copyExpected: "https://example.com",
+ },
+];
+
+function testCopy(copyVal, targetValue) {
+ info("Expecting copy of: " + targetValue);
+
+ if (copyVal) {
+ let offsets = [];
+ while (true) {
+ let startBracket = copyVal.indexOf("<");
+ let endBracket = copyVal.indexOf(">");
+ if (startBracket == -1 && endBracket == -1) {
+ break;
+ }
+ if (startBracket > endBracket || startBracket == -1) {
+ offsets = [];
+ break;
+ }
+ offsets.push([startBracket, endBracket - 1]);
+ copyVal = copyVal.replace("<", "").replace(">", "");
+ }
+ if (!offsets.length || copyVal != gURLBar.value) {
+ ok(false, "invalid copyVal: " + copyVal);
+ }
+ gURLBar.selectionStart = offsets[0][0];
+ gURLBar.selectionEnd = offsets[0][1];
+ if (offsets.length > 1) {
+ let sel = gURLBar.editor.selection;
+ let r0 = sel.getRangeAt(0);
+ let node0 = r0.startContainer;
+ sel.removeAllRanges();
+ offsets.map(function (startEnd) {
+ let range = r0.cloneRange();
+ range.setStart(node0, startEnd[0]);
+ range.setEnd(node0, startEnd[1]);
+ sel.addRange(range);
+ });
+ }
+ } else {
+ gURLBar.select();
+ }
+
+ return SimpleTest.promiseClipboardChange(targetValue, () =>
+ goDoCommand("cmd_copy")
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/browser_customizeMode.js b/browser/components/urlbar/tests/browser/browser_customizeMode.js
new file mode 100644
index 0000000000..0ed26644cc
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_customizeMode.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test checks that the left/right arrow keys and home/end keys work in
+// the urlbar after customize mode starts and ends.
+
+"use strict";
+
+add_task(async function test() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await startCustomizing(win);
+ await endCustomizing(win);
+
+ let urlbar = win.gURLBar;
+
+ let value = "example";
+ urlbar.value = value;
+ urlbar.focus();
+ urlbar.selectionEnd = value.length;
+ urlbar.selectionStart = value.length;
+
+ // left
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win);
+ Assert.equal(urlbar.selectionStart, value.length - 1);
+ Assert.equal(urlbar.selectionEnd, value.length - 1);
+
+ // home
+ if (AppConstants.platform == "macosx") {
+ EventUtils.synthesizeKey("KEY_ArrowLeft", { metaKey: true }, win);
+ } else {
+ EventUtils.synthesizeKey("KEY_Home", {}, win);
+ }
+ Assert.equal(urlbar.selectionStart, 0);
+ Assert.equal(urlbar.selectionEnd, 0);
+
+ // right
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, win);
+ Assert.equal(urlbar.selectionStart, 1);
+ Assert.equal(urlbar.selectionEnd, 1);
+
+ // end
+ if (AppConstants.platform == "macosx") {
+ EventUtils.synthesizeKey("KEY_ArrowRight", { metaKey: true }, win);
+ } else {
+ EventUtils.synthesizeKey("KEY_End", {}, win);
+ }
+ Assert.equal(urlbar.selectionStart, value.length);
+ Assert.equal(urlbar.selectionEnd, value.length);
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+async function startCustomizing(win = window) {
+ if (win.document.documentElement.getAttribute("customizing") != "true") {
+ let eventPromise = BrowserTestUtils.waitForEvent(
+ win.gNavToolbox,
+ "customizationready"
+ );
+ win.gCustomizeMode.enter();
+ await eventPromise;
+ }
+}
+
+async function endCustomizing(win = window) {
+ if (win.document.documentElement.getAttribute("customizing") == "true") {
+ let eventPromise = BrowserTestUtils.waitForEvent(
+ win.gNavToolbox,
+ "aftercustomization"
+ );
+ win.gCustomizeMode.exit();
+ await eventPromise;
+ }
+}
diff --git a/browser/components/urlbar/tests/browser/browser_cutting.js b/browser/components/urlbar/tests/browser/browser_cutting.js
new file mode 100644
index 0000000000..31b51751d9
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_cutting.js
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ gURLBar.focus();
+ gURLBar.inputField.value = "https://example.com/";
+ gURLBar.selectionStart = 4;
+ gURLBar.selectionEnd = 5;
+ goDoCommand("cmd_cut");
+ is(
+ gURLBar.inputField.value,
+ "http://example.com/",
+ "location bar value after cutting 's' from https"
+ );
+ gURLBar.handleRevert();
+}
diff --git a/browser/components/urlbar/tests/browser/browser_decode.js b/browser/components/urlbar/tests/browser/browser_decode.js
new file mode 100644
index 0000000000..ae0b4dfda1
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_decode.js
@@ -0,0 +1,144 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This test makes sure (1) you can't break the urlbar by typing particular JSON
+// or JS fragments into it, (2) urlbar.textValue shows URLs unescaped, and (3)
+// the urlbar also shows the URLs embedded in action URIs unescaped. See bug
+// 1233672.
+
+add_task(async function injectJSON() {
+ let inputStrs = [
+ 'http://example.com/ ", "url": "bar',
+ "http://example.com/\\",
+ 'http://example.com/"',
+ 'http://example.com/","url":"evil.com',
+ "http://mozilla.org/\\u0020",
+ 'http://www.mozilla.org/","url":1e6,"some-key":"foo',
+ 'http://www.mozilla.org/","url":null,"some-key":"foo',
+ 'http://www.mozilla.org/","url":["foo","bar"],"some-key":"foo',
+ ];
+ for (let inputStr of inputStrs) {
+ await checkInput(inputStr);
+ }
+ gURLBar.value = "";
+ gURLBar.handleRevert();
+ gURLBar.blur();
+});
+
+add_task(function losslessDecode() {
+ let urlNoScheme = "example.com/\u30a2\u30a4\u30a6\u30a8\u30aa";
+ let url = "http://" + urlNoScheme;
+ const result = new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
+ UrlbarUtils.RESULT_SOURCE.TABS,
+ { url }
+ );
+ gURLBar.setValueFromResult({ result });
+ // Since this is directly setting textValue, it is expected to be trimmed.
+ Assert.equal(
+ gURLBar.inputField.value,
+ urlNoScheme,
+ "The string displayed in the textbox should not be escaped"
+ );
+ gURLBar.value = "";
+ gURLBar.handleRevert();
+ gURLBar.blur();
+});
+
+add_task(async function actionURILosslessDecode() {
+ let urlNoScheme = "example.com/\u30a2\u30a4\u30a6\u30a8\u30aa";
+ let url = "http://" + urlNoScheme;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: url,
+ });
+
+ // At this point the heuristic result is selected but the urlbar's value is
+ // simply `url`. Key down and back around until the heuristic result is
+ // selected again.
+ do {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ } while (UrlbarTestUtils.getSelectedRowIndex(window) != 0);
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.URL,
+ "Should have selected a result of URL type"
+ );
+
+ Assert.equal(
+ gURLBar.inputField.value,
+ urlNoScheme,
+ "The string displayed in the textbox should not be escaped"
+ );
+
+ gURLBar.value = "";
+ gURLBar.handleRevert();
+ gURLBar.blur();
+});
+
+add_task(async function test_resultsDisplayDecoded() {
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+
+ await PlacesTestUtils.addVisits("http://example.com/%E9%A1%B5");
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "example",
+ });
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ result.displayed.url,
+ "http://example.com/\u9875",
+ "Should be displayed the correctly unescaped URL"
+ );
+});
+
+async function checkInput(inputStr) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: inputStr,
+ });
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+
+ // URL matches have their param.urls fixed up.
+ let fixupInfo = Services.uriFixup.getFixupURIInfo(
+ inputStr,
+ Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
+ Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
+ );
+ let expectedVisitURL = fixupInfo.fixedURI.spec;
+
+ Assert.equal(result.url, expectedVisitURL, "Should have the correct URL");
+ Assert.equal(
+ result.title,
+ inputStr.replace("\\", "/"),
+ "Should have the correct title"
+ );
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.URL,
+ "Should have be a result of type URL"
+ );
+
+ Assert.equal(
+ result.displayed.title,
+ inputStr.replace("\\", "/"),
+ "Should be displaying the correct text"
+ );
+ let [action] = await document.l10n.formatValues([
+ { id: "urlbar-result-action-visit" },
+ ]);
+ Assert.equal(
+ result.displayed.action,
+ action,
+ "Should be displaying the correct action text"
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/browser_delete.js b/browser/components/urlbar/tests/browser/browser_delete.js
new file mode 100644
index 0000000000..f4a883ea30
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_delete.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test deleting the start of urls works correctly.
+ */
+
+add_task(async function () {
+ let bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://bug1105244.example.com/",
+ title: "test",
+ });
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.bookmarks.remove(bm);
+ });
+
+ await BrowserTestUtils.withNewTab("about:blank", testDelete);
+});
+
+function sendHome() {
+ // unclear why VK_HOME doesn't work on Mac, but it doesn't...
+ if (AppConstants.platform == "macosx") {
+ EventUtils.synthesizeKey("KEY_ArrowLeft", { metaKey: true });
+ } else {
+ EventUtils.synthesizeKey("KEY_Home");
+ }
+}
+
+function sendDelete() {
+ EventUtils.synthesizeKey("KEY_Delete");
+}
+
+async function testDelete() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "bug1105244",
+ });
+
+ // move to the start.
+ sendHome();
+
+ // delete the first few chars - each delete should operate on the input field.
+ await UrlbarTestUtils.promisePopupOpen(window, sendDelete);
+ Assert.equal(gURLBar.inputField.value, "ug1105244.example.com/");
+ sendDelete();
+ Assert.equal(gURLBar.inputField.value, "g1105244.example.com/");
+}
diff --git a/browser/components/urlbar/tests/browser/browser_deleteAllText.js b/browser/components/urlbar/tests/browser/browser_deleteAllText.js
new file mode 100644
index 0000000000..5b355fa477
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_deleteAllText.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test makes sure that deleting all text in the input doesn't mess up
+// subsequent searches.
+
+"use strict";
+
+add_task(async function test() {
+ await runTest();
+ // Setting suggest.topsites to false disables the view's autoOpen behavior,
+ // which changes this test's outcomes.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.topsites", false]],
+ });
+ info("Running the test with autoOpen disabled.");
+ await runTest();
+ await SpecialPowers.popPrefEnv();
+});
+
+async function runTest() {
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+ await PlacesTestUtils.addVisits([
+ "http://example.com/",
+ "http://mozilla.org/",
+ ]);
+
+ // Do an initial search for "x".
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "x",
+ fireInputEvent: true,
+ });
+ await checkResults();
+
+ await deleteInput();
+
+ // Type "x". A new search should start. Don't use
+ // promiseAutocompleteResultPopup, which has some logic that starts the search
+ // manually in certain conditions. We want to specifically check that the
+ // input event causes UrlbarInput to start a new search on its own. If it
+ // doesn't, then the test will hang here on promiseSearchComplete.
+ EventUtils.synthesizeKey("x");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await checkResults();
+
+ // Now repeat the backspace + x two more times. Same thing should happen.
+ for (let i = 0; i < 2; i++) {
+ await deleteInput();
+ EventUtils.synthesizeKey("x");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await checkResults();
+ }
+
+ await deleteInput();
+ // autoOpen opened the panel, so we need to close it.
+ gURLBar.view.close();
+}
+
+async function checkResults() {
+ Assert.equal(await UrlbarTestUtils.getResultCount(window), 2);
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(details.type, UrlbarUtils.RESULT_TYPE.SEARCH);
+ Assert.equal(details.searchParams.query, "x");
+ details = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(details.type, UrlbarUtils.RESULT_TYPE.URL);
+ Assert.equal(details.url, "http://example.com/");
+}
+
+async function deleteInput() {
+ if (UrlbarPrefs.get("suggest.topsites")) {
+ // The popup should remain open and show top sites.
+ while (gURLBar.value.length) {
+ EventUtils.synthesizeKey("KEY_Backspace");
+ }
+ Assert.ok(
+ gURLBar.view.isOpen,
+ "View should remain open when deleting all input text"
+ );
+ let queryContext = await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.notEqual(
+ queryContext.results.length,
+ 0,
+ "View should show results when deleting all input text"
+ );
+ Assert.equal(
+ queryContext.searchString,
+ "",
+ "Results should be for the empty search string (i.e. top sites) when deleting all input text"
+ );
+ } else {
+ // Deleting all text should close the view.
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ while (gURLBar.value.length) {
+ EventUtils.synthesizeKey("KEY_Backspace");
+ }
+ });
+ }
+}
diff --git a/browser/components/urlbar/tests/browser/browser_display_selectedAction_Extensions.js b/browser/components/urlbar/tests/browser/browser_display_selectedAction_Extensions.js
new file mode 100644
index 0000000000..d3a51ede76
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_display_selectedAction_Extensions.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests for the presence of selected action text "Extensions:" in the URL bar.
+ */
+
+add_task(async function testSwitchToTabTextDisplay() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ omnibox: {
+ keyword: "omniboxtest",
+ },
+
+ background() {
+ /* global browser */
+ browser.omnibox.setDefaultSuggestion({
+ description: "doit",
+ });
+ // Just do nothing for this test.
+ },
+ },
+ });
+
+ await extension.startup();
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "omniboxtest ",
+ fireInputEvent: true,
+ });
+
+ // The "Extension:" label appears after a key down followed by a key up
+ // back to the extension result.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+
+ // Checks to see if "Extension:" text in URL bar is visible
+ const extensionText = document.getElementById("urlbar-label-extension");
+ Assert.ok(BrowserTestUtils.is_visible(extensionText));
+ Assert.equal(extensionText.value, "Extension:");
+
+ // Check to see if all other labels are hidden
+ const allLabels = document.getElementById("urlbar-label-box").children;
+ for (let label of allLabels) {
+ if (label != extensionText) {
+ Assert.ok(BrowserTestUtils.is_hidden(label));
+ }
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+ await extension.unload();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_dns_first_for_single_words.js b/browser/components/urlbar/tests/browser/browser_dns_first_for_single_words.js
new file mode 100644
index 0000000000..a0aacc83d2
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_dns_first_for_single_words.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Checks that if browser.fixup.dns_first_for_single_words pref is set, we pass
+// the original search string to the docshell and not a search url.
+
+add_task(async function test() {
+ const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+ );
+ const sandbox = sinon.createSandbox();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.fixup.dns_first_for_single_words", true]],
+ });
+
+ registerCleanupFunction(sandbox.restore);
+
+ /**
+ * Tests the given search string.
+ *
+ * @param {string} str The search string
+ * @param {boolean} passthrough whether the value should be passed unchanged
+ * to the docshell that will first execute a DNS request.
+ */
+ async function testVal(str, passthrough) {
+ sandbox.stub(gURLBar, "_loadURL").callsFake(url => {
+ if (passthrough) {
+ Assert.equal(url, str, "Should pass the unmodified search string");
+ } else {
+ Assert.ok(url.startsWith("http"), "Should pass an url");
+ }
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: str,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+ sandbox.restore();
+ }
+
+ await testVal("test", true);
+ await testVal("te-st", true);
+ await testVal("test ", true);
+ await testVal(" test", true);
+ await testVal(" test", true);
+ await testVal("test.test", true);
+ await testVal("test test", false);
+ // This is not a single word host, though it contains one. At a certain point
+ // we may evaluate to increase coverage of the feature to also ask for this.
+ await testVal("test/test", false);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_downArrowKeySearch.js b/browser/components/urlbar/tests/browser/browser_downArrowKeySearch.js
new file mode 100644
index 0000000000..2f9cc19983
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_downArrowKeySearch.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Checks that pressing the down arrow key starts the proper searches, depending
+// on the input value/state.
+
+"use strict";
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+ // Enough vists to get this site into Top Sites.
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits("http://example.com/");
+ }
+
+ await updateTopSites(
+ sites => sites && sites[0] && sites[0].url == "http://example.com/"
+ );
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+add_task(async function url() {
+ await BrowserTestUtils.withNewTab("http://example.com/", async () => {
+ gURLBar.focus();
+ gURLBar.selectionEnd = gURLBar.untrimmedValue.length;
+ gURLBar.selectionStart = gURLBar.untrimmedValue.length;
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), 0);
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill);
+ Assert.equal(details.url, "http://example.com/");
+ Assert.equal(gURLBar.value, "example.com/");
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+add_task(async function userTyping() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), 0);
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(details.type, UrlbarUtils.RESULT_TYPE.SEARCH);
+ Assert.ok(details.searchParams);
+ Assert.equal(details.searchParams.query, "foo");
+ Assert.equal(gURLBar.value, "foo");
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+add_task(async function empty() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), -1);
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(details.url, "http://example.com/");
+ Assert.equal(gURLBar.value, "");
+});
+
+add_task(async function new_window() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ win.gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(win), -1);
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(win, 0);
+ Assert.equal(details.url, "http://example.com/");
+ Assert.equal(win.gURLBar.value, "");
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_dragdropURL.js b/browser/components/urlbar/tests/browser/browser_dragdropURL.js
new file mode 100644
index 0000000000..52c19e8965
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_dragdropURL.js
@@ -0,0 +1,106 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests for draging and dropping to the Urlbar.
+ */
+
+const TEST_URL = "data:text/html,a test page";
+
+add_task(async function test_setup() {
+ // Stop search-engine loads from hitting the network.
+ await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
+
+ registerCleanupFunction(async function cleanup() {
+ while (gBrowser.tabs.length > 1) {
+ BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
+ }
+ });
+
+ CustomizableUI.addWidgetToArea("home-button", "nav-bar");
+ registerCleanupFunction(() =>
+ CustomizableUI.removeWidgetFromArea("home-button")
+ );
+});
+
+/**
+ * Simulates a drop on the URL bar input field.
+ * The drag source must be something different from the URL bar, so we pick the
+ * home button somewhat arbitrarily.
+ *
+ * @param {object} content a {type, data} object representing the DND content.
+ */
+function simulateURLBarDrop(content) {
+ EventUtils.synthesizeDrop(
+ document.getElementById("home-button"), // Dragstart element.
+ gURLBar.inputField, // Drop element.
+ [[content]], // Drag data.
+ "copy",
+ window
+ );
+}
+
+add_task(async function checkDragURL() {
+ await BrowserTestUtils.withNewTab(TEST_URL, function (browser) {
+ info("Check dragging a normal url to the urlbar");
+ const DRAG_URL = "http://www.example.com/";
+ simulateURLBarDrop({ type: "text/plain", data: DRAG_URL });
+ Assert.equal(
+ gURLBar.value,
+ TEST_URL,
+ "URL bar value should not have changed"
+ );
+ Assert.equal(
+ gBrowser.selectedBrowser.userTypedValue,
+ null,
+ "Stored URL bar value should not have changed"
+ );
+ });
+});
+
+add_task(async function checkDragForbiddenURL() {
+ await BrowserTestUtils.withNewTab(TEST_URL, function (browser) {
+ // See also browser_removeUnsafeProtocolsFromURLBarPaste.js for other
+ // examples. In general we trust that function, we pick some testcases to
+ // ensure we disallow dropping trimmed text.
+ for (let url of [
+ "chrome://browser/content/aboutDialog.xhtml",
+ "file:///",
+ "javascript:",
+ "javascript:void(0)",
+ "java\r\ns\ncript:void(0)",
+ " javascript:void(0)",
+ "\u00A0java\nscript:void(0)",
+ "javascript:document.domain",
+ "javascript:javascript:alert('hi!')",
+ ]) {
+ info(`Check dragging "{$url}" to the URL bar`);
+ simulateURLBarDrop({ type: "text/plain", data: url });
+ Assert.notEqual(
+ gURLBar.value,
+ url,
+ `Shouldn't be allowed to drop ${url} on URL bar`
+ );
+ }
+ });
+});
+
+add_task(async function checkDragText() {
+ await BrowserTestUtils.withNewTab(TEST_URL, async browser => {
+ info("Check dragging multi word text to the urlbar");
+ const TEXT = "Firefox is awesome";
+ const TEXT_URL = "https://example.com/?q=Firefox+is+awesome";
+ let promiseLoad = BrowserTestUtils.browserLoaded(browser, false, TEXT_URL);
+ simulateURLBarDrop({ type: "text/plain", data: TEXT });
+ await promiseLoad;
+
+ info("Check dragging single word text to the urlbar");
+ const WORD = "Firefox";
+ const WORD_URL = "https://example.com/?q=Firefox";
+ promiseLoad = BrowserTestUtils.browserLoaded(browser, false, WORD_URL);
+ simulateURLBarDrop({ type: "text/plain", data: WORD });
+ await promiseLoad;
+ });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_dynamicResults.js b/browser/components/urlbar/tests/browser/browser_dynamicResults.js
new file mode 100644
index 0000000000..a4e9013be5
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_dynamicResults.js
@@ -0,0 +1,799 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests dynamic results.
+ */
+
+"use strict";
+
+const DYNAMIC_TYPE_NAME = "test";
+
+const DYNAMIC_TYPE_VIEW_TEMPLATE = {
+ stylesheet: getRootDirectory(gTestPath) + "dynamicResult0.css",
+ children: [
+ {
+ name: "selectable",
+ tag: "span",
+ attributes: {
+ selectable: "true",
+ },
+ },
+ {
+ name: "text",
+ tag: "span",
+ },
+ {
+ name: "buttonBox",
+ tag: "span",
+ children: [
+ {
+ name: "button1",
+ tag: "span",
+ attributes: {
+ role: "button",
+ attribute_to_remove: "value",
+ },
+ },
+ {
+ name: "button2",
+ tag: "span",
+ attributes: {
+ role: "button",
+ },
+ },
+ ],
+ },
+ ],
+};
+
+const DUMMY_PAGE =
+ "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
+
+// Tests the dynamic type registration functions and stylesheet loading.
+add_task(async function registration() {
+ // Get our test stylesheet URIs.
+ let stylesheetURIs = [];
+ for (let i = 0; i < 2; i++) {
+ stylesheetURIs.push(
+ Services.io.newURI(getRootDirectory(gTestPath) + `dynamicResult${i}.css`)
+ );
+ }
+
+ // Maps from dynamic type names to their type.
+ let viewTemplatesByName = {
+ foo: {
+ stylesheet: stylesheetURIs[0].spec,
+ children: [
+ {
+ name: "text",
+ tag: "span",
+ },
+ ],
+ },
+ bar: {
+ stylesheet: stylesheetURIs[1].spec,
+ children: [
+ {
+ name: "icon",
+ tag: "span",
+ },
+ {
+ name: "button",
+ tag: "span",
+ attributes: {
+ role: "button",
+ },
+ },
+ ],
+ },
+ };
+
+ // First, open another window so that multiple windows are open when we add
+ // the types so we can verify below that the stylesheets are added to all open
+ // windows.
+ let newWindows = [];
+ newWindows.push(await BrowserTestUtils.openNewBrowserWindow());
+
+ // Add the test dynamic types.
+ for (let [name, viewTemplate] of Object.entries(viewTemplatesByName)) {
+ UrlbarResult.addDynamicResultType(name);
+ UrlbarView.addDynamicViewTemplate(name, viewTemplate);
+ }
+
+ // Get them back to make sure they were added.
+ for (let name of Object.keys(viewTemplatesByName)) {
+ let actualType = UrlbarResult.getDynamicResultType(name);
+ // Types are currently just empty objects.
+ Assert.deepEqual(actualType, {}, "Types should match");
+ }
+
+ // Their stylesheets should have been applied to all open windows. There's no
+ // good way to check this because:
+ //
+ // * nsIStyleSheetService has a function that returns whether a stylesheet has
+ // been loaded, but it's global and not per window.
+ // * nsIDOMWindowUtils has functions to load stylesheets but not one to check
+ // whether a stylesheet has been loaded.
+ // * document.stylesheets only contains stylesheets in the DOM.
+ //
+ // So instead we set a CSS variable on #urlbar in each of our stylesheets and
+ // check that it's present.
+ function getCSSVariables(windows) {
+ let valuesByWindow = new Map();
+ for (let win of windows) {
+ let values = [];
+ valuesByWindow.set(window, values);
+ for (let i = 0; i < stylesheetURIs.length; i++) {
+ let value = win
+ .getComputedStyle(gURLBar.panel)
+ .getPropertyValue(`--testDynamicResult${i}`);
+ values.push((value || "").trim());
+ }
+ }
+ return valuesByWindow;
+ }
+ function checkCSSVariables(windows) {
+ for (let values of getCSSVariables(windows).values()) {
+ for (let i = 0; i < stylesheetURIs.length; i++) {
+ if (values[i].trim() !== `ok${i}`) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ // The stylesheets are loaded asyncly, so we need to poll for it.
+ await TestUtils.waitForCondition(() =>
+ checkCSSVariables(BrowserWindowTracker.orderedWindows)
+ );
+ Assert.ok(true, "Stylesheets loaded in all open windows");
+
+ // Open another window to make sure the stylesheets are loaded in it after we
+ // added the new dynamic types.
+ let newWin = await BrowserTestUtils.openNewBrowserWindow();
+ newWindows.push(newWin);
+ await TestUtils.waitForCondition(() => checkCSSVariables([newWin]));
+ Assert.ok(true, "Stylesheets loaded in new window");
+
+ // Remove the dynamic types.
+ for (let name of Object.keys(viewTemplatesByName)) {
+ UrlbarView.removeDynamicViewTemplate(name);
+ UrlbarResult.removeDynamicResultType(name);
+ let actualType = UrlbarResult.getDynamicResultType(name);
+ Assert.equal(actualType, null, "Type should be unregistered");
+ }
+
+ // The stylesheets should be removed from all windows.
+ let valuesByWindow = getCSSVariables(BrowserWindowTracker.orderedWindows);
+ for (let values of valuesByWindow.values()) {
+ for (let i = 0; i < stylesheetURIs.length; i++) {
+ Assert.ok(!values[i], "Stylesheet should be removed");
+ }
+ }
+
+ // Close the new windows.
+ for (let win of newWindows) {
+ await BrowserTestUtils.closeWindow(win);
+ }
+});
+
+// Tests that the view is created correctly from the view template.
+add_task(async function viewCreated() {
+ await withDynamicTypeProvider(async () => {
+ // Do a search.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ waitForFocus: SimpleTest.waitForFocus,
+ });
+
+ // Get the row.
+ let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ Assert.equal(
+ row.result.type,
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ "row.result.type"
+ );
+ Assert.equal(
+ row.getAttribute("dynamicType"),
+ DYNAMIC_TYPE_NAME,
+ "row[dynamicType]"
+ );
+ let inner = row.querySelector(".urlbarView-row-inner");
+ Assert.ok(inner, ".urlbarView-row-inner should exist");
+
+ // Check the DOM.
+ checkDOM(inner, DYNAMIC_TYPE_VIEW_TEMPLATE.children);
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+// Tests that the view is updated correctly.
+async function checkViewUpdated(provider) {
+ await withDynamicTypeProvider(async () => {
+ // Test a few different search strings. The dynamic result view will be
+ // updated to reflect the current string.
+ for (let searchString of ["test", "some other string", "and another"]) {
+ // Do a search.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ waitForFocus: SimpleTest.waitForFocus,
+ });
+
+ let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ let text = row.querySelector(
+ `.urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-text`
+ );
+
+ // The view's call to provider.getViewUpdate is async, so we need to make
+ // sure the update has been applied before continuing to avoid
+ // intermittent failures.
+ await TestUtils.waitForCondition(
+ () => text.getAttribute("searchString") == searchString
+ );
+
+ // The "searchString" attribute of these elements should be updated.
+ let elementNames = ["selectable", "text", "button1", "button2"];
+ for (let name of elementNames) {
+ let element = row.querySelector(
+ `.urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-${name}`
+ );
+ Assert.equal(
+ element.getAttribute("searchString"),
+ searchString,
+ 'element.getAttribute("searchString")'
+ );
+ }
+
+ let button1 = row.querySelector(
+ `.urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-button1`
+ );
+
+ Assert.equal(
+ button1.hasAttribute("attribute_to_remove"),
+ false,
+ "Attribute should be removed"
+ );
+
+ // text.textContent should be updated.
+ Assert.equal(
+ text.textContent,
+ `result.payload.searchString is: ${searchString}`,
+ "text.textContent"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ }
+ }, provider);
+}
+
+add_task(async function checkViewUpdatedPlain() {
+ await checkViewUpdated(new TestProvider());
+});
+
+add_task(async function checkViewUpdatedWDynamicViewTemplate() {
+ /**
+ * A dummy provider that provides the viewTemplate dynamically.
+ */
+ class TestShouldCallGetViewTemplateProvider extends TestProvider {
+ getViewTemplateWasCalled = false;
+
+ getViewTemplate() {
+ this.getViewTemplateWasCalled = true;
+ return DYNAMIC_TYPE_VIEW_TEMPLATE;
+ }
+ }
+
+ let provider = new TestShouldCallGetViewTemplateProvider();
+ Assert.ok(
+ !provider.getViewTemplateWasCalled,
+ "getViewTemplate has not yet been called for the provider"
+ );
+ Assert.ok(
+ !UrlbarView.dynamicViewTemplatesByName.get(DYNAMIC_TYPE_NAME),
+ "No template has been registered"
+ );
+ await checkViewUpdated(provider);
+ Assert.ok(
+ provider.getViewTemplateWasCalled,
+ "getViewTemplate was called for the provider"
+ );
+});
+
+// Tests that selection correctly moves through buttons and selectables in a
+// dynamic result.
+add_task(async function selection() {
+ await withDynamicTypeProvider(async () => {
+ // Add a visit so we have at least one result after the dynamic result.
+ await PlacesUtils.history.clear();
+ await PlacesTestUtils.addVisits("http://example.com/test");
+
+ // Do a search.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ waitForFocus: SimpleTest.waitForFocus,
+ });
+
+ // Sanity check that the dynamic result is at index 1.
+ let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ Assert.equal(
+ row.result.type,
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ "row.result.type"
+ );
+
+ // The heuristic result will be selected. TAB from the heuristic through
+ // all the selectable elements in the dynamic result.
+ let selectables = ["selectable", "button1", "button2"];
+ for (let name of selectables) {
+ let element = row.querySelector(
+ `.urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-${name}`
+ );
+ Assert.ok(element, "Sanity check element");
+ EventUtils.synthesizeKey("KEY_Tab");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedElement(window),
+ element,
+ `Selected element: ${name}`
+ );
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 1,
+ "Row at index 1 selected"
+ );
+ Assert.equal(UrlbarTestUtils.getSelectedRow(window), row, "Row selected");
+ }
+
+ // TAB again to select the result after the dynamic result.
+ EventUtils.synthesizeKey("KEY_Tab");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 2,
+ "Row at index 2 selected"
+ );
+ Assert.notEqual(
+ UrlbarTestUtils.getSelectedRow(window),
+ row,
+ "Row is not selected"
+ );
+
+ // SHIFT+TAB back through the dynamic result.
+ for (let name of selectables.reverse()) {
+ let element = row.querySelector(
+ `.urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-${name}`
+ );
+ Assert.ok(element, "Sanity check element");
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+ Assert.equal(
+ UrlbarTestUtils.getSelectedElement(window),
+ element,
+ `Selected element: ${name}`
+ );
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 1,
+ "Row at index 1 selected"
+ );
+ Assert.equal(UrlbarTestUtils.getSelectedRow(window), row, "Row selected");
+ }
+
+ // SHIFT+TAB again to select the heuristic result.
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "Row at index 0 selected"
+ );
+ Assert.notEqual(
+ UrlbarTestUtils.getSelectedRow(window),
+ row,
+ "Row is not selected"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ await PlacesUtils.history.clear();
+ });
+});
+
+// Tests picking elements in a dynamic result.
+add_task(async function pick() {
+ await withDynamicTypeProvider(async provider => {
+ let selectables = ["selectable", "button1", "button2"];
+ for (let i = 0; i < selectables.length; i++) {
+ let selectable = selectables[i];
+
+ // Do a search.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ waitForFocus: SimpleTest.waitForFocus,
+ });
+
+ // Sanity check that the dynamic result is at index 1.
+ let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ Assert.equal(
+ row.result.type,
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ "row.result.type"
+ );
+
+ // The heuristic result will be selected. TAB from the heuristic
+ // to the selectable element.
+ let element = row.querySelector(
+ `.urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-${selectable}`
+ );
+ Assert.ok(element, "Sanity check element");
+ EventUtils.synthesizeKey("KEY_Tab", { repeat: i + 1 });
+ Assert.equal(
+ UrlbarTestUtils.getSelectedElement(window),
+ element,
+ `Selected element: ${name}`
+ );
+
+ // Pick the element.
+ let pickPromise = provider.promisePick();
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Enter")
+ );
+ let [result, pickedElement] = await pickPromise;
+ Assert.equal(result, row.result, "Picked result");
+ Assert.equal(pickedElement, element, "Picked element");
+ }
+ });
+});
+
+// Tests picking elements in a dynamic result.
+add_task(async function shouldNavigate() {
+ /**
+ * A dummy provider that providers results with a `shouldNavigate` property.
+ */
+ class TestShouldNavigateProvider extends TestProvider {
+ /**
+ * @param {object} context - Data regarding the context of the query.
+ * @param {Function} addCallback - Function to add a result to the query.
+ */
+ async startQuery(context, addCallback) {
+ for (let result of this._results) {
+ result.payload.searchString = context.searchString;
+ result.payload.shouldNavigate = true;
+ result.payload.url = DUMMY_PAGE;
+ addCallback(this, result);
+ }
+ }
+ }
+
+ await withDynamicTypeProvider(async provider => {
+ // Do a search.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ waitForFocus: SimpleTest.waitForFocus,
+ });
+
+ // Sanity check that the dynamic result is at index 1.
+ let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ Assert.equal(
+ row.result.type,
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ "row.result.type"
+ );
+
+ // The heuristic result will be selected. TAB from the heuristic
+ // to the selectable element.
+ let element = row.querySelector(
+ `.urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-selectable`
+ );
+ Assert.ok(element, "Sanity check element");
+ EventUtils.synthesizeKey("KEY_Tab", { repeat: 1 });
+ Assert.equal(
+ UrlbarTestUtils.getSelectedElement(window),
+ element,
+ `Selected element: ${name}`
+ );
+
+ // Pick the element.
+ let pickPromise = provider.promisePick();
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Enter")
+ );
+ // Verify that onEngagement was still called.
+ let [result, pickedElement] = await pickPromise;
+ Assert.equal(result, row.result, "Picked result");
+ Assert.equal(pickedElement, element, "Picked element");
+
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ is(
+ gBrowser.currentURI.spec,
+ DUMMY_PAGE,
+ "We navigated to payload.url when result selected"
+ );
+
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, "about:home");
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ "about:home"
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ waitForFocus: SimpleTest.waitForFocus,
+ });
+
+ row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ element = row.querySelector(
+ `.urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-selectable`
+ );
+
+ pickPromise = provider.promisePick();
+ EventUtils.synthesizeMouseAtCenter(element, {});
+ [result, pickedElement] = await pickPromise;
+ Assert.equal(result, row.result, "Picked result");
+ Assert.equal(pickedElement, element, "Picked element");
+
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ is(
+ gBrowser.currentURI.spec,
+ DUMMY_PAGE,
+ "We navigated to payload.url when result is clicked"
+ );
+ }, new TestShouldNavigateProvider());
+});
+
+// Tests applying highlighting to a dynamic result.
+add_task(async function highlighting() {
+ /**
+ * Provides a dynamic result with highlighted text.
+ */
+ class TestHighlightProvider extends TestProvider {
+ startQuery(context, addCallback) {
+ let result = Object.assign(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ ...UrlbarResult.payloadAndSimpleHighlights(context.tokens, {
+ dynamicType: DYNAMIC_TYPE_NAME,
+ text: ["Test title", UrlbarUtils.HIGHLIGHT.SUGGESTED],
+ })
+ ),
+ { suggestedIndex: 1 }
+ );
+ addCallback(this, result);
+ }
+
+ getViewUpdate(result, idsByName) {
+ return {};
+ }
+ }
+
+ // Test that highlighting is applied.
+ await withDynamicTypeProvider(async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ waitForFocus: SimpleTest.waitForFocus,
+ });
+
+ let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ Assert.equal(
+ row.result.type,
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ "row.result.type"
+ );
+ let parentTextNode = row.querySelector(
+ `.urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-text`
+ );
+ let highlightedTextNode = row.querySelector(
+ `.urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-text > strong`
+ );
+ Assert.equal(parentTextNode.firstChild.textContent, "Test");
+ Assert.equal(
+ highlightedTextNode.textContent,
+ " title",
+ "The highlighting was applied successfully."
+ );
+ }, new TestHighlightProvider());
+
+ /**
+ * Provides a dynamic result with highlighted text that is then overridden.
+ */
+ class TestHighlightProviderOveridden extends TestHighlightProvider {
+ getViewUpdate(result, idsByName) {
+ return {
+ text: {
+ textContent: "Test title",
+ },
+ };
+ }
+ }
+
+ // Test that highlighting is not applied when overridden from getViewUpdate.
+ await withDynamicTypeProvider(async () => {
+ // Do a search.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ waitForFocus: SimpleTest.waitForFocus,
+ });
+
+ let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ Assert.equal(
+ row.result.type,
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ "row.result.type"
+ );
+ let parentTextNode = row.querySelector(
+ `.urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-text`
+ );
+ let highlightedTextNode = row.querySelector(
+ `.urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-text > strong`
+ );
+ Assert.equal(
+ parentTextNode.firstChild.textContent,
+ "Test title",
+ "No highlighting was applied"
+ );
+ Assert.ok(!highlightedTextNode, "The <strong> child node was deleted.");
+ }, new TestHighlightProviderOveridden());
+});
+
+/**
+ * Provides a dynamic result.
+ */
+class TestProvider extends UrlbarTestUtils.TestProvider {
+ constructor() {
+ super({
+ results: [
+ Object.assign(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ dynamicType: DYNAMIC_TYPE_NAME,
+ }
+ ),
+ { suggestedIndex: 1 }
+ ),
+ ],
+ });
+ }
+
+ async startQuery(context, addCallback) {
+ for (let result of this._results) {
+ result.payload.searchString = context.searchString;
+ addCallback(this, result);
+ }
+ }
+
+ getViewUpdate(result, idsByName) {
+ for (let child of DYNAMIC_TYPE_VIEW_TEMPLATE.children) {
+ Assert.ok(idsByName.get(child.name), `idsByName contains ${child.name}`);
+ }
+
+ return {
+ selectable: {
+ textContent: "Selectable",
+ attributes: {
+ searchString: result.payload.searchString,
+ },
+ },
+ text: {
+ textContent: `result.payload.searchString is: ${result.payload.searchString}`,
+ attributes: {
+ searchString: result.payload.searchString,
+ },
+ },
+ button1: {
+ textContent: "Button 1",
+ attributes: {
+ searchString: result.payload.searchString,
+ attribute_to_remove: null,
+ },
+ },
+ button2: {
+ textContent: "Button 2",
+ attributes: {
+ searchString: result.payload.searchString,
+ },
+ },
+ };
+ }
+
+ onEngagement(isPrivate, state, queryContext, details) {
+ if (this._pickPromiseResolve) {
+ let { result, element } = details;
+ this._pickPromiseResolve([result, element]);
+ delete this._pickPromiseResolve;
+ delete this._pickPromise;
+ }
+ }
+
+ promisePick() {
+ this._pickPromise = new Promise(resolve => {
+ this._pickPromiseResolve = resolve;
+ });
+ return this._pickPromise;
+ }
+}
+
+/**
+ * Provides a dynamic result.
+ *
+ * @param {object} callback - Function that runs the body of the test.
+ * @param {object} provider - The dummy provider to use.
+ */
+async function withDynamicTypeProvider(
+ callback,
+ provider = new TestProvider()
+) {
+ // Add a dynamic result type.
+ UrlbarResult.addDynamicResultType(DYNAMIC_TYPE_NAME);
+ if (!provider.getViewTemplate) {
+ UrlbarView.addDynamicViewTemplate(
+ DYNAMIC_TYPE_NAME,
+ DYNAMIC_TYPE_VIEW_TEMPLATE
+ );
+ }
+
+ // Add a provider of the dynamic type.
+ UrlbarProvidersManager.registerProvider(provider);
+
+ await callback(provider);
+
+ // Clean up.
+ UrlbarProvidersManager.unregisterProvider(provider);
+ if (!provider.getViewTemplate) {
+ UrlbarView.removeDynamicViewTemplate(DYNAMIC_TYPE_NAME);
+ }
+ UrlbarResult.removeDynamicResultType(DYNAMIC_TYPE_NAME);
+}
+
+function checkDOM(parentNode, expectedChildren) {
+ info(
+ `checkDOM: Checking parentNode id=${parentNode.id} className=${parentNode.className}`
+ );
+ for (let i = 0; i < expectedChildren.length; i++) {
+ let child = expectedChildren[i];
+ let actualChild = parentNode.children[i];
+ info(`checkDOM: Checking expected child: ${JSON.stringify(child)}`);
+ Assert.ok(actualChild, "actualChild should exist");
+ Assert.equal(actualChild.tagName, child.tag, "child.tag");
+ Assert.equal(actualChild.getAttribute("name"), child.name, "child.name");
+ Assert.ok(
+ actualChild.classList.contains(
+ `urlbarView-dynamic-${DYNAMIC_TYPE_NAME}-${child.name}`
+ ),
+ "child.name should be in classList"
+ );
+ // We have to use startsWith/endsWith since the middle of the ID is a random
+ // number.
+ Assert.ok(actualChild.id.startsWith("urlbarView-row-"));
+ Assert.ok(
+ actualChild.id.endsWith(child.name),
+ "The child was assigned the correct ID."
+ );
+ for (let [name, value] of Object.entries(child.attributes || {})) {
+ if (name == "attribute_to_remove") {
+ Assert.equal(
+ actualChild.hasAttribute(name),
+ false,
+ `attribute: ${name}`
+ );
+ continue;
+ }
+ Assert.equal(actualChild.getAttribute(name), value, `attribute: ${name}`);
+ }
+ for (let name of child.classList || []) {
+ Assert.ok(actualChild.classList.contains(name), `classList: ${name}`);
+ }
+ if (child.children) {
+ checkDOM(actualChild, child.children);
+ }
+ }
+}
diff --git a/browser/components/urlbar/tests/browser/browser_edit_invalid_url.js b/browser/components/urlbar/tests/browser/browser_edit_invalid_url.js
new file mode 100644
index 0000000000..5a710c1285
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_edit_invalid_url.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Checks that we trim invalid urls when they are selected, so that if the user
+// modifies the selected url, or just closes the results pane, we do a visit
+// rather than searching for the trimmed string.
+
+const url = BrowserUIUtils.trimURLProtocol + "invalid.somehost/mytest";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.trimURLs", true]],
+ });
+ await PlacesTestUtils.addVisits(url);
+ registerCleanupFunction(PlacesUtils.history.clear);
+});
+
+add_task(async function test_escape() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "invalid",
+ });
+ // Look for our result.
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+ Assert.greater(resultCount, 1, "There should be at least two results");
+ for (let i = 0; i < resultCount; i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ info(`Result at ${i} has url ${result.url}`);
+ if (result.url.startsWith(url)) {
+ break;
+ }
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ Assert.equal(
+ gURLBar.value,
+ url,
+ "The string displayed in the textbox should be the untrimmed url"
+ );
+ // Close the results pane by ESC.
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_Escape");
+ });
+ // Confirm the result and check the loaded page.
+ let promise = waitforLoadURL();
+ EventUtils.synthesizeKey("KEY_Enter");
+ let loadedUrl = await promise;
+ Assert.equal(loadedUrl, url, "Should try to load a url");
+});
+
+add_task(async function test_edit_url() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "invalid",
+ });
+ // Look for our result.
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+ Assert.greater(resultCount, 1, "There should be at least two results");
+ for (let i = 1; i < resultCount; i++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ info(`Result at ${i} has url ${result.url}`);
+ if (result.url.startsWith(url)) {
+ break;
+ }
+ }
+ Assert.equal(
+ gURLBar.value,
+ url,
+ "The string displayed in the textbox should be the untrimmed url"
+ );
+ // Modify the url.
+ EventUtils.synthesizeKey("2");
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.URL, "Should visit a url");
+ Assert.equal(result.url, url + "2", "Should visit the modified url");
+
+ // Confirm the result and check the loaded page.
+ let promise = waitforLoadURL();
+ EventUtils.synthesizeKey("KEY_Enter");
+ let loadedUrl = await promise;
+ Assert.equal(loadedUrl, url + "2", "Should try to load the modified url");
+});
+
+async function waitforLoadURL() {
+ let sandbox = sinon.createSandbox();
+ let loadedUrl = await new Promise(resolve =>
+ sandbox.stub(gURLBar, "_loadURL").callsFake(resolve)
+ );
+ sandbox.restore();
+ return loadedUrl;
+}
diff --git a/browser/components/urlbar/tests/browser/browser_engagement.js b/browser/components/urlbar/tests/browser/browser_engagement.js
new file mode 100644
index 0000000000..b964a61a75
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_engagement.js
@@ -0,0 +1,206 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests the UrlbarProvider.onEngagement() method.
+
+"use strict";
+
+add_task(async function abandonment() {
+ await doTest({
+ expectedEndState: "abandonment",
+ endEngagement: async () => {
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ },
+ });
+});
+
+add_task(async function engagement() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await doTest({
+ expectedEndState: "engagement",
+ endEngagement: async () => {
+ let result, element;
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ result = gURLBar.view.selectedResult;
+ element = gURLBar.view.selectedElement;
+ EventUtils.synthesizeKey("KEY_Enter");
+ });
+ return { result, element };
+ },
+ expectedEndDetails: {
+ selIndex: 0,
+ selType: "history",
+ provider: "",
+ searchSource: "urlbar",
+ isSessionOngoing: false,
+ },
+ });
+ });
+});
+
+add_task(async function privateWindow_abandonment() {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ await doTest({
+ win,
+ expectedEndState: "abandonment",
+ expectedIsPrivate: true,
+ endEngagement: async () => {
+ await UrlbarTestUtils.promisePopupClose(win, () => win.gURLBar.blur());
+ },
+ });
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function privateWindow_engagement() {
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ await doTest({
+ win,
+ expectedEndState: "engagement",
+ expectedIsPrivate: true,
+ endEngagement: async () => {
+ let result, element;
+ await UrlbarTestUtils.promisePopupClose(win, () => {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ result = win.gURLBar.view.selectedResult;
+ element = win.gURLBar.view.selectedElement;
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+ });
+ return { result, element };
+ },
+ expectedEndDetails: {
+ selIndex: 0,
+ selType: "history",
+ provider: "",
+ searchSource: "urlbar",
+ isSessionOngoing: false,
+ },
+ });
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Performs an engagement test.
+ *
+ * @param {object} options
+ * Options object.
+ * @param {string} options.expectedEndState
+ * The expected state at the end of the engagement.
+ * @param {Function} options.endEngagement
+ * A function that should end the engagement. If the expected end state is
+ * "engagement", the function should return `{ result, element }` with the
+ * expected engaged result and element.
+ * @param {window} [options.win]
+ * The window to perform the test in.
+ * @param {boolean} [options.expectedIsPrivate]
+ * Whether the engagement and query context are expected to be private.
+ * @param {object} [options.expectedEndDetails]
+ * The expected `details` at the end of the engagement. `searchString` is
+ * automatically included since it's always present. If `provider` is
+ * expected, then include it and set it to any value; this function will
+ * replace it with the name of the test provider.
+ */
+async function doTest({
+ expectedEndState,
+ endEngagement,
+ win = window,
+ expectedIsPrivate = false,
+ expectedEndDetails = {},
+}) {
+ let provider = new TestProvider();
+ UrlbarProvidersManager.registerProvider(provider);
+
+ let startPromise = provider.promiseEngagement();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "test",
+ fireInputEvent: true,
+ });
+
+ let [isPrivate, state, queryContext, details] = await startPromise;
+ Assert.equal(isPrivate, expectedIsPrivate, "Start isPrivate");
+ Assert.equal(state, "start", "Start state");
+
+ // `queryContext` isn't always defined for `start`, and `onEngagement`
+ // shouldn't rely on it being defined on start, but there's no good reason to
+ // assert that it's not defined here.
+
+ // Similarly, `details` is never defined for `start`, but there's no good
+ // reason to assert that it's not defined.
+
+ let endPromise = provider.promiseEngagement();
+ let { result, element } = (await endEngagement()) ?? {};
+
+ [isPrivate, state, queryContext, details] = await endPromise;
+ Assert.equal(isPrivate, expectedIsPrivate, "End isPrivate");
+ Assert.equal(state, expectedEndState, "End state");
+ Assert.ok(queryContext, "End queryContext");
+ Assert.equal(
+ queryContext.isPrivate,
+ expectedIsPrivate,
+ "End queryContext.isPrivate"
+ );
+
+ let detailsDefaults = {
+ searchString: "test",
+ searchSource: "urlbar",
+ provider: undefined,
+ selIndex: -1,
+ };
+ if ("provider" in expectedEndDetails) {
+ detailsDefaults.provider = provider.name;
+ delete expectedEndDetails.provider;
+ }
+
+ if (expectedEndState == "engagement") {
+ Assert.ok(
+ result,
+ "endEngagement() should have returned the expected engaged result"
+ );
+ Assert.ok(
+ element,
+ "endEngagement() should have returned the expected engaged element"
+ );
+ expectedEndDetails.result = result;
+ expectedEndDetails.element = element;
+ }
+
+ Assert.deepEqual(
+ details,
+ Object.assign(detailsDefaults, expectedEndDetails),
+ "End details"
+ );
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+}
+
+/**
+ * Test provider that resolves promises when onEngagement is called.
+ */
+class TestProvider extends UrlbarTestUtils.TestProvider {
+ _resolves = [];
+
+ constructor() {
+ super({
+ priority: Infinity,
+ results: [
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: "http://example.com/" }
+ ),
+ ],
+ });
+ }
+
+ onEngagement(...args) {
+ let resolve = this._resolves.shift();
+ if (resolve) {
+ resolve(args);
+ }
+ }
+
+ promiseEngagement() {
+ return new Promise(resolve => this._resolves.push(resolve));
+ }
+}
diff --git a/browser/components/urlbar/tests/browser/browser_enter.js b/browser/components/urlbar/tests/browser/browser_enter.js
new file mode 100644
index 0000000000..de1cda7cc1
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_enter.js
@@ -0,0 +1,331 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_VALUE = "example.com/\xF7?\xF7";
+const START_VALUE = "example.com/%C3%B7?%C3%B7";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.quickactions", false]],
+ });
+ const engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ setAsDefault: true,
+ });
+ engine.alias = "@default";
+});
+
+add_task(async function returnKeypress() {
+ info("Simple return keypress");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, START_VALUE);
+
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_Enter");
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ // Check url bar and selected tab.
+ is(
+ gURLBar.value,
+ TEST_VALUE,
+ "Urlbar should preserve the value on return keypress"
+ );
+ is(gBrowser.selectedTab, tab, "New URL was loaded in the current tab");
+
+ // Cleanup.
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function altReturnKeypress() {
+ info("Alt+Return keypress");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, START_VALUE);
+
+ let tabOpenPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabOpen"
+ );
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_Enter", { altKey: true });
+
+ // wait for the new tab to appear.
+ await tabOpenPromise;
+
+ // Check url bar and selected tab.
+ is(
+ gURLBar.value,
+ TEST_VALUE,
+ "Urlbar should preserve the value on return keypress"
+ );
+ isnot(gBrowser.selectedTab, tab, "New URL was loaded in a new tab");
+
+ // Cleanup.
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function altGrReturnKeypress() {
+ info("AltGr+Return keypress");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, START_VALUE);
+
+ let tabOpenPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabOpen"
+ );
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_Enter", { altGraphKey: true });
+
+ // wait for the new tab to appear.
+ await tabOpenPromise;
+
+ // Check url bar and selected tab.
+ is(
+ gURLBar.value,
+ TEST_VALUE,
+ "Urlbar should preserve the value on return keypress"
+ );
+ isnot(gBrowser.selectedTab, tab, "New URL was loaded in a new tab");
+
+ // Cleanup.
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function searchOnEnterNoPick() {
+ info("Search on Enter without picking a urlbar result");
+ await SpecialPowers.pushPrefEnv({
+ // The test checks that the untrimmed value is equal to the spec.
+ // When using showSearchTerms, the untrimmed value becomes
+ // the search terms.
+ set: [["browser.urlbar.showSearchTerms.featureGate", false]],
+ });
+
+ // Why is BrowserTestUtils.openNewForegroundTab not causing the bug?
+ let promiseTabOpened = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabOpen"
+ );
+ EventUtils.synthesizeMouseAtCenter(gBrowser.tabContainer.newTabButton, {});
+ let openEvent = await promiseTabOpened;
+ let tab = openEvent.target;
+
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ null,
+ true
+ );
+ gURLBar.focus();
+ gURLBar.value = "test test";
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+
+ Assert.ok(
+ gBrowser.selectedBrowser.currentURI.spec.endsWith("test+test"),
+ "Should have loaded the correct page"
+ );
+ Assert.equal(
+ gBrowser.selectedBrowser.currentURI.spec,
+ gURLBar.untrimmedValue,
+ "The location should have changed"
+ );
+
+ // Cleanup.
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function searchOnEnterSoon() {
+ info("Search on Enter as soon as typing a char");
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ START_VALUE
+ );
+
+ const onLoad = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ const onPageHide = SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ return new Promise(resolve => {
+ content.window.addEventListener("pagehide", () => {
+ resolve();
+ });
+ });
+ });
+ const onResult = SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ return new Promise(resolve => {
+ content.window.addEventListener("keyup", () => {
+ resolve("keyup");
+ });
+ content.window.addEventListener("unload", () => {
+ resolve("unload");
+ });
+ });
+ });
+
+ // Focus on the input field in urlbar.
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ const ownerDocument = gBrowser.selectedBrowser.ownerDocument;
+ is(
+ ownerDocument.activeElement,
+ gURLBar.inputField,
+ "The input field in urlbar has focus"
+ );
+
+ info("Keydown a char and Enter");
+ EventUtils.synthesizeKey("x", { type: "keydown" });
+ EventUtils.synthesizeKey("KEY_Enter", { type: "keydown" });
+
+ // Wait for pagehide event in the content.
+ await onPageHide;
+ is(
+ ownerDocument.activeElement,
+ gURLBar.inputField,
+ "The input field in urlbar still has focus"
+ );
+
+ // Check the caret position.
+ Assert.equal(
+ gURLBar.selectionStart,
+ gURLBar.value.length,
+ "The selectionStart indicates at ending of the value"
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ gURLBar.value.length,
+ "The selectionEnd indicates at ending of the value"
+ );
+
+ // Keyup both key as soon as pagehide event happens.
+ EventUtils.synthesizeKey("x", { type: "keyup" });
+ EventUtils.synthesizeKey("KEY_Enter", { type: "keyup" });
+
+ // Wait for moving the focus.
+ await TestUtils.waitForCondition(
+ () => ownerDocument.activeElement === gBrowser.selectedBrowser
+ );
+ info("The focus is moved to the browser");
+
+ // Check whether keyup event is not captured before unload event happens.
+ const result = await onResult;
+ is(result, "unload", "Keyup event is not captured.");
+
+ // Check the caret position again.
+ Assert.equal(
+ gURLBar.selectionStart,
+ 0,
+ "The selectionStart indicates at beginning of the value"
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ 0,
+ "The selectionEnd indicates at beginning of the value"
+ );
+
+ // Cleanup.
+ await onLoad;
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function searchByMultipleEnters() {
+ info("Search on Enter after selecting the search engine by Enter");
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ START_VALUE
+ );
+
+ info("Select a search engine by Enter key");
+ gURLBar.focus();
+ gURLBar.select();
+ EventUtils.sendString("@default");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await TestUtils.waitForCondition(
+ () => gURLBar.searchMode,
+ "Wait until entering search mode"
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: "browser_searchSuggestionEngine searchSuggestionEngine.xml",
+ entry: "keywordoffer",
+ });
+ const ownerDocument = gBrowser.selectedBrowser.ownerDocument;
+ is(
+ ownerDocument.activeElement,
+ gURLBar.inputField,
+ "The input field in urlbar has focus"
+ );
+
+ info("Search by Enter key");
+ const onLoad = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.sendString("mozilla");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await onLoad;
+ is(
+ ownerDocument.activeElement,
+ gBrowser.selectedBrowser,
+ "The focus is moved to the browser"
+ );
+
+ // Cleanup.
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function typeCharWhileProcessingEnter() {
+ info("Typing a char while processing enter key");
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ START_VALUE
+ );
+
+ const onLoad = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ `http://${START_VALUE}`
+ );
+ gURLBar.focus();
+
+ info("Keydown Enter");
+ EventUtils.synthesizeKey("KEY_Enter", { type: "keydown" });
+ await TestUtils.waitForCondition(
+ () => gURLBar._keyDownEnterDeferred,
+ "Wait for starting process for the enter key"
+ );
+
+ info("Keydown a char");
+ EventUtils.synthesizeKey("x", { type: "keydown" });
+
+ info("Keyup both");
+ EventUtils.synthesizeKey("x", { type: "keyup" });
+ EventUtils.synthesizeKey("KEY_Enter", { type: "keyup" });
+
+ Assert.equal(
+ gURLBar.inputField.value,
+ TEST_VALUE,
+ "The value of urlbar is correct"
+ );
+
+ await onLoad;
+ Assert.ok("Browser loaded the correct url");
+
+ // Cleanup.
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function keyupEnterWhilePressingMeta() {
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ info("Keydown Meta+Enter");
+ gURLBar.focus();
+ gURLBar.value = "";
+ EventUtils.synthesizeKey("KEY_Enter", { type: "keydown", metaKey: true });
+
+ // Pressing Enter key while pressing Meta key, and next, even when releasing
+ // Enter key before releasing Meta key, the keyup event is not fired.
+ // Therefor, we fire Meta keyup event only.
+ info("Keyup Meta");
+ EventUtils.synthesizeKey("KEY_Meta", { type: "keyup" });
+
+ // Check whether we can input on URL bar.
+ EventUtils.synthesizeKey("a");
+ is(gURLBar.value, "a", "Can input a char");
+
+ // Cleanup.
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_enterAfterMouseOver.js b/browser/components/urlbar/tests/browser/browser_enterAfterMouseOver.js
new file mode 100644
index 0000000000..e102fda09c
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_enterAfterMouseOver.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that enter works correctly after a mouse over.
+ */
+
+function repeat(limit, func) {
+ for (let i = 0; i < limit; i++) {
+ func(i);
+ }
+}
+
+async function promiseAutoComplete(inputText) {
+ gURLBar.focus();
+ gURLBar.value = inputText.slice(0, -1);
+ EventUtils.sendString(inputText.slice(-1));
+ await UrlbarTestUtils.promiseSearchComplete(window);
+}
+
+function assertSelected(index) {
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ index,
+ "Should have the correct index selected"
+ );
+}
+
+let gMaxResults;
+
+add_task(async function () {
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+
+ await PlacesUtils.history.clear();
+
+ gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+
+ let visits = [];
+ repeat(gMaxResults, i => {
+ visits.push({
+ uri: makeURI("http://example.com/autocomplete/?" + i),
+ });
+ });
+ await PlacesTestUtils.addVisits(visits);
+
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
+ await promiseAutoComplete("http://example.com/autocomplete/");
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ gMaxResults,
+ "Should have got the correct amount of results"
+ );
+
+ let initiallySelected = UrlbarTestUtils.getSelectedRowIndex(window);
+
+ info("Key Down to select the next item");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ assertSelected(initiallySelected + 1);
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ initiallySelected + 1
+ );
+ let expectedURL = result.url;
+
+ Assert.equal(
+ gURLBar.untrimmedValue,
+ expectedURL,
+ "Value in the URL bar should be updated by keyboard selection"
+ );
+
+ // Verify that what we're about to do changes the selectedIndex:
+ Assert.notEqual(
+ initiallySelected + 1,
+ 3,
+ "Shouldn't be changing the selectedIndex to the same index we keyboard-selected."
+ );
+
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 3);
+ EventUtils.synthesizeMouseAtCenter(element, { type: "mousemove" });
+
+ await UrlbarTestUtils.promisePopupClose(window, async () => {
+ let openedExpectedPage = BrowserTestUtils.waitForDocLoadAndStopIt(
+ expectedURL,
+ gBrowser.selectedBrowser
+ );
+ EventUtils.synthesizeKey("KEY_Enter");
+ await openedExpectedPage;
+ });
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_focusedCmdK.js b/browser/components/urlbar/tests/browser/browser_focusedCmdK.js
new file mode 100644
index 0000000000..fc32c2c13c
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_focusedCmdK.js
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function () {
+ // Test that Ctrl/Cmd + K will focus the url bar
+ let focusPromise = BrowserTestUtils.waitForEvent(gURLBar, "focus");
+ document.documentElement.focus();
+ EventUtils.synthesizeKey("k", { accelKey: true });
+ await focusPromise;
+ Assert.equal(
+ document.activeElement,
+ gURLBar.inputField,
+ "URL Bar should be focused"
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_groupLabels.js b/browser/components/urlbar/tests/browser/browser_groupLabels.js
new file mode 100644
index 0000000000..2b43990b77
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_groupLabels.js
@@ -0,0 +1,629 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests group labels in the view.
+
+"use strict";
+
+const SUGGESTIONS_FIRST_PREF = "browser.urlbar.showSearchSuggestionsFirst";
+const SUGGESTIONS_PREF = "browser.urlbar.suggest.searches";
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+const TEST_ENGINE_2_BASENAME = "searchSuggestionEngine2.xml";
+const MAX_RESULTS = UrlbarPrefs.get("maxRichResults");
+
+const TOP_SITES = [
+ "http://example-1.com/",
+ "http://example-2.com/",
+ "http://example-3.com/",
+];
+
+const FIREFOX_SUGGEST_LABEL = "Firefox Suggest";
+
+// %s is replaced with the engine name.
+const ENGINE_SUGGESTIONS_LABEL = "%s suggestions";
+
+// Allow more time for Mac machines so they don't time out in verify mode.
+if (AppConstants.platform == "macosx") {
+ requestLongerTimeout(3);
+}
+
+add_setup(async function () {
+ Assert.ok(
+ UrlbarPrefs.get("showSearchSuggestionsFirst"),
+ "Precondition: Search suggestions shown first by default"
+ );
+
+ // Add some history.
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ await UrlbarTestUtils.formHistory.clear();
+ await addHistory();
+
+ // Make sure we have some top sites.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.topsites", true],
+ ["browser.newtabpage.activity-stream.default.sites", TOP_SITES.join(",")],
+ ],
+ });
+ // Waiting for all top sites to be added intermittently times out, so just
+ // wait for any to be added. We're not testing top sites here; we only need
+ // the view to open in top-sites mode.
+ await updateTopSites(sites => sites && sites.length);
+
+ // Add a mock engine so we don't hit the network.
+ await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
+
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+// The Firefox Suggest label should not appear when the labels pref is disabled.
+add_task(async function prefDisabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.groupLabels.enabled", false]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkLabels(MAX_RESULTS, {});
+ await UrlbarTestUtils.promisePopupClose(window);
+ await SpecialPowers.popPrefEnv();
+});
+
+// The Firefox Suggest label should not appear when the view shows top sites.
+add_task(async function topSites() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await checkLabels(-1, {});
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// The Firefox Suggest label should appear when the search string is non-empty
+// and there are only general results.
+add_task(async function general() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkLabels(MAX_RESULTS, {
+ 1: FIREFOX_SUGGEST_LABEL,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// The Firefox Suggest label should appear when the search string is non-empty
+// and there are suggestions followed by general results.
+add_task(async function suggestionsBeforeGeneral() {
+ await withSuggestions(async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkLabels(MAX_RESULTS, {
+ 3: FIREFOX_SUGGEST_LABEL,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+// Both the Firefox Suggest and Suggestions labels should appear when the search
+// string is non-empty, general results are shown before suggestions, and there
+// are general and suggestion results.
+add_task(async function generalBeforeSuggestions() {
+ await withSuggestions(async engine => {
+ Assert.ok(engine.name, "Engine name is non-empty");
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUGGESTIONS_FIRST_PREF, false]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkLabels(MAX_RESULTS, {
+ 1: FIREFOX_SUGGEST_LABEL,
+ [MAX_RESULTS - 2]: engineSuggestionsLabel(engine.name),
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+// Neither the Firefox Suggest nor Suggestions label should appear when the
+// search string is non-empty, general results are shown before suggestions, and
+// there are only suggestion results.
+add_task(async function generalBeforeSuggestions_suggestionsOnly() {
+ await PlacesUtils.history.clear();
+
+ await withSuggestions(async engine => {
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUGGESTIONS_FIRST_PREF, false]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkLabels(3, {});
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+
+ // Add back history so subsequent tasks run with this test's initial state.
+ await addHistory();
+});
+
+// The Suggestions label should be updated when the default engine changes.
+add_task(async function generalBeforeSuggestions_defaultChanged() {
+ // Install both test engines, one after the other. Engine 2 will be the final
+ // default engine.
+ await withSuggestions(async engine1 => {
+ await withSuggestions(async engine2 => {
+ Assert.ok(engine2.name, "Engine 2 name is non-empty");
+ Assert.notEqual(engine1.name, engine2.name, "Engine names are different");
+ Assert.equal(
+ Services.search.defaultEngine.name,
+ engine2.name,
+ "Engine 2 is default"
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUGGESTIONS_FIRST_PREF, false]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkLabels(MAX_RESULTS, {
+ 1: FIREFOX_SUGGEST_LABEL,
+ [MAX_RESULTS - 2]: engineSuggestionsLabel(engine2.name),
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ }, TEST_ENGINE_2_BASENAME);
+ });
+});
+
+// The Firefox Suggest label should appear above a suggested-index result when
+// the result is the only result with that label.
+add_task(async function suggestedIndex_only() {
+ // Clear history, add a provider that returns a result with suggestedIndex =
+ // -1, set up an engine with suggestions, and start a query. The suggested-
+ // index result will be the only result with a label.
+ await PlacesUtils.history.clear();
+
+ let index = -1;
+ let provider = new SuggestedIndexProvider(index);
+ UrlbarProvidersManager.registerProvider(provider);
+
+ await withSuggestions(async engine => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 3);
+ Assert.equal(
+ result.element.row.result.suggestedIndex,
+ index,
+ "Sanity check: Our suggested-index result is present"
+ );
+ await checkLabels(4, {
+ 3: FIREFOX_SUGGEST_LABEL,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+
+ // Add back history so subsequent tasks run with this test's initial state.
+ await addHistory();
+});
+
+// The Firefox Suggest label should appear above a suggested-index result when
+// the result is the first but not the only result with that label.
+add_task(async function suggestedIndex_first() {
+ let index = 1;
+ let provider = new SuggestedIndexProvider(index);
+ UrlbarProvidersManager.registerProvider(provider);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ Assert.equal(
+ result.element.row.result.suggestedIndex,
+ index,
+ "Sanity check: Our suggested-index result is present"
+ );
+ await checkLabels(MAX_RESULTS, {
+ [index]: FIREFOX_SUGGEST_LABEL,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
+
+// The Firefox Suggest label should not appear above a suggested-index result
+// when the result is not the first with that label.
+add_task(async function suggestedIndex_notFirst() {
+ let index = -1;
+ let provider = new SuggestedIndexProvider(index);
+ UrlbarProvidersManager.registerProvider(provider);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ MAX_RESULTS + index
+ );
+ Assert.equal(
+ result.element.row.result.suggestedIndex,
+ index,
+ "Sanity check: Our suggested-index result is present"
+ );
+ await checkLabels(MAX_RESULTS, {
+ 1: FIREFOX_SUGGEST_LABEL,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
+
+// Labels that appear multiple times but not consecutively should be shown.
+add_task(async function repeatLabels() {
+ let engineName = Services.search.defaultEngine.name;
+ let results = [
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ { url: "http://example.com/1" }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ UrlbarUtils.RESULT_SOURCE.SEARCH,
+ { suggestion: "test1", engine: engineName }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ { url: "http://example.com/2" }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ UrlbarUtils.RESULT_SOURCE.SEARCH,
+ { suggestion: "test2", engine: engineName }
+ ),
+ ];
+
+ for (let i = 0; i < results.length; i++) {
+ results[i].suggestedIndex = i;
+ }
+
+ let provider = new UrlbarTestUtils.TestProvider({
+ results,
+ priority: Infinity,
+ });
+ UrlbarProvidersManager.registerProvider(provider);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkLabels(results.length, {
+ 0: FIREFOX_SUGGEST_LABEL,
+ 1: engineSuggestionsLabel(engineName),
+ 2: FIREFOX_SUGGEST_LABEL,
+ 3: engineSuggestionsLabel(engineName),
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
+
+// Clicking a row label shouldn't do anything.
+add_task(async function clickLabel() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ // Do a search. The mock history added in init() should appear with the
+ // Firefox Suggest label at index 1.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkLabels(MAX_RESULTS, {
+ 1: FIREFOX_SUGGEST_LABEL,
+ });
+
+ // Check the result at index 2.
+ let result2 = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.ok(result2.url, "Result at index 2 has a URL");
+ let url2 = result2.url;
+ Assert.ok(
+ url2.startsWith("http://example.com/"),
+ "Result at index 2 is one of our mock history results"
+ );
+
+ // Get the row at index 3 and click above it. The click should hit the row
+ // at index 2 and load its URL. We do this to make sure our click code
+ // here in the test works properly and that performing a similar click
+ // relative to index 1 (see below) would hit the row at index 0 if not for
+ // the label at index 1.
+ let result3 = await UrlbarTestUtils.getDetailsOfResultAt(window, 3);
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ info("Performing click relative to index 3");
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ click(result3.element.row, { y: -2 })
+ );
+ info("Waiting for load after performing click relative to index 3");
+ await loadPromise;
+ Assert.equal(gBrowser.currentURI.spec, url2, "Loaded URL at index 2");
+ // Now do the search again.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+
+ await checkLabels(MAX_RESULTS, {
+ 1: FIREFOX_SUGGEST_LABEL,
+ });
+
+ // Check the result at index 1, the one with the label.
+ let result1 = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.ok(result1.url, "Result at index 1 has a URL");
+ let url1 = result1.url;
+ Assert.ok(
+ url1.startsWith("http://example.com/"),
+ "Result at index 1 is one of our mock history results"
+ );
+ Assert.notEqual(url1, url2, "URLs at indexes 1 and 2 are different");
+
+ // Do a click on the row at index 1 in the same way as before. This time
+ // nothing should happen because the click should hit the label, not the
+ // row at index 0.
+ info("Clicking row label at index 1");
+ click(result1.element.row, { y: -2 });
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 500));
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "View remains open");
+ Assert.equal(
+ gBrowser.currentURI.spec,
+ url2,
+ "Current URL is still URL from index 2"
+ );
+
+ // Now click the main part of the row at index 1. Its URL should load.
+ loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ let { height } = result1.element.row.getBoundingClientRect();
+ info(`Clicking main part of the row at index 1, height=${height}`);
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ click(result1.element.row)
+ );
+ info("Waiting for load after clicking row at index 1");
+ await loadPromise;
+ Assert.equal(gBrowser.currentURI.spec, url1, "Loaded URL at index 1");
+ });
+});
+
+add_task(async function ariaLabel() {
+ const helpUrl = "http://example.com/help";
+ const results = [
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ { url: "http://example.com/1", helpUrl }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ { url: "http://example.com/2", helpUrl }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ { url: "http://example.com/3" }
+ ),
+ ];
+
+ for (let i = 0; i < results.length; i++) {
+ results[i].suggestedIndex = i;
+ }
+
+ const provider = new UrlbarTestUtils.TestProvider({
+ results,
+ priority: Infinity,
+ });
+ UrlbarProvidersManager.registerProvider(provider);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await checkLabels(results.length, {
+ 0: FIREFOX_SUGGEST_LABEL,
+ });
+
+ const expectedRows = [
+ { hasGroupAriaLabel: true, ariaLabel: FIREFOX_SUGGEST_LABEL },
+ { hasGroupAriaLabel: false },
+ { hasGroupAriaLabel: false },
+ ];
+ await checkGroupAriaLabels(expectedRows);
+
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
+
+/**
+ * Provider that returns a suggested-index result.
+ */
+class SuggestedIndexProvider extends UrlbarTestUtils.TestProvider {
+ constructor(suggestedIndex) {
+ super({
+ results: [
+ Object.assign(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ { url: "http://example.com/" }
+ ),
+ { suggestedIndex }
+ ),
+ ],
+ });
+ }
+}
+
+async function addHistory() {
+ for (let i = 0; i < MAX_RESULTS; i++) {
+ await PlacesTestUtils.addVisits("http://example.com/" + i);
+ }
+}
+
+/**
+ * Asserts that each result in the view does or doesn't have a label, depending
+ * on `labelsByIndex`.
+ *
+ * @param {number} resultCount
+ * The expected number of results. Pass -1 to use the max index in
+ * `labelsByIndex` or the actual result count if `labelsByIndex` is empty.
+ * @param {object} labelsByIndex
+ * A mapping from indexes to expected labels.
+ */
+async function checkLabels(resultCount, labelsByIndex) {
+ if (resultCount >= 0) {
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ resultCount,
+ "Expected result count"
+ );
+ } else {
+ // This `else` branch is only necessary because waiting for all top sites to
+ // be added intermittently times out. Don't let the test fail for such a
+ // dumb reason.
+ let indexes = Object.keys(labelsByIndex);
+ if (indexes.length) {
+ resultCount = indexes.sort((a, b) => b - a)[0] + 1;
+ } else {
+ resultCount = UrlbarTestUtils.getResultCount(window);
+ Assert.greater(resultCount, 0, "Actual result count is > 0");
+ }
+ }
+ for (let i = 0; i < resultCount; i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ let { row } = result.element;
+ let before = getComputedStyle(row, "::before");
+ if (labelsByIndex.hasOwnProperty(i)) {
+ Assert.equal(
+ before.content,
+ "attr(label)",
+ `::before.content is correct at index ${i}`
+ );
+ Assert.equal(
+ row.getAttribute("label"),
+ labelsByIndex[i],
+ `Row has correct label at index ${i}`
+ );
+ } else {
+ Assert.equal(
+ before.content,
+ "none",
+ `::before.content is 'none' at index ${i}`
+ );
+ Assert.ok(
+ !row.hasAttribute("label"),
+ `Row does not have label attribute at index ${i}`
+ );
+ }
+ }
+}
+
+/**
+ * Asserts that an element for group aria label.
+ *
+ * @param {Array} expectedRows The expected rows.
+ */
+async function checkGroupAriaLabels(expectedRows) {
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ expectedRows.length,
+ "Expected result count"
+ );
+
+ for (let i = 0; i < expectedRows.length; i++) {
+ const result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ const { row } = result.element;
+ const groupAriaLabel = row.querySelector(".urlbarView-group-aria-label");
+
+ const expected = expectedRows[i];
+
+ Assert.equal(
+ !!groupAriaLabel,
+ expected.hasGroupAriaLabel,
+ `Group aria label exists as expected in the results[${i}]`
+ );
+
+ if (expected.hasGroupAriaLabel) {
+ Assert.equal(
+ groupAriaLabel.getAttribute("aria-label"),
+ expected.ariaLabel,
+ `Content of aria-label attribute in the element for group aria label in the results[${i}] is correct`
+ );
+ }
+ }
+}
+
+function engineSuggestionsLabel(engineName) {
+ return ENGINE_SUGGESTIONS_LABEL.replace("%s", engineName);
+}
+
+/**
+ * Adds a search engine that provides suggestions, calls your callback, and then
+ * remove the engine.
+ *
+ * @param {Function} callback
+ * Your callback function.
+ * @param {string} [engineBasename]
+ * The basename of the engine file.
+ */
+async function withSuggestions(
+ callback,
+ engineBasename = TEST_ENGINE_BASENAME
+) {
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUGGESTIONS_PREF, true]],
+ });
+ let engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + engineBasename,
+ });
+ let oldDefaultEngine = await Services.search.getDefault();
+ await Services.search.setDefault(
+ engine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ try {
+ await callback(engine);
+ } finally {
+ await Services.search.setDefault(
+ oldDefaultEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await Services.search.removeEngine(engine);
+ await SpecialPowers.popPrefEnv();
+ }
+}
+
+function click(element, { x = undefined, y = undefined } = {}) {
+ let { width, height } = element.getBoundingClientRect();
+ if (typeof x != "number") {
+ x = width / 2;
+ }
+ if (typeof y != "number") {
+ y = height / 2;
+ }
+ EventUtils.synthesizeMouse(element, x, y, { type: "mousedown" });
+ EventUtils.synthesizeMouse(element, x, y, { type: "mouseup" });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_handleCommand_fallback.js b/browser/components/urlbar/tests/browser/browser_handleCommand_fallback.js
new file mode 100644
index 0000000000..9d8ac8754c
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_handleCommand_fallback.js
@@ -0,0 +1,142 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the fallback paths of handleCommand (no view and no previous
+ * result) work consistently against the normal case of picking the heuristic
+ * result.
+ */
+
+const TEST_STRINGS = [
+ "test",
+ "test/",
+ "test.com",
+ "test.invalid",
+ "moz",
+ "moz test",
+ "@moz test",
+ "keyword",
+ "keyword test",
+ "test/test/",
+ "test /test/",
+];
+
+add_task(async function () {
+ // Disable autofill so mozilla.org isn't autofilled below.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.autoFill", false]],
+ });
+
+ sandbox = sinon.createSandbox();
+ await SearchTestUtils.installSearchExtension();
+ await SearchTestUtils.installSearchExtension({ name: "Example2" });
+
+ let bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "https://example.com/?q=%s",
+ title: "test",
+ });
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "https://example.com/?q=%s",
+ });
+ registerCleanupFunction(async () => {
+ sandbox.restore();
+ await PlacesUtils.bookmarks.remove(bm);
+ await UrlbarTestUtils.formHistory.clear();
+ });
+
+ async function promiseLoadURL() {
+ return new Promise(resolve => {
+ sandbox.stub(gURLBar, "_loadURL").callsFake(function () {
+ sandbox.restore();
+ // The last arguments are optional and apply only to some cases, so we
+ // could not use deepEqual with them.
+ resolve(Array.from(arguments).slice(0, 3));
+ });
+ });
+ }
+
+ // Run the string through a normal search where the user types the string
+ // and confirms the heuristic result, store the arguments to _loadURL, then
+ // confirm the same string without a view and without an input event, and
+ // compare the arguments.
+ for (let value of TEST_STRINGS) {
+ info(`Input the value normally and Enter. Value: ${value}`);
+ let promise = promiseLoadURL();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+ let args = await promise;
+ Assert.ok(args.length, "Sanity check");
+ info("Close the panel and confirm again.");
+ promise = promiseLoadURL();
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ Assert.deepEqual(await promise, args, "Check arguments are coherent");
+
+ info("Set the value directly and Enter.");
+ // To properly testing the original value we must be out of search mode.
+ if (gURLBar.searchMode) {
+ await UrlbarTestUtils.exitSearchMode(window);
+ // Exiting search mode may reopen the panel.
+ await UrlbarTestUtils.promisePopupClose(window);
+ }
+ promise = promiseLoadURL();
+ gURLBar.value = value;
+ let spy = sinon.spy(UrlbarUtils, "getHeuristicResultFor");
+ EventUtils.synthesizeKey("KEY_Enter");
+ spy.restore();
+ Assert.ok(spy.called, "invoked getHeuristicResultFor");
+ Assert.deepEqual(await promise, args, "Check arguments are coherent");
+ gURLBar.handleRevert();
+ }
+});
+
+// This is testing the final fallback case that may happen when we can't
+// get a heuristic result, maybe because the Places database is corrupt.
+add_task(async function no_heuristic_test() {
+ sandbox = sinon.createSandbox();
+
+ let stub = sandbox
+ .stub(UrlbarUtils, "getHeuristicResultFor")
+ .callsFake(async function () {
+ throw new Error("I failed!");
+ });
+
+ registerCleanupFunction(async () => {
+ sandbox.restore();
+ await UrlbarTestUtils.formHistory.clear();
+ });
+
+ async function promiseLoadURL() {
+ return new Promise(resolve => {
+ sandbox.stub(gURLBar, "_loadURL").callsFake(function () {
+ sandbox.restore();
+ // The last arguments are optional and apply only to some cases, so we
+ // could not use deepEqual with them.
+ resolve(Array.from(arguments).slice(0, 3));
+ });
+ });
+ }
+
+ // Run the string through a normal search where the user types the string
+ // and confirms the heuristic result, store the arguments to _loadURL, then
+ // confirm the same string without a view and without an input event, and
+ // compare the arguments.
+ for (let value of TEST_STRINGS) {
+ // To properly testing the original value we must be out of search mode.
+ if (gURLBar.searchMode) {
+ await UrlbarTestUtils.exitSearchMode(window);
+ }
+ let promise = promiseLoadURL();
+ gURLBar.value = value;
+ EventUtils.synthesizeKey("KEY_Enter");
+ Assert.ok(stub.called, "invoked getHeuristicResultFor");
+ // The first argument to _loadURL should always be a valid url, so this
+ // should never throw.
+ new URL((await promise)[0]);
+ }
+});
diff --git a/browser/components/urlbar/tests/browser/browser_hashChangeProxyState.js b/browser/components/urlbar/tests/browser/browser_hashChangeProxyState.js
new file mode 100644
index 0000000000..d0e236fe7e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_hashChangeProxyState.js
@@ -0,0 +1,151 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that navigating through both the URL bar and using in-page hash- or ref-
+ * based links and back or forward navigation updates the URL bar and identity block correctly.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.navigation.requireUserInteraction", false]],
+ });
+ let baseURL = `${TEST_BASE_URL}dummy_page.html`;
+ let url = baseURL + "#foo";
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url },
+ async function (browser) {
+ let identityBox = document.getElementById("identity-box");
+ let expectedURL = url;
+
+ let verifyURLBarState = testType => {
+ is(
+ gURLBar.value,
+ expectedURL,
+ "URL bar visible value should be correct " + testType
+ );
+ is(
+ gURLBar.untrimmedValue,
+ expectedURL,
+ "URL bar value should be correct " + testType
+ );
+ ok(
+ identityBox.classList.contains("verifiedDomain"),
+ "Identity box should know we're doing SSL " + testType
+ );
+ is(
+ gURLBar.getAttribute("pageproxystate"),
+ "valid",
+ "URL bar is in valid page proxy state"
+ );
+ };
+
+ verifyURLBarState("at the beginning");
+
+ let locationChangePromise;
+ let resolveLocationChangePromise;
+ let expectURL = urlTemp => {
+ expectedURL = urlTemp;
+ locationChangePromise = new Promise(
+ r => (resolveLocationChangePromise = r)
+ );
+ };
+ let wpl = {
+ onLocationChange(unused, unused2, location) {
+ is(location.spec, expectedURL, "Got the expected URL");
+ resolveLocationChangePromise();
+ },
+ };
+ gBrowser.addProgressListener(wpl);
+
+ expectURL(baseURL + "#foo");
+ gURLBar.select();
+ EventUtils.sendKey("return");
+
+ await locationChangePromise;
+ verifyURLBarState("after hitting enter on the same URL a second time");
+
+ expectURL(baseURL + "#bar");
+ gURLBar.value = expectedURL;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+
+ await locationChangePromise;
+ verifyURLBarState("after a URL bar hash navigation");
+
+ expectURL(baseURL + "#foo");
+ await SpecialPowers.spawn(browser, [], function () {
+ let a = content.document.createElement("a");
+ a.href = "#foo";
+ a.textContent = "Foo Link";
+ content.document.body.appendChild(a);
+ a.click();
+ });
+
+ await locationChangePromise;
+ verifyURLBarState("after a page link hash navigation");
+
+ expectURL(baseURL + "#bar");
+ gBrowser.goBack();
+
+ await locationChangePromise;
+ verifyURLBarState("after going back");
+
+ expectURL(baseURL + "#foo");
+ gBrowser.goForward();
+
+ await locationChangePromise;
+ verifyURLBarState("after going forward");
+
+ expectURL(baseURL + "#foo");
+ gURLBar.select();
+ EventUtils.sendKey("return");
+
+ await locationChangePromise;
+ verifyURLBarState("after hitting enter on the same URL");
+
+ gBrowser.removeProgressListener(wpl);
+ }
+ );
+});
+
+/**
+ * Check that initial secure loads that swap remoteness
+ * get the correct page icon when finished.
+ */
+add_task(async function () {
+ // Ensure there's no preloaded newtab browser, since that'll not fire a load event.
+ NewTabPagePreloading.removePreloadedBrowser(window);
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:newtab"
+ );
+ let url = `${TEST_BASE_URL}dummy_page.html#foo`;
+ gURLBar.value = url;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ is(
+ gURLBar.value,
+ url,
+ "URL bar visible value should be correct when the page loads from about:newtab"
+ );
+ is(
+ gURLBar.untrimmedValue,
+ url,
+ "URL bar value should be correct when the page loads from about:newtab"
+ );
+ let identityBox = document.getElementById("identity-box");
+ ok(
+ identityBox.classList.contains("verifiedDomain"),
+ "Identity box should know we're doing SSL when the page loads from about:newtab"
+ );
+ is(
+ gURLBar.getAttribute("pageproxystate"),
+ "valid",
+ "URL bar is in valid page proxy state when SSL page with hash loads from about:newtab"
+ );
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_helpUrl.js b/browser/components/urlbar/tests/browser/browser_helpUrl.js
new file mode 100644
index 0000000000..5182a8ddb0
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_helpUrl.js
@@ -0,0 +1,428 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests the help/info button that appears for results whose payloads have a
+// `helpUrl` property.
+
+"use strict";
+
+const MAX_RESULTS = UrlbarPrefs.get("maxRichResults");
+const RESULT_URL = "http://example.com/test";
+const RESULT_HELP_URL = "http://example.com/help";
+
+add_setup(async function () {
+ // Add enough results to fill up the view.
+ await PlacesUtils.history.clear();
+ for (let i = 0; i < MAX_RESULTS; i++) {
+ await PlacesTestUtils.addVisits("http://example.com/" + i);
+ }
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+// Sets `helpL10n` on the result payload and makes sure the help button ends
+// up with a corresponding l10n attribute.
+add_task(async function title_helpL10n() {
+ if (UrlbarPrefs.get("resultMenu")) {
+ return;
+ }
+ let provider = registerTestProvider(1);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "example",
+ window,
+ });
+
+ await assertIsTestResult(1);
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ let helpButton = result.element.row._buttons.get("help");
+ Assert.ok(helpButton, "Sanity check: help button should exist");
+
+ let l10nAttrs = document.l10n.getAttributes(helpButton);
+ Assert.deepEqual(
+ l10nAttrs,
+ { id: "urlbar-tip-help-icon", args: null },
+ "The l10n ID attribute was correctly set"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
+
+// (SHIFT+)TABs through a result with a help button. The result is the
+// second result and has other results after it.
+add_task(async function keyboardSelection_secondResult() {
+ let provider = registerTestProvider(1);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "example",
+ window,
+ });
+
+ // Sanity-check initial state.
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ MAX_RESULTS,
+ "There should be MAX_RESULTS results in the view"
+ );
+ Assert.equal(
+ UrlbarTestUtils.getSelectedElementIndex(window),
+ 0,
+ "The heuristic result should be selected"
+ );
+ await assertIsTestResult(1);
+
+ info("Arrow down to the main part of the result.");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ assertMainPartSelected(1);
+
+ info("TAB to the button.");
+ EventUtils.synthesizeKey("KEY_Tab");
+ assertButtonSelected(2);
+
+ info("TAB to the next (third) result.");
+ EventUtils.synthesizeKey("KEY_Tab");
+ assertOtherResultSelected(3, "next result");
+
+ info("SHIFT+TAB to the help button.");
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+ assertButtonSelected(2);
+
+ info("SHIFT+TAB to the main part of the result.");
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+ assertMainPartSelected(1);
+
+ info("Arrow up to the previous (first) result.");
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertOtherResultSelected(0, "previous result");
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
+
+// (SHIFT+)TABs through a result with a help button. The result is the
+// last result.
+add_task(async function keyboardSelection_lastResult() {
+ let provider = registerTestProvider(MAX_RESULTS - 1);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "example",
+ window,
+ });
+
+ // Sanity-check initial state.
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ MAX_RESULTS,
+ "There should be MAX_RESULTS results in the view"
+ );
+ Assert.equal(
+ UrlbarTestUtils.getSelectedElementIndex(window),
+ 0,
+ "The heuristic result should be selected"
+ );
+ await assertIsTestResult(MAX_RESULTS - 1);
+
+ let numSelectable = UrlbarPrefs.get("resultMenu")
+ ? MAX_RESULTS * 2 - 2
+ : MAX_RESULTS;
+
+ // Arrow down to the main part of the result.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: MAX_RESULTS - 1 });
+ assertMainPartSelected(numSelectable - 1);
+
+ // TAB to the help button.
+ EventUtils.synthesizeKey("KEY_Tab");
+ assertButtonSelected(numSelectable);
+
+ // Arrow down to the first one-off. If this test is running alone, the
+ // one-offs will rebuild themselves when the view is opened above, and they
+ // may not be visible yet. Wait for the first one to become visible before
+ // trying to select it.
+ await TestUtils.waitForCondition(() => {
+ return (
+ gURLBar.view.oneOffSearchButtons.buttons.firstElementChild &&
+ BrowserTestUtils.is_visible(
+ gURLBar.view.oneOffSearchButtons.buttons.firstElementChild
+ )
+ );
+ }, "Waiting for first one-off to become visible.");
+
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ await TestUtils.waitForCondition(() => {
+ return gURLBar.view.oneOffSearchButtons.selectedButton;
+ }, "Waiting for one-off to become selected.");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedElementIndex(window),
+ -1,
+ "No results should be selected."
+ );
+
+ // SHIFT+TAB to the help button.
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+ assertButtonSelected(numSelectable);
+
+ // SHIFT+TAB to the main part of the result.
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+ assertMainPartSelected(numSelectable - 1);
+
+ // Arrow up to the previous result.
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertOtherResultSelected(
+ numSelectable - (UrlbarPrefs.get("resultMenu") ? 3 : 2),
+ "previous result"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
+
+// Picks the main part of the test result -- the non-help-button part -- with
+// the keyboard.
+add_task(async function pick_mainPart_keyboard() {
+ await doPickTest({ pickButton: false, useKeyboard: true });
+});
+
+// Picks the help button with the keyboard.
+add_task(async function pick_helpButton_keyboard() {
+ await doPickTest({ pickButton: true, useKeyboard: true });
+});
+
+// Picks the main part of the test result -- the non-help-button part -- with
+// the mouse.
+add_task(async function pick_mainPart_mouse() {
+ await doPickTest({ pickButton: false, useKeyboard: false });
+});
+
+// Picks the help button with the mouse.
+add_task(async function pick_helpButton_mouse() {
+ await doPickTest({ pickButton: true, useKeyboard: false });
+});
+
+async function doPickTest({ pickButton, useKeyboard }) {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ let index = 1;
+ let provider = registerTestProvider(index);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "example",
+ window,
+ });
+
+ // Sanity-check initial state.
+ Assert.equal(
+ UrlbarTestUtils.getSelectedElementIndex(window),
+ 0,
+ "The heuristic result should be selected"
+ );
+ await assertIsTestResult(index);
+
+ if (useKeyboard) {
+ // Arrow down to the result.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: index });
+ assertMainPartSelected(
+ UrlbarPrefs.get("resultMenu") ? index * 2 - 1 : index
+ );
+ }
+
+ // Pick the result. The appropriate URL should load.
+ let loadPromise = pickButton
+ ? BrowserTestUtils.waitForNewTab(gBrowser)
+ : BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ await Promise.all([
+ loadPromise,
+ UrlbarTestUtils.promisePopupClose(window, async () => {
+ if (pickButton && UrlbarPrefs.get("resultMenu")) {
+ await UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "h", {
+ openByMouse: !useKeyboard,
+ resultIndex: index,
+ });
+ } else if (useKeyboard) {
+ if (pickButton) {
+ // TAB to the button.
+ EventUtils.synthesizeKey("KEY_Tab");
+ assertButtonSelected(index + 1);
+ }
+ EventUtils.synthesizeKey("KEY_Enter");
+ } else {
+ // Get the click target.
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ index
+ );
+ let clickTarget = pickButton
+ ? result.element.row._buttons.get("help")
+ : result.element.row._content;
+ Assert.ok(
+ clickTarget,
+ "Click target found, pickButton=" + pickButton
+ );
+ EventUtils.synthesizeMouseAtCenter(clickTarget, {});
+ }
+ }),
+ ]);
+ Assert.equal(
+ gBrowser.selectedBrowser.currentURI.spec,
+ pickButton ? RESULT_HELP_URL : RESULT_URL,
+ "Expected URL should have loaded"
+ );
+
+ if (pickButton) {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+ UrlbarProvidersManager.unregisterProvider(provider);
+
+ // Avoid showing adaptive history autofill.
+ await PlacesTestUtils.clearInputHistory();
+ });
+}
+
+/**
+ * Registers a provider that creates a result with a help button.
+ *
+ * @param {number} suggestedIndex
+ * The result's suggestedIndex.
+ * @returns {UrlbarProvider}
+ * The new provider.
+ */
+function registerTestProvider(suggestedIndex) {
+ let results = [
+ Object.assign(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ url: RESULT_URL,
+ helpUrl: RESULT_HELP_URL,
+ helpL10n: {
+ id: UrlbarPrefs.get("resultMenu")
+ ? "urlbar-result-menu-tip-get-help"
+ : "urlbar-tip-help-icon",
+ },
+ }
+ ),
+ { suggestedIndex }
+ ),
+ ];
+ let provider = new UrlbarTestUtils.TestProvider({ results });
+ UrlbarProvidersManager.registerProvider(provider);
+ return provider;
+}
+
+/**
+ * Asserts that the result at the given index is our test result with a help
+ * button.
+ *
+ * @param {number} index
+ * The expected index of the test result.
+ */
+async function assertIsTestResult(index) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.URL,
+ "The second result should be a URL"
+ );
+ Assert.equal(
+ result.url,
+ RESULT_URL,
+ "The result's URL should be the expected URL"
+ );
+
+ let { row } = result.element;
+ if (UrlbarPrefs.get("resultMenu")) {
+ Assert.ok(row._buttons.get("menu"), "The result should have a menu button");
+ } else {
+ let helpButton = row._buttons.get("help");
+ Assert.ok(helpButton, "The result should have a help button");
+ Assert.ok(helpButton.id, "Help button has an ID");
+ }
+ Assert.ok(row._content.id, "Row-inner has an ID");
+ Assert.equal(
+ row.getAttribute("role"),
+ "presentation",
+ "Row should have role=presentation"
+ );
+ Assert.equal(
+ row._content.getAttribute("role"),
+ "option",
+ "Row-inner should have role=option"
+ );
+}
+
+/**
+ * Asserts that a particular element is selected.
+ *
+ * @param {number} expectedSelectedElementIndex
+ * The expected selected element index.
+ * @param {string} expectedClassName
+ * A class name of the expected selected element.
+ * @param {string} msg
+ * A string to include in the assertion message.
+ */
+function assertSelection(expectedSelectedElementIndex, expectedClassName, msg) {
+ Assert.equal(
+ UrlbarTestUtils.getSelectedElementIndex(window),
+ expectedSelectedElementIndex,
+ "Expected selected element index: " + msg
+ );
+ Assert.ok(
+ UrlbarTestUtils.getSelectedElement(window).classList.contains(
+ expectedClassName
+ ),
+ `Expected selected element: ${msg} (${
+ UrlbarTestUtils.getSelectedElement(window).classList
+ } == ${expectedClassName})`
+ );
+}
+
+/**
+ * Asserts that the main part of our test resut -- the non-help-button part --
+ * is selected.
+ *
+ * @param {number} expectedSelectedElementIndex
+ * The expected selected element index.
+ */
+function assertMainPartSelected(expectedSelectedElementIndex) {
+ assertSelection(
+ expectedSelectedElementIndex,
+ "urlbarView-row-inner",
+ "main part of test result"
+ );
+}
+
+/**
+ * Asserts that the help button part of our test result is selected.
+ *
+ * @param {number} expectedSelectedElementIndex
+ * The expected selected element index.
+ */
+function assertButtonSelected(expectedSelectedElementIndex) {
+ if (UrlbarPrefs.get("resultMenu")) {
+ assertSelection(
+ expectedSelectedElementIndex,
+ "urlbarView-button-menu",
+ "menu button"
+ );
+ } else {
+ assertSelection(
+ expectedSelectedElementIndex,
+ "urlbarView-button-help",
+ "help button"
+ );
+ }
+}
+
+/**
+ * Asserts that a result other than our test result is selected.
+ *
+ * @param {number} expectedSelectedElementIndex
+ * The expected selected element index.
+ * @param {string} msg
+ * A string to include in the assertion message.
+ */
+function assertOtherResultSelected(expectedSelectedElementIndex, msg) {
+ Assert.equal(
+ UrlbarTestUtils.getSelectedElementIndex(window),
+ expectedSelectedElementIndex,
+ "Expected other selected element index: " + msg
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/browser_heuristicNotAddedFirst.js b/browser/components/urlbar/tests/browser/browser_heuristicNotAddedFirst.js
new file mode 100644
index 0000000000..fa7c65b378
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_heuristicNotAddedFirst.js
@@ -0,0 +1,159 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// When the heuristic result is not the first result added, it should still be
+// selected.
+
+"use strict";
+
+// When the heuristic result is not the first result added, it should still be
+// selected.
+add_task(async function slowHeuristicSelected() {
+ // First, add a provider that adds a heuristic result on a delay. Both this
+ // provider and the one below have a high priority so that only they are used
+ // during the test.
+ let engine = await Services.search.getDefault();
+ let heuristicResult = new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ UrlbarUtils.RESULT_SOURCE.SEARCH,
+ {
+ suggestion: "test",
+ engine: engine.name,
+ }
+ );
+ heuristicResult.heuristic = true;
+ let heuristicProvider = new UrlbarTestUtils.TestProvider({
+ results: [heuristicResult],
+ name: "heuristicProvider",
+ priority: Infinity,
+ addTimeout: 500,
+ });
+ UrlbarProvidersManager.registerProvider(heuristicProvider);
+
+ // Second, add another provider that adds a non-heuristic result immediately
+ // with suggestedIndex = 1.
+ let nonHeuristicResult = makeTipResult();
+ nonHeuristicResult.suggestedIndex = 1;
+ let nonHeuristicProvider = new UrlbarTestUtils.TestProvider({
+ results: [nonHeuristicResult],
+ name: "nonHeuristicProvider",
+ priority: Infinity,
+ });
+ UrlbarProvidersManager.registerProvider(nonHeuristicProvider);
+
+ // Do a search.
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "test",
+ window: win,
+ });
+
+ // The first result should be the heuristic and it should be selected.
+ let actualHeuristic = await UrlbarTestUtils.getDetailsOfResultAt(win, 0);
+ Assert.equal(actualHeuristic.type, UrlbarUtils.RESULT_TYPE.SEARCH);
+ Assert.equal(UrlbarTestUtils.getSelectedElementIndex(win), 0);
+
+ // Check the second result for good measure.
+ let actualNonHeuristic = await UrlbarTestUtils.getDetailsOfResultAt(win, 1);
+ Assert.equal(actualNonHeuristic.type, UrlbarUtils.RESULT_TYPE.TIP);
+
+ await UrlbarTestUtils.promisePopupClose(win);
+ UrlbarProvidersManager.unregisterProvider(heuristicProvider);
+ UrlbarProvidersManager.unregisterProvider(nonHeuristicProvider);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// When the heuristic result is not the first result added but a one-off search
+// button is already selected, the heuristic result should not steal the
+// selection from the one-off button.
+add_task(async function oneOffRemainsSelected() {
+ // First, add a provider that adds a heuristic result on a delay. Both this
+ // provider and the one below have a high priority so that only they are used
+ // during the test.
+ let engine = await Services.search.getDefault();
+ let heuristicResult = new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ UrlbarUtils.RESULT_SOURCE.SEARCH,
+ {
+ suggestion: "test",
+ engine: engine.name,
+ }
+ );
+ heuristicResult.heuristic = true;
+ let heuristicProvider = new UrlbarTestUtils.TestProvider({
+ results: [heuristicResult],
+ name: "heuristicProvider",
+ priority: Infinity,
+ addTimeout: 500,
+ });
+ UrlbarProvidersManager.registerProvider(heuristicProvider);
+
+ // Second, add another provider that adds a non-heuristic result immediately
+ // with suggestedIndex = 1.
+ let nonHeuristicResult = makeTipResult();
+ nonHeuristicResult.suggestedIndex = 1;
+ let nonHeuristicProvider = new UrlbarTestUtils.TestProvider({
+ results: [nonHeuristicResult],
+ name: "nonHeuristicProvider",
+ priority: Infinity,
+ });
+ UrlbarProvidersManager.registerProvider(nonHeuristicProvider);
+
+ // Do a search but don't wait for it to finish.
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ let searchPromise = UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "test",
+ window: win,
+ });
+
+ // When the view opens, press the up arrow key to select the one-off search
+ // settings button. There's no point in selecting instead the non-heuristic
+ // result because once we do that, the search is canceled, and the heuristic
+ // result will never be added.
+ await UrlbarTestUtils.promisePopupOpen(win, () => {});
+ EventUtils.synthesizeKey("KEY_ArrowUp", {}, win);
+
+ // Wait for the search to finish.
+ await searchPromise;
+
+ // The first result should be the heuristic.
+ let actualHeuristic = await UrlbarTestUtils.getDetailsOfResultAt(win, 0);
+ Assert.equal(actualHeuristic.type, UrlbarUtils.RESULT_TYPE.SEARCH);
+
+ // Check the second result for good measure.
+ let actualNonHeuristic = await UrlbarTestUtils.getDetailsOfResultAt(win, 1);
+ Assert.equal(actualNonHeuristic.type, UrlbarUtils.RESULT_TYPE.TIP);
+
+ // No result should be selected.
+ Assert.equal(UrlbarTestUtils.getSelectedElement(win), null);
+ Assert.equal(UrlbarTestUtils.getSelectedElementIndex(win), -1);
+
+ // The one-off settings button should be selected.
+ Assert.equal(
+ win.gURLBar.view.oneOffSearchButtons.selectedButton,
+ win.gURLBar.view.oneOffSearchButtons.settingsButton
+ );
+
+ await UrlbarTestUtils.promisePopupClose(win);
+ UrlbarProvidersManager.unregisterProvider(heuristicProvider);
+ UrlbarProvidersManager.unregisterProvider(nonHeuristicProvider);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+function makeTipResult() {
+ return new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.TIP,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ helpUrl: "http://example.com/",
+ type: "test",
+ titleL10n: { id: "urlbar-search-tips-confirm" },
+ buttons: [
+ {
+ url: "http://example.com/",
+ l10n: { id: "urlbar-search-tips-confirm" },
+ },
+ ],
+ }
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/browser_hideHeuristic.js b/browser/components/urlbar/tests/browser/browser_hideHeuristic.js
new file mode 100644
index 0000000000..e8f8774e01
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_hideHeuristic.js
@@ -0,0 +1,513 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Basic smoke tests for the `browser.urlbar.experimental.hideHeuristic` pref,
+// which hides the heuristic result. Each task performs a search that triggers a
+// specific heuristic, verifies that it's hidden or shown as appropriate, and
+// verifies that it's picked when enter is pressed.
+//
+// If/when it becomes the default, we should update existing tests as necessary
+// and remove this one.
+
+"use strict";
+
+// Allow more time for Mac machines so they don't time out in verify mode.
+if (AppConstants.platform == "macosx") {
+ requestLongerTimeout(3);
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.experimental.hideHeuristic", true],
+ ["browser.urlbar.suggest.quickactions", false],
+ ],
+ });
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+});
+
+// UrlbarUtils.RESULT_GROUP.HEURISTIC_EXTENSION should be hidden.
+add_task(async function extension() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await withVisits(async visitURLs => {
+ // Add an extension provider that returns a heuristic.
+ let url = "http://example.com/extension-test";
+ let provider = new UrlbarTestUtils.TestProvider({
+ name: "ExtensionTest",
+ type: UrlbarUtils.PROVIDER_TYPE.EXTENSION,
+ results: [
+ Object.assign(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ url,
+ title: "Test",
+ }
+ ),
+ { heuristic: true }
+ ),
+ ],
+ });
+ UrlbarProvidersManager.registerProvider(provider);
+
+ // Do a search that fetches the provider's result and check it.
+ let heuristic = await search({
+ value: "test",
+ expectedGroup: UrlbarUtils.RESULT_GROUP.HEURISTIC_EXTENSION,
+ });
+ Assert.equal(heuristic.payload.url, url, "Heuristic URL is correct");
+
+ // Check the other visit results.
+ await checkVisitResults(visitURLs);
+
+ // Press enter to verify the heuristic result is loaded.
+ await synthesizeEnterAndAwaitLoad(url);
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+ });
+ });
+});
+
+// UrlbarUtils.RESULT_GROUP.HEURISTIC_OMNIBOX should be hidden.
+add_task(async function omnibox() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ // Load an extension.
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ omnibox: {
+ keyword: "omniboxtest",
+ },
+ },
+ background() {
+ /* global browser */
+ browser.omnibox.onInputEntered.addListener(() => {
+ browser.test.sendMessage("onInputEntered");
+ });
+ },
+ });
+ await extension.startup();
+
+ // Do a search using the omnibox keyword and check the hidden heuristic
+ // result.
+ let heuristic = await search({
+ value: "omniboxtest foo",
+ expectedGroup: UrlbarUtils.RESULT_GROUP.HEURISTIC_OMNIBOX,
+ });
+ Assert.equal(
+ heuristic.payload.keyword,
+ "omniboxtest",
+ "Heuristic keyword is correct"
+ );
+
+ // Press enter to verify the heuristic result is picked.
+ let messagePromise = extension.awaitMessage("onInputEntered");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await messagePromise;
+
+ await extension.unload();
+ });
+});
+
+// UrlbarUtils.RESULT_GROUP.HEURISTIC_SEARCH_TIP should be shown.
+add_task(async function searchTip() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.searchTips.test.ignoreShowLimits", true]],
+ });
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser: window.gBrowser,
+ url: "about:newtab",
+ // `withNewTab` hangs waiting for about:newtab to load without this.
+ waitForLoad: false,
+ },
+ async () => {
+ await UrlbarTestUtils.promisePopupOpen(window, () => {});
+ Assert.ok(true, "View opened");
+ Assert.equal(UrlbarTestUtils.getResultCount(window), 1);
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.TIP);
+ Assert.ok(result.heuristic);
+ Assert.ok(UrlbarTestUtils.getSelectedElement(window), "Selection exists");
+ }
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+// UrlbarUtils.RESULT_GROUP.HEURISTIC_ENGINE_ALIAS should be hidden.
+add_task(async function engineAlias() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await withVisits(async visitURLs => {
+ // Add an engine with an alias.
+ await withEngine({ keyword: "test" }, async () => {
+ // Do a search using the alias and check the hidden heuristic result.
+ // The heuristic will be HEURISTIC_FALLBACK, not HEURISTIC_ENGINE_ALIAS,
+ // because two searches are performed and
+ // `UrlbarTestUtils.promiseAutocompleteResultPopup` waits for both. The
+ // first returns a HEURISTIC_ENGINE_ALIAS that triggers search mode and
+ // then an immediate second search, which returns HEURISTIC_FALLBACK.
+ let heuristic = await search({
+ value: "test foo",
+ expectedGroup: UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK,
+ });
+ Assert.equal(
+ heuristic.payload.engine,
+ "Example",
+ "Heuristic engine is correct"
+ );
+ Assert.equal(
+ heuristic.payload.query,
+ "foo",
+ "Heuristic query is correct"
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: "Example",
+ entry: "typed",
+ });
+
+ // Check the other visit results.
+ await checkVisitResults(visitURLs);
+
+ // Press enter to verify the heuristic result is loaded.
+ await synthesizeEnterAndAwaitLoad("https://example.com/?q=foo");
+ });
+ });
+ });
+});
+
+// UrlbarUtils.RESULT_GROUP.HEURISTIC_BOOKMARK_KEYWORD should be hidden.
+add_task(async function bookmarkKeyword() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await withVisits(async visitURLs => {
+ // Add a bookmark with a keyword.
+ let keyword = "bm";
+ let bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/?q=%s",
+ title: "test",
+ });
+ await PlacesUtils.keywords.insert({ keyword, url: bm.url });
+
+ // Do a search using the keyword and check the hidden heuristic result.
+ let heuristic = await search({
+ value: "bm foo",
+ expectedGroup: UrlbarUtils.RESULT_GROUP.HEURISTIC_BOOKMARK_KEYWORD,
+ });
+ Assert.equal(
+ heuristic.payload.keyword,
+ keyword,
+ "Heuristic keyword is correct"
+ );
+ let heuristicURL = "http://example.com/?q=foo";
+ Assert.equal(
+ heuristic.payload.url,
+ heuristicURL,
+ "Heuristic URL is correct"
+ );
+
+ // Check the other visit results.
+ await checkVisitResults(visitURLs);
+
+ // Press enter to verify the heuristic result is loaded.
+ await synthesizeEnterAndAwaitLoad(heuristicURL);
+
+ await PlacesUtils.bookmarks.eraseEverything();
+ await UrlbarTestUtils.formHistory.clear(window);
+ });
+ });
+});
+
+// UrlbarUtils.RESULT_GROUP.HEURISTIC_AUTOFILL should be hidden.
+add_task(async function autofill() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await withVisits(async visitURLs => {
+ // Do a search that triggers autofill and check the hidden heuristic
+ // result.
+ let heuristic = await search({
+ value: "ex",
+ expectedGroup: UrlbarUtils.RESULT_GROUP.HEURISTIC_AUTOFILL,
+ });
+ Assert.ok(heuristic.autofill, "Heuristic is autofill");
+ let heuristicURL = "http://example.com/";
+ Assert.equal(
+ heuristic.payload.url,
+ heuristicURL,
+ "Heuristic URL is correct"
+ );
+ Assert.equal(gURLBar.value, "example.com/", "Input has been autofilled");
+
+ // Check the other visit results.
+ await checkVisitResults(visitURLs);
+
+ // Press enter to verify the heuristic result is loaded.
+ await synthesizeEnterAndAwaitLoad(heuristicURL);
+ });
+ });
+});
+
+// UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK with an unknown URL should be
+// hidden.
+add_task(async function fallback_unknownURL() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ // Do a search for an unknown URL and check the hidden heuristic result.
+ let url = "http://example.com/unknown-url";
+ let heuristic = await search({
+ value: url,
+ expectedGroup: UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK,
+ });
+ Assert.equal(heuristic.payload.url, url, "Heuristic URL is correct");
+
+ // Press enter to verify the heuristic result is loaded.
+ await synthesizeEnterAndAwaitLoad(url);
+ });
+});
+
+// UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK with the search restriction token
+// should be hidden.
+add_task(async function fallback_searchRestrictionToken() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await withVisits(async visitURLs => {
+ // Add a mock default engine so we don't hit the network.
+ await withEngine({ makeDefault: true }, async () => {
+ // Do a search with `?` and check the hidden heuristic result.
+ let heuristic = await search({
+ value: UrlbarTokenizer.RESTRICT.SEARCH + " foo",
+ expectedGroup: UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK,
+ });
+ Assert.equal(
+ heuristic.payload.engine,
+ "Example",
+ "Heuristic engine is correct"
+ );
+ Assert.equal(
+ heuristic.payload.query,
+ "foo",
+ "Heuristic query is correct"
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: "Example",
+ entry: "typed",
+ });
+
+ // Check the other visit results.
+ await checkVisitResults(visitURLs);
+
+ // Press enter to verify the heuristic result is loaded.
+ await synthesizeEnterAndAwaitLoad("https://example.com/?q=foo");
+
+ await UrlbarTestUtils.formHistory.clear(window);
+ });
+ });
+ });
+});
+
+// UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK with a search string that falls
+// back to a search result should be hidden.
+add_task(async function fallback_search() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await withVisits(async visitURLs => {
+ // Add a mock default engine so we don't hit the network.
+ await withEngine({ makeDefault: true }, async () => {
+ // Do a search and check the hidden heuristic result.
+ let heuristic = await search({
+ value: "foo",
+ expectedGroup: UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK,
+ });
+ Assert.equal(
+ heuristic.payload.engine,
+ "Example",
+ "Heuristic engine is correct"
+ );
+ Assert.equal(
+ heuristic.payload.query,
+ "foo",
+ "Heuristic query is correct"
+ );
+
+ // Check the other visit results.
+ await checkVisitResults(visitURLs);
+
+ // Press enter to verify the heuristic result is loaded.
+ await synthesizeEnterAndAwaitLoad("https://example.com/?q=foo");
+
+ await UrlbarTestUtils.formHistory.clear(window);
+ });
+ });
+ });
+});
+
+// Picking a non-heuristic result should work correctly (and not pick the
+// heuristic).
+add_task(async function pickNonHeuristic() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await withVisits(async visitURLs => {
+ // Do a search that triggers autofill and check the hidden heuristic
+ // result.
+ let heuristic = await search({
+ value: "ex",
+ expectedGroup: UrlbarUtils.RESULT_GROUP.HEURISTIC_AUTOFILL,
+ });
+ Assert.ok(heuristic.autofill, "Heuristic is autofill");
+ Assert.equal(
+ heuristic.payload.url,
+ "http://example.com/",
+ "Heuristic URL is correct"
+ );
+
+ // Pick the first visit result.
+ Assert.notEqual(
+ heuristic.payload.url,
+ visitURLs[0],
+ "Sanity check: Heuristic and first results have different URLs"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ await synthesizeEnterAndAwaitLoad(visitURLs[0]);
+ });
+ });
+});
+
+/**
+ * Adds `maxRichResults` visits, calls your callback, and clears history. We add
+ * `maxRichResults` visits to verify that the view correctly contains the
+ * maximum number of results when the heuristic is hidden.
+ *
+ * @param {Function} callback
+ * The callback to call after adding visits. Can be async
+ */
+async function withVisits(callback) {
+ let urls = [];
+ for (let i = 0; i < UrlbarPrefs.get("maxRichResults"); i++) {
+ urls.push("http://example.com/foo/" + i);
+ }
+ await PlacesTestUtils.addVisits(urls);
+
+ // The URLs will appear in the view in reverse order so that newer visits are
+ // first. Reverse the array now so callers to `checkVisitResults` or
+ // `checkVisitResults` itself doesn't need to do it.
+ urls.reverse();
+
+ await callback(urls);
+ await PlacesUtils.history.clear();
+}
+
+/**
+ * Adds a search engine, calls your callback, and removes the engine.
+ *
+ * @param {object} options
+ * Options object
+ * @param {string} [options.keyword]
+ * The keyword/alias for the engine.
+ * @param {boolean} [options.makeDefault]
+ * Whether to make the engine default.
+ * @param {Function} callback
+ * The callback to call after changing the default search engine. Can be async
+ */
+async function withEngine(
+ { keyword = undefined, makeDefault = false },
+ callback
+) {
+ await SearchTestUtils.installSearchExtension({ keyword });
+ let engine = Services.search.getEngineByName("Example");
+ let originalEngine;
+ if (makeDefault) {
+ originalEngine = await Services.search.getDefault();
+ await Services.search.setDefault(
+ engine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ }
+ await callback();
+ if (originalEngine) {
+ await Services.search.setDefault(
+ originalEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ }
+ await Services.search.removeEngine(engine);
+}
+
+/**
+ * Asserts the view contains visit results with the given URLs.
+ *
+ * @param {Array} expectedURLs
+ * The expected urls.
+ */
+async function checkVisitResults(expectedURLs) {
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ expectedURLs.length,
+ "The view has other results"
+ );
+ for (let i = 0; i < expectedURLs.length; i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.URL,
+ "Other result type is correct at index " + i
+ );
+ Assert.equal(
+ result.source,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ "Other result source is correct at index " + i
+ );
+ Assert.equal(
+ result.url,
+ expectedURLs[i],
+ "Other result URL is correct at index " + i
+ );
+ }
+}
+
+/**
+ * Performs a search and makes some basic assertions under the assumption that
+ * the heuristic should be hidden.
+ *
+ * @param {object} options
+ * Options object
+ * @param {string} options.value
+ * The search string.
+ * @param {UrlbarUtils.RESULT_GROUP} options.expectedGroup
+ * The expected result group of the hidden heuristic.
+ * @returns {UrlbarResult}
+ * The hidden heuristic result.
+ */
+async function search({ value, expectedGroup }) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value,
+ fireInputEvent: true,
+ });
+
+ // _resultForCurrentValue should be the hidden heuristic result.
+ let { _resultForCurrentValue: result } = gURLBar;
+ Assert.ok(result, "_resultForCurrentValue is defined");
+ Assert.ok(result.heuristic, "_resultForCurrentValue.heuristic is true");
+ Assert.equal(
+ UrlbarUtils.getResultGroup(result),
+ expectedGroup,
+ "_resultForCurrentValue has expected group"
+ );
+
+ Assert.ok(!UrlbarTestUtils.getSelectedElement(window), "No selection exists");
+
+ return result;
+}
+
+/**
+ * Synthesizes the enter key and waits for a load in the current tab.
+ *
+ * @param {string} expectedURL
+ * The URL that should load.
+ */
+async function synthesizeEnterAndAwaitLoad(expectedURL) {
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ expectedURL
+ );
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ await PlacesUtils.history.clear();
+}
diff --git a/browser/components/urlbar/tests/browser/browser_ime_composition.js b/browser/components/urlbar/tests/browser/browser_ime_composition.js
new file mode 100644
index 0000000000..13a0cf0584
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_ime_composition.js
@@ -0,0 +1,327 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests ime composition handling.
+
+function composeAndCheckPanel(string, isPopupOpen) {
+ EventUtils.synthesizeCompositionChange({
+ composition: {
+ string,
+ clauses: [
+ {
+ length: string.length,
+ attr: Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE,
+ },
+ ],
+ },
+ caret: { start: string.length, length: 0 },
+ key: { key: string ? string[string.length - 1] : "KEY_Backspace" },
+ });
+ Assert.equal(
+ UrlbarTestUtils.isPopupOpen(window),
+ isPopupOpen,
+ "Check panel open state"
+ );
+}
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.tabToSearch.onboard.interactionsLeft", 0],
+ ["browser.urlbar.suggest.quickactions", false],
+ ],
+ });
+
+ await PlacesUtils.history.clear();
+ // Add at least one typed entry for the empty results set. Also clear history
+ // so that this can be over the autofill threshold.
+ await PlacesTestUtils.addVisits({
+ uri: "http://mozilla.org/",
+ transition: PlacesUtils.history.TRANSITIONS.TYPED,
+ });
+ // Add a bookmark to ensure we autofill the engine domain for tab-to-search.
+ let bm = await PlacesUtils.bookmarks.insert({
+ url: "http://example.com/",
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ });
+
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: "Test",
+ keyword: "@test",
+ },
+ { setAsDefault: true }
+ );
+
+ registerCleanupFunction(async () => {
+ await PlacesUtils.bookmarks.remove(bm);
+ await PlacesUtils.history.clear();
+ });
+
+ // Test both pref values.
+ for (let val of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.keepPanelOpenDuringImeComposition", val]],
+ });
+ await test_composition(val);
+ await test_composition_searchMode_preview(val);
+ await test_composition_tabToSearch(val);
+ await test_composition_autofill(val);
+ }
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+});
+
+async function test_composition(keepPanelOpenDuringImeComposition) {
+ gURLBar.focus();
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ info("Check the panel state during composition");
+ composeAndCheckPanel("I", false);
+ Assert.equal(gURLBar.value, "I", "Check urlbar value");
+ composeAndCheckPanel("In", false);
+ Assert.equal(gURLBar.value, "In", "Check urlbar value");
+
+ info("Committing composition should open the panel.");
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeComposition({
+ type: "compositioncommitasis",
+ key: { key: "KEY_Enter" },
+ });
+ });
+ Assert.equal(gURLBar.value, "In", "Check urlbar value");
+
+ info("Check the panel state starting from an open panel.");
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Popup should be open");
+ composeAndCheckPanel("t", keepPanelOpenDuringImeComposition);
+ Assert.equal(gURLBar.value, "Int", "Check urlbar value");
+ composeAndCheckPanel("te", keepPanelOpenDuringImeComposition);
+ Assert.equal(gURLBar.value, "Inte", "Check urlbar value");
+
+ // Committing composition should open the popup.
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeComposition({
+ type: "compositioncommitasis",
+ key: { key: "KEY_Enter" },
+ });
+ });
+ Assert.equal(gURLBar.value, "Inte", "Check urlbar value");
+
+ info("If composition is cancelled, the value shouldn't be changed.");
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Popup should be open");
+ composeAndCheckPanel("r", keepPanelOpenDuringImeComposition);
+ Assert.equal(gURLBar.value, "Inter", "Check urlbar value");
+ composeAndCheckPanel("", keepPanelOpenDuringImeComposition);
+ Assert.equal(gURLBar.value, "Inte", "Check urlbar value");
+ // Canceled compositionend should reopen the popup.
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeComposition({
+ type: "compositioncommit",
+ data: "",
+ key: { key: "KEY_Escape" },
+ });
+ });
+ Assert.equal(gURLBar.value, "Inte", "Check urlbar value");
+
+ info(
+ "If composition replaces some characters and canceled, the search string should be the latest value."
+ );
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Popup should be open");
+ EventUtils.synthesizeKey("VK_LEFT", { shiftKey: true });
+ EventUtils.synthesizeKey("VK_LEFT", { shiftKey: true });
+ composeAndCheckPanel("t", keepPanelOpenDuringImeComposition);
+ Assert.equal(gURLBar.value, "Int", "Check urlbar value");
+ composeAndCheckPanel("te", keepPanelOpenDuringImeComposition);
+ Assert.equal(gURLBar.value, "Inte", "Check urlbar value");
+ composeAndCheckPanel("", keepPanelOpenDuringImeComposition);
+ Assert.equal(gURLBar.value, "In", "Check urlbar value");
+
+ // Canceled compositionend should search the result with the latest value.
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeComposition({
+ type: "compositioncommitasis",
+ key: { key: "KEY_Escape" },
+ });
+ });
+ Assert.equal(gURLBar.value, "In", "Check urlbar value");
+
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Popup should be open");
+ info(
+ "Removing all characters should leave the popup open, Esc should then close it."
+ );
+ EventUtils.synthesizeKey("KEY_Backspace", {});
+ EventUtils.synthesizeKey("KEY_Backspace", {});
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Popup should be open");
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_Escape", {});
+ });
+ Assert.equal(gURLBar.value, "", "Check urlbar value");
+
+ info("Composition which is canceled shouldn't cause opening the popup.");
+ Assert.ok(!UrlbarTestUtils.isPopupOpen(window), "Popup should be closed");
+ composeAndCheckPanel("I", false);
+ Assert.equal(gURLBar.value, "I", "Check urlbar value");
+ composeAndCheckPanel("In", false);
+ Assert.equal(gURLBar.value, "In", "Check urlbar value");
+ composeAndCheckPanel("", false);
+ Assert.equal(gURLBar.value, "", "Check urlbar value");
+
+ info("Canceled compositionend shouldn't open the popup if it was closed.");
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeComposition({
+ type: "compositioncommitasis",
+ key: { key: "KEY_Escape" },
+ });
+ Assert.ok(!UrlbarTestUtils.isPopupOpen(window), "Popup should be closed");
+ Assert.equal(gURLBar.value, "", "Check urlbar value");
+
+ info("Down key should open the popup even if the editor is empty.");
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {});
+ });
+ Assert.equal(gURLBar.value, "", "Check urlbar value");
+
+ info(
+ "If popup is open at starting composition, the popup should be reopened after composition anyway."
+ );
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Popup should be open");
+ composeAndCheckPanel("I", keepPanelOpenDuringImeComposition);
+ Assert.equal(gURLBar.value, "I", "Check urlbar value");
+ composeAndCheckPanel("In", keepPanelOpenDuringImeComposition);
+ Assert.equal(gURLBar.value, "In", "Check urlbar value");
+ composeAndCheckPanel("", keepPanelOpenDuringImeComposition);
+ Assert.equal(gURLBar.value, "", "Check urlbar value");
+ // A canceled compositionend should open the popup if it was open.
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeComposition({
+ type: "compositioncommitasis",
+ key: { key: "KEY_Escape" },
+ });
+ });
+ Assert.equal(gURLBar.value, "", "Check urlbar value");
+
+ info("Type normally, and hit escape, the popup should be closed.");
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Popup should be open");
+ EventUtils.synthesizeKey("I", {});
+ EventUtils.synthesizeKey("n", {});
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_Escape", {});
+ });
+ Assert.equal(gURLBar.value, "In", "Check urlbar value");
+ // Clear typed chars.
+ EventUtils.synthesizeKey("KEY_Backspace", {});
+ EventUtils.synthesizeKey("KEY_Backspace", {});
+ Assert.equal(gURLBar.value, "", "Check urlbar value");
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_Escape", {});
+ });
+
+ info("With autofill, compositionstart shouldn't open the popup");
+ Assert.ok(!UrlbarTestUtils.isPopupOpen(window), "Popup should be closed");
+ composeAndCheckPanel("M", false);
+ Assert.equal(gURLBar.value, "M", "Check urlbar value");
+ composeAndCheckPanel("Mo", false);
+ Assert.equal(gURLBar.value, "Mo", "Check urlbar value");
+ // Committing composition should open the popup.
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeComposition({
+ type: "compositioncommitasis",
+ key: { key: "KEY_Enter" },
+ });
+ });
+ Assert.equal(gURLBar.value, "Mozilla.org/", "Check urlbar value");
+}
+
+async function test_composition_searchMode_preview(
+ keepPanelOpenDuringImeComposition
+) {
+ info("Check Search Mode preview is retained by composition");
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ });
+
+ while (gURLBar.searchMode?.engineName != "Test") {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ }
+ let expectedSearchMode = {
+ engineName: "Test",
+ isPreview: true,
+ entry: "keywordoffer",
+ };
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+ composeAndCheckPanel("I", keepPanelOpenDuringImeComposition);
+ Assert.equal(gURLBar.value, "I", "Check urlbar value");
+ if (keepPanelOpenDuringImeComposition) {
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ }
+ // Test that we are in confirmed search mode.
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: "Test",
+ entry: "keywordoffer",
+ });
+ await UrlbarTestUtils.exitSearchMode(window);
+}
+
+async function test_composition_tabToSearch(keepPanelOpenDuringImeComposition) {
+ info("Check Tab-to-Search is retained by composition");
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exa",
+ fireInputEvent: true,
+ });
+
+ while (gURLBar.searchMode?.engineName != "Test") {
+ EventUtils.synthesizeKey("KEY_Tab", {}, window);
+ }
+ let expectedSearchMode = {
+ engineName: "Test",
+ isPreview: true,
+ entry: "tabtosearch",
+ };
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+ composeAndCheckPanel("I", keepPanelOpenDuringImeComposition);
+ Assert.equal(gURLBar.value, "I", "Check urlbar value");
+ if (keepPanelOpenDuringImeComposition) {
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ }
+ // Test that we are in confirmed search mode.
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: "Test",
+ entry: "tabtosearch",
+ });
+ await UrlbarTestUtils.exitSearchMode(window);
+}
+
+async function test_composition_autofill(keepPanelOpenDuringImeComposition) {
+ info("Check whether autofills or not");
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ info("Check the urlbar value during composition");
+ composeAndCheckPanel("m", false);
+
+ if (keepPanelOpenDuringImeComposition) {
+ info("Wait for search suggestions");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ }
+
+ Assert.equal(
+ gURLBar.value,
+ "m",
+ "The urlbar value is not autofilled while turning IME on"
+ );
+
+ info("Check the urlbar value after committing composition");
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeComposition({
+ type: "compositioncommitasis",
+ key: { key: "KEY_Enter" },
+ });
+ });
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.equal(gURLBar.value, "mozilla.org/", "The urlbar value is autofilled");
+
+ // Clean-up.
+ gURLBar.value = "";
+}
diff --git a/browser/components/urlbar/tests/browser/browser_inputHistory.js b/browser/components/urlbar/tests/browser/browser_inputHistory.js
new file mode 100644
index 0000000000..7fb93ca35d
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_inputHistory.js
@@ -0,0 +1,548 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests the urlbar adaptive behavior powered by input history.
+ */
+
+"use strict";
+
+async function bumpScore(
+ uri,
+ searchString,
+ counts,
+ useMouseClick = false,
+ needToLoad = false
+) {
+ if (counts.visits) {
+ let visits = new Array(counts.visits).fill(uri);
+ await PlacesTestUtils.addVisits(visits);
+ }
+ if (counts.picks) {
+ for (let i = 0; i < counts.picks; ++i) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ });
+ let promise = needToLoad
+ ? BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser)
+ : BrowserTestUtils.waitForDocLoadAndStopIt(
+ uri,
+ gBrowser.selectedBrowser
+ );
+ // Look for the expected uri.
+ while (gURLBar.untrimmedValue != uri) {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {});
+ }
+ if (useMouseClick) {
+ let element = UrlbarTestUtils.getSelectedRow(window);
+ EventUtils.synthesizeMouseAtCenter(element, {});
+ } else {
+ EventUtils.synthesizeKey("KEY_Enter", {});
+ }
+ await promise;
+ }
+ }
+ await PlacesTestUtils.promiseAsyncUpdates();
+}
+
+async function decayInputHistory() {
+ await Cc["@mozilla.org/places/frecency-recalculator;1"]
+ .getService(Ci.nsIObserver)
+ .wrappedJSObject.decay();
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // We don't want autofill to influence this test.
+ ["browser.urlbar.autoFill", false],
+ ],
+ });
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ });
+});
+
+add_task(async function test_adaptive_with_search_terms() {
+ let url1 = "http://site.tld/1";
+ let url2 = "http://site.tld/2";
+
+ info("Same visit count, same picks, one partial match, one exact match");
+ await PlacesUtils.history.clear();
+ await bumpScore(url1, "si", { visits: 3, picks: 3 });
+ await bumpScore(url2, "site", { visits: 3, picks: 3 });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url1, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url2, "Check second result");
+
+ info(
+ "Same visit count, same picks, one partial match, one exact match, invert"
+ );
+ await PlacesUtils.history.clear();
+ await bumpScore(url1, "site", { visits: 3, picks: 3 });
+ await bumpScore(url2, "si", { visits: 3, picks: 3 });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url2, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url1, "Check second result");
+
+ info("Same visit count, different picks, both exact match");
+ await PlacesUtils.history.clear();
+ await bumpScore(url1, "si", { visits: 3, picks: 3 });
+ await bumpScore(url2, "si", { visits: 3, picks: 1 });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url1, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url2, "Check second result");
+
+ info("Same visit count, different picks, both exact match, invert");
+ await PlacesUtils.history.clear();
+ await bumpScore(url1, "si", { visits: 3, picks: 1 });
+ await bumpScore(url2, "si", { visits: 3, picks: 3 });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url2, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url1, "Check second result");
+
+ info("Same visit count, different picks, both partial match");
+ await PlacesUtils.history.clear();
+ await bumpScore(url1, "site", { visits: 3, picks: 3 });
+ await bumpScore(url2, "site", { visits: 3, picks: 1 });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url1, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url2, "Check second result");
+
+ info("Same visit count, different picks, both partial match, invert");
+ await PlacesUtils.history.clear();
+ await bumpScore(url1, "site", { visits: 3, picks: 1 });
+ await bumpScore(url2, "site", { visits: 3, picks: 3 });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url2, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url1, "Check second result");
+});
+
+add_task(async function test_adaptive_with_decay() {
+ let url1 = "http://site.tld/1";
+ let url2 = "http://site.tld/2";
+
+ info("Same visit count, same picks, both exact match, decay first");
+ await PlacesUtils.history.clear();
+ await bumpScore(url1, "si", { visits: 3, picks: 3 });
+ await decayInputHistory();
+ await bumpScore(url2, "si", { visits: 3, picks: 3 });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url2, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url1, "Check second result");
+
+ info("Same visit count, same picks, both exact match, decay second");
+ await PlacesUtils.history.clear();
+ await bumpScore(url2, "si", { visits: 3, picks: 3 });
+ await decayInputHistory();
+ await bumpScore(url1, "si", { visits: 3, picks: 3 });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url1, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url2, "Check second result");
+});
+
+add_task(async function test_adaptive_limited() {
+ let url1 = "http://site.tld/1";
+ let url2 = "http://site.tld/2";
+
+ info("Same visit count, same picks, both exact match, decay first");
+ await PlacesUtils.history.clear();
+ await bumpScore(url1, "si", { visits: 3, picks: 3 });
+ await decayInputHistory();
+ await bumpScore(url2, "si", { visits: 3, picks: 3 });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url2, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url1, "Check second result");
+
+ info("Same visit count, same picks, both exact match, decay second");
+ await PlacesUtils.history.clear();
+ await bumpScore(url2, "si", { visits: 3, picks: 3 });
+ await decayInputHistory();
+ await bumpScore(url1, "si", { visits: 3, picks: 3 });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url1, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url2, "Check second result");
+});
+
+add_task(async function test_adaptive_limited() {
+ info("Up to 3 adaptive results should be added at the top, then enqueued");
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ // Add as many adaptive results as maxRichResults.
+ let n = UrlbarPrefs.get("maxRichResults");
+ let urls = Array(n)
+ .fill(0)
+ .map((e, i) => "http://site.tld/" + i);
+ for (let url of urls) {
+ await bumpScore(url, "site", { visits: 1, picks: 1 });
+ }
+
+ // Add a matching bookmark with an higher frecency.
+ let url = "http://site.bookmark.tld/";
+ await PlacesTestUtils.addVisits(url);
+ let bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "test_site_book",
+ url,
+ });
+
+ // After 1 heuristic and 3 input history results.
+ let expectedBookmarkIndex = 4;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "site",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ expectedBookmarkIndex
+ );
+ Assert.equal(result.url, url, "Check bookmarked result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, n - 1);
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ n,
+ "Check all the results are filled"
+ );
+ Assert.ok(
+ result.url.startsWith("http://site.tld"),
+ "Check last adaptive result"
+ );
+
+ await PlacesUtils.bookmarks.remove(bm);
+});
+
+add_task(async function test_adaptive_behaviors() {
+ info(
+ "Check adaptive results are not provided regardless of the requested behavior"
+ );
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ // Add an adaptive entry.
+ let historyUrl = "http://site.tld/1";
+ await bumpScore(historyUrl, "site", { visits: 1, picks: 1 });
+
+ let bookmarkURL = "http://bookmarked.site.tld/1";
+ let bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "test_book",
+ url: bookmarkURL,
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Search only bookmarks.
+ ["browser.urlbar.suggest.bookmark", true],
+ ["browser.urlbar.suggest.history", false],
+ ],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "site",
+ });
+ let result = (await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1))
+ .result;
+ Assert.equal(result.payload.url, bookmarkURL, "Check bookmarked result");
+ Assert.notEqual(
+ result.providerName,
+ "InputHistory",
+ "The bookmarked result is not from InputHistory."
+ );
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "Check there are no unexpected results"
+ );
+ await PlacesUtils.bookmarks.remove(bm);
+
+ // Repeat the previous case but now the bookmark has the same URL as the
+ // history result. We expect the returned result comes from InputHistory.
+ bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "test_book",
+ url: historyUrl,
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "sit",
+ });
+ result = (await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1))
+ .result;
+ Assert.equal(result.payload.url, historyUrl, "Check bookmarked result");
+ Assert.equal(
+ result.providerName,
+ "InputHistory",
+ "The bookmarked result is from InputHistory."
+ );
+ Assert.equal(
+ result.source,
+ UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ "The input history result is a bookmark."
+ );
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "Check there are no unexpected results"
+ );
+
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Search only open pages. We don't provide an open page, but we want to
+ // enable at least one of these prefs so that UrlbarProviderInputHistory
+ // is active.
+ ["browser.urlbar.suggest.bookmark", false],
+ ["browser.urlbar.suggest.history", false],
+ ["browser.urlbar.suggest.openpage", true],
+ ],
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "site",
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 1,
+ "There is no adaptive history result because it is not an open page."
+ );
+ await SpecialPowers.popPrefEnv();
+
+ // Clearing history but not deleting the bookmark. This simulates the case
+ // where the user has cleared their history or is using permanent private
+ // browsing mode.
+ await PlacesUtils.history.clear();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.bookmark", true],
+ ["browser.urlbar.suggest.history", false],
+ ["browser.urlbar.suggest.openpage", false],
+ ],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "sit",
+ });
+ result = (await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1))
+ .result;
+ Assert.equal(result.payload.url, historyUrl, "Check bookmarked result");
+ Assert.equal(
+ result.providerName,
+ "InputHistory",
+ "The bookmarked result is from InputHistory."
+ );
+ Assert.equal(
+ result.source,
+ UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ "The input history result is a bookmark."
+ );
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "Check there are no unexpected results"
+ );
+
+ await PlacesUtils.bookmarks.remove(bm);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_adaptive_mouse() {
+ info("Check adaptive results are updated on mouse picks");
+ let url1 = "http://site.tld/1";
+ let url2 = "http://site.tld/2";
+
+ info("Same visit count, different picks");
+ await PlacesUtils.history.clear();
+ await bumpScore(url1, "site", { visits: 3, picks: 3 }, true);
+ await bumpScore(url2, "site", { visits: 3, picks: 1 }, true);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url1, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url2, "Check second result");
+
+ info("Same visit count, different picks, invert");
+ await PlacesUtils.history.clear();
+ await bumpScore(url1, "site", { visits: 3, picks: 1 }, true);
+ await bumpScore(url2, "site", { visits: 3, picks: 3 }, true);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url2, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url1, "Check second result");
+});
+
+add_task(async function test_adaptive_searchmode() {
+ info("Check adaptive history is not shown in search mode.");
+
+ let suggestionsEngine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ });
+
+ let url1 = "http://site.tld/1";
+ let url2 = "http://site.tld/2";
+
+ info("Sanity check: adaptive history is shown for a normal search.");
+ await PlacesUtils.history.clear();
+ await bumpScore(url1, "site", { visits: 3, picks: 3 }, true);
+ await bumpScore(url2, "site", { visits: 3, picks: 1 }, true);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "si",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, url1, "Check first result");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(result.url, url2, "Check second result");
+
+ info("Entering search mode.");
+ // enterSearchMode checks internally that our site.tld URLs are not shown.
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: suggestionsEngine.name,
+ });
+
+ await Services.search.removeEngine(suggestionsEngine);
+});
+
+add_task(async function test_ignore_case() {
+ const url1 = "http://example.com/yes";
+ const url2 = "http://example.com/no";
+ await PlacesUtils.history.clear();
+ await PlacesTestUtils.addVisits([url1, url2]);
+ await UrlbarUtils.addToInputHistory(url1, "SampLE");
+ await UrlbarUtils.addToInputHistory(url1, "SaMpLE");
+ await UrlbarUtils.addToInputHistory(url1, "SAMPLE");
+ await UrlbarUtils.addToInputHistory(url1, "sample");
+ await UrlbarUtils.addToInputHistory(url2, "sample");
+ await UrlbarUtils.addToInputHistory(url2, "sample");
+ await UrlbarUtils.addToInputHistory(url2, "sample");
+ await UrlbarUtils.addToInputHistory(url2, "sample");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "sAM",
+ });
+ const result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ result.url,
+ url1,
+ "Seaching for input history is case-insensitive"
+ );
+});
+
+add_task(async function test_adaptive_history_in_privatewindow() {
+ info(
+ "Check adaptive history is not shown in private window as tab switching candidate."
+ );
+
+ await PlacesUtils.history.clear();
+
+ info("Add a test url as an input history.");
+ const url = "http://example.com/";
+ // We need to wait for loading the page in order to register the url into
+ // moz_openpages_temp table.
+ await bumpScore(url, "exa", { visits: 1, picks: 1 }, false, true);
+
+ info("Check the url could be registered properly.");
+ const connection = await PlacesUtils.promiseLargeCacheDBConnection();
+ const rows = await connection.executeCached(
+ "SELECT userContextId FROM moz_openpages_temp WHERE url = :url",
+ { url }
+ );
+ Assert.equal(rows.length, 1, "Length of rows for the url is 1.");
+ Assert.greaterOrEqual(
+ rows[0].getResultByName("userContextId"),
+ 0,
+ "The url is registered as non-private-browsing context."
+ );
+
+ info("Open popup in private window.");
+ const privateWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: privateWindow,
+ value: "ex",
+ });
+
+ info("Check the popup results.");
+ let hasResult = false;
+ for (let i = 0; i < UrlbarTestUtils.getResultCount(privateWindow); i++) {
+ const result = await UrlbarTestUtils.getDetailsOfResultAt(privateWindow, i);
+
+ if (result.url !== url) {
+ continue;
+ }
+
+ Assert.notEqual(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
+ "Result type of the url is not for tab switch."
+ );
+
+ hasResult = true;
+ }
+ Assert.ok(hasResult, "Popup has a result for the url.");
+
+ await BrowserTestUtils.closeWindow(privateWindow);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_inputHistory_autofill.js b/browser/components/urlbar/tests/browser/browser_inputHistory_autofill.js
new file mode 100644
index 0000000000..19457884b6
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_inputHistory_autofill.js
@@ -0,0 +1,207 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests for input history related to autofill.
+
+"use strict";
+
+let addToInputHistorySpy;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.autoFill.adaptiveHistory.enabled", true]],
+ });
+
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+
+ let sandbox = sinon.createSandbox();
+ addToInputHistorySpy = sandbox.spy(UrlbarUtils, "addToInputHistory");
+
+ registerCleanupFunction(async () => {
+ sandbox.restore();
+ });
+});
+
+// Input history use count should be bumped when an adaptive history autofill
+// result is triggered and picked.
+add_task(async function bumped() {
+ let input = "exam";
+ let tests = [
+ // Basic test where the search string = the adaptive history input.
+ {
+ url: "http://example.com/test",
+ searchString: "exam",
+ },
+ // The history with input "exam" should be bumped, not "example", even
+ // though the search string is "example".
+ {
+ url: "http://example.com/test",
+ searchString: "example",
+ },
+ // The history with URL "http://www.example.com/test" should be bumped, not
+ // "http://example.com/test", even though the search string starts with
+ // "example".
+ {
+ url: "http://www.example.com/test",
+ searchString: "exam",
+ },
+ ];
+
+ for (let { url, searchString } of tests) {
+ info("Running subtest: " + JSON.stringify({ url, searchString }));
+
+ await PlacesTestUtils.addVisits(url);
+ await UrlbarUtils.addToInputHistory(url, input);
+ addToInputHistorySpy.resetHistory();
+
+ let initialUseCount = await getUseCount({ url, input });
+ info("Got initial use count: " + initialUseCount);
+
+ await triggerAutofillAndPickResult(searchString, "example.com/test");
+
+ let calls = addToInputHistorySpy.getCalls();
+ Assert.equal(
+ calls.length,
+ 1,
+ "UrlbarUtils.addToInputHistory() called once"
+ );
+ Assert.deepEqual(
+ calls[0].args,
+ [url, input],
+ "UrlbarUtils.addToInputHistory() called with expected args"
+ );
+
+ Assert.greater(
+ await getUseCount({ url, input }),
+ initialUseCount,
+ "New use count > initial use count"
+ );
+
+ if (searchString != input) {
+ Assert.strictEqual(
+ await getUseCount({ input: searchString }),
+ undefined,
+ "Search string not present in input history: " + searchString
+ );
+ }
+
+ await PlacesUtils.history.clear();
+ await PlacesTestUtils.clearInputHistory();
+ addToInputHistorySpy.resetHistory();
+ }
+});
+
+// Input history use count should not be bumped when an origin autofill result
+// is triggered and picked.
+add_task(async function notBumped_origin() {
+ // Add enough visits to trigger origin autofill.
+ let url = "http://example.com/test";
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits(url);
+ }
+
+ await triggerAutofillAndPickResult("exam", "example.com/");
+
+ let calls = addToInputHistorySpy.getCalls();
+ Assert.equal(calls.length, 0, "UrlbarUtils.addToInputHistory() not called");
+
+ Assert.strictEqual(
+ await getUseCount({ url }),
+ undefined,
+ "URL not present in input history: " + url
+ );
+
+ await PlacesUtils.history.clear();
+});
+
+// Input history use count should not be bumped when a URL autofill result is
+// triggered and picked.
+add_task(async function notBumped_url() {
+ let url = "http://example.com/test";
+ await PlacesTestUtils.addVisits(url);
+
+ await triggerAutofillAndPickResult("example.com/t", "example.com/test");
+
+ let calls = addToInputHistorySpy.getCalls();
+ Assert.equal(calls.length, 0, "UrlbarUtils.addToInputHistory() not called");
+
+ Assert.strictEqual(
+ await getUseCount({ url }),
+ undefined,
+ "URL not present in input history: " + url
+ );
+
+ await PlacesUtils.history.clear();
+});
+
+/**
+ * Performs a search and picks the first result.
+ *
+ * @param {string} searchString
+ * The search string. Assumed to trigger an autofill result.
+ * @param {string} autofilledValue
+ * The input's expected value after autofill occurs.
+ */
+async function triggerAutofillAndPickResult(searchString, autofilledValue) {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill, "Result is autofill");
+ Assert.equal(gURLBar.value, autofilledValue, "gURLBar.value");
+ Assert.equal(gURLBar.selectionStart, searchString.length, "selectionStart");
+ Assert.equal(gURLBar.selectionEnd, autofilledValue.length, "selectionEnd");
+
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ });
+}
+
+/**
+ * Gets the use count of an input history record.
+ *
+ * @param {object} options
+ * Options object.
+ * @param {string} [options.url]
+ * The URL of the `moz_places` row corresponding to the record.
+ * @param {string} [options.input]
+ * The `input` value in the record.
+ * @returns {number}
+ * The use count. If no record exists with the URL and/or input, undefined is
+ * returned.
+ */
+async function getUseCount({ url = undefined, input = undefined }) {
+ return PlacesUtils.withConnectionWrapper("test::getUseCount", async db => {
+ let rows;
+ if (input && url) {
+ rows = await db.executeCached(
+ `SELECT i.use_count
+ FROM moz_inputhistory i
+ JOIN moz_places h ON h.id = i.place_id
+ WHERE h.url = :url AND i.input = :input`,
+ { url, input }
+ );
+ } else if (url) {
+ rows = await db.executeCached(
+ `SELECT i.use_count
+ FROM moz_inputhistory i
+ JOIN moz_places h ON h.id = i.place_id
+ WHERE h.url = :url`,
+ { url }
+ );
+ } else if (input) {
+ rows = await db.executeCached(
+ `SELECT use_count
+ FROM moz_inputhistory i
+ WHERE input = :input`,
+ { input }
+ );
+ }
+ return rows[0]?.getResultByIndex(0);
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_inputHistory_emptystring.js b/browser/components/urlbar/tests/browser/browser_inputHistory_emptystring.js
new file mode 100644
index 0000000000..28c967a851
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_inputHistory_emptystring.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests input history in cases where the search string is empty.
+ * In the future we may want to not account for these, but for now they are
+ * stored with an empty input field.
+ */
+
+"use strict";
+
+async function checkInputHistory(len = 0) {
+ await PlacesUtils.withConnectionWrapper(
+ "test::checkInputHistory",
+ async db => {
+ let rows = await db.executeCached(`SELECT input FROM moz_inputhistory`);
+ Assert.equal(rows.length, len, "There should only be 1 entry");
+ if (len) {
+ Assert.equal(rows[0].getResultByIndex(0), "", "Input should be empty");
+ }
+ }
+ );
+}
+
+const TEST_URL = "http://example.com/";
+
+async function do_test(openFn, pickMethod) {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "about:blank",
+ },
+ async function (browser) {
+ await PlacesTestUtils.clearInputHistory();
+ await openFn();
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ let promise = BrowserTestUtils.waitForDocLoadAndStopIt(TEST_URL, browser);
+ if (pickMethod == "keyboard") {
+ info(`Test pressing Enter`);
+ EventUtils.sendKey("down");
+ EventUtils.sendKey("return");
+ } else {
+ info("Test with click");
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ EventUtils.synthesizeMouseAtCenter(result.element.row, {});
+ }
+ await promise;
+ await checkInputHistory(1);
+ }
+ );
+}
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits(TEST_URL);
+ }
+
+ await updateTopSites(sites => sites && sites[0] && sites[0].url == TEST_URL);
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+add_task(async function test_history_no_search_terms() {
+ for (let pickMethod of ["keyboard", "mouse"]) {
+ // If a testFn returns false, it will be skipped.
+ for (let openFn of [
+ () => {
+ info("Test opening panel with down key");
+ gURLBar.focus();
+ EventUtils.sendKey("down");
+ },
+ async () => {
+ info("Test opening panel on focus");
+ gURLBar.blur();
+ EventUtils.synthesizeMouseAtCenter(gURLBar.textbox, {});
+ },
+ async () => {
+ info("Test opening panel on focus on a page");
+ let selectedBrowser = gBrowser.selectedBrowser;
+ // A page other than TEST_URL must be loaded, or the first Top Site
+ // result will be a switch-to-tab result and page won't be reloaded when
+ // the result is selected.
+ BrowserTestUtils.loadURIString(selectedBrowser, "http://example.org/");
+ await BrowserTestUtils.browserLoaded(selectedBrowser);
+ gURLBar.blur();
+ EventUtils.synthesizeMouseAtCenter(gURLBar.textbox, {});
+ },
+ ]) {
+ await do_test(openFn, pickMethod);
+ }
+ }
+});
diff --git a/browser/components/urlbar/tests/browser/browser_keepStateAcrossTabSwitches.js b/browser/components/urlbar/tests/browser/browser_keepStateAcrossTabSwitches.js
new file mode 100644
index 0000000000..03ba6a6473
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_keepStateAcrossTabSwitches.js
@@ -0,0 +1,224 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Verify user typed text remains in the URL bar when tab switching, even when
+ * loads fail.
+ */
+add_task(async function validURL() {
+ let input = "i-definitely-dont-exist.example.com";
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://example.com/"
+ );
+ let browser = tab.linkedBrowser;
+ // Note: Waiting on content document not being hidden because new tab pages can be preloaded,
+ // in which case no load events fire.
+ await SpecialPowers.spawn(browser, [], async () => {
+ await ContentTaskUtils.waitForCondition(() => {
+ return content.document && !content.document.hidden;
+ });
+ });
+ let errorPageLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ gURLBar.value = input;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+ await errorPageLoaded;
+ is(gURLBar.value, input, "Text is still in URL bar");
+ await BrowserTestUtils.switchTab(gBrowser, tab.previousElementSibling);
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+ is(gURLBar.value, input, "Text is still in URL bar after tab switch");
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Invalid URIs fail differently (that is, immediately, in the loadURI call)
+ * if keyword searches are turned off. Test that this works, too.
+ */
+add_task(async function invalidURL() {
+ let input = "To be or not to be-that is the question";
+ await SpecialPowers.pushPrefEnv({ set: [["keyword.enabled", false]] });
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank",
+ false
+ );
+ let browser = tab.linkedBrowser;
+ // Note: Waiting on content document not being hidden because new tab pages can be preloaded,
+ // in which case no load events fire.
+ await SpecialPowers.spawn(browser, [], async () => {
+ await ContentTaskUtils.waitForCondition(() => {
+ return content.document && !content.document.hidden;
+ });
+ });
+ let errorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser);
+ gURLBar.value = input;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+ await errorPageLoaded;
+ is(gURLBar.value, input, "Text is still in URL bar");
+ is(tab.linkedBrowser.userTypedValue, input, "Text still stored on browser");
+ await BrowserTestUtils.switchTab(gBrowser, tab.previousElementSibling);
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+ is(gURLBar.value, input, "Text is still in URL bar after tab switch");
+ is(tab.linkedBrowser.userTypedValue, input, "Text still stored on browser");
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Test the urlbar status of text selection and focusing by tab switching.
+ */
+add_task(async function selectAndFocus() {
+ // Create a tab with normal web page.
+ const webpageTabURL = "https://example.com";
+ const webpageTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: webpageTabURL,
+ });
+
+ // Create a tab with userTypedValue.
+ const userTypedTabText = "test";
+ const userTypedTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ });
+ await UrlbarTestUtils.inputIntoURLBar(window, userTypedTabText);
+
+ // Create an empty tab.
+ const emptyTab = await BrowserTestUtils.openNewForegroundTab({ gBrowser });
+
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ BrowserTestUtils.removeTab(webpageTab);
+ BrowserTestUtils.removeTab(userTypedTab);
+ BrowserTestUtils.removeTab(emptyTab);
+ });
+
+ await doSelectAndFocusTest({
+ targetTab: webpageTab,
+ targetSelectionStart: 0,
+ targetSelectionEnd: 0,
+ anotherTab: userTypedTab,
+ });
+ await doSelectAndFocusTest({
+ targetTab: webpageTab,
+ targetSelectionStart: 2,
+ targetSelectionEnd: 5,
+ anotherTab: userTypedTab,
+ });
+ await doSelectAndFocusTest({
+ targetTab: webpageTab,
+ targetSelectionStart: webpageTabURL.length,
+ targetSelectionEnd: webpageTabURL.length,
+ anotherTab: userTypedTab,
+ });
+ await doSelectAndFocusTest({
+ targetTab: webpageTab,
+ targetSelectionStart: 0,
+ targetSelectionEnd: 0,
+ anotherTab: emptyTab,
+ });
+ await doSelectAndFocusTest({
+ targetTab: userTypedTab,
+ targetSelectionStart: 0,
+ targetSelectionEnd: 0,
+ anotherTab: webpageTab,
+ });
+ await doSelectAndFocusTest({
+ targetTab: userTypedTab,
+ targetSelectionStart: 0,
+ targetSelectionEnd: 0,
+ anotherTab: emptyTab,
+ });
+ await doSelectAndFocusTest({
+ targetTab: userTypedTab,
+ targetSelectionStart: 1,
+ targetSelectionEnd: 2,
+ anotherTab: emptyTab,
+ });
+ await doSelectAndFocusTest({
+ targetTab: userTypedTab,
+ targetSelectionStart: userTypedTabText.length,
+ targetSelectionEnd: userTypedTabText.length,
+ anotherTab: emptyTab,
+ });
+ await doSelectAndFocusTest({
+ targetTab: emptyTab,
+ targetSelectionStart: 0,
+ targetSelectionEnd: 0,
+ anotherTab: webpageTab,
+ });
+ await doSelectAndFocusTest({
+ targetTab: emptyTab,
+ targetSelectionStart: 0,
+ targetSelectionEnd: 0,
+ anotherTab: userTypedTab,
+ });
+});
+
+async function doSelectAndFocusTest({
+ targetTab,
+ targetSelectionStart,
+ targetSelectionEnd,
+ anotherTab,
+}) {
+ const testCases = [
+ {
+ targetFocus: false,
+ anotherFocus: false,
+ },
+ {
+ targetFocus: true,
+ anotherFocus: false,
+ },
+ {
+ targetFocus: true,
+ anotherFocus: true,
+ },
+ ];
+
+ for (const { targetFocus, anotherFocus } of testCases) {
+ // Setup the target tab.
+ await switchTab(targetTab);
+ setURLBarFocus(targetFocus);
+ gURLBar.inputField.setSelectionRange(
+ targetSelectionStart,
+ targetSelectionEnd
+ );
+ const targetValue = gURLBar.value;
+
+ // Switch to another tab.
+ await switchTab(anotherTab);
+ setURLBarFocus(anotherFocus);
+
+ // Switch back to the target tab.
+ await switchTab(targetTab);
+
+ // Check whether the value, selection and focusing status are reverted.
+ Assert.equal(gURLBar.value, targetValue);
+ Assert.equal(gURLBar.focused, targetFocus);
+ if (gURLBar.focused) {
+ Assert.equal(gURLBar.selectionStart, targetSelectionStart);
+ Assert.equal(gURLBar.selectionEnd, targetSelectionEnd);
+ } else {
+ Assert.equal(gURLBar.selectionStart, gURLBar.value.length);
+ Assert.equal(gURLBar.selectionEnd, gURLBar.value.length);
+ }
+ }
+}
+
+function setURLBarFocus(focus) {
+ if (focus) {
+ gURLBar.focus();
+ } else {
+ gURLBar.blur();
+ }
+}
+
+async function switchTab(tab) {
+ if (gBrowser.selectedTab !== tab) {
+ EventUtils.synthesizeMouseAtCenter(tab, {});
+ await BrowserTestUtils.waitForCondition(() => gBrowser.selectedTab === tab);
+ }
+}
diff --git a/browser/components/urlbar/tests/browser/browser_keyword.js b/browser/components/urlbar/tests/browser/browser_keyword.js
new file mode 100644
index 0000000000..618bcad3c7
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_keyword.js
@@ -0,0 +1,238 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This tests that keywords are displayed and handled correctly.
+ */
+
+async function promise_first_result(inputText) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: inputText,
+ });
+
+ return UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+}
+
+function assertURL(result, expectedUrl, keyword, input, postData) {
+ Assert.equal(result.url, expectedUrl, "Should have the correct URL");
+ if (postData) {
+ Assert.equal(
+ NetUtil.readInputStreamToString(
+ result.postData,
+ result.postData.available()
+ ),
+ postData,
+ "Should have the correct postData"
+ );
+ }
+}
+
+const TEST_URL = `${TEST_BASE_URL}print_postdata.sjs`;
+
+add_setup(async function () {
+ await PlacesUtils.keywords.insert({
+ keyword: "get",
+ url: TEST_URL + "?q=%s",
+ });
+ await PlacesUtils.keywords.insert({
+ keyword: "post",
+ url: TEST_URL,
+ postData: "q=%s",
+ });
+ await PlacesUtils.keywords.insert({
+ keyword: "question?",
+ url: TEST_URL + "?q2=%s",
+ });
+ await PlacesUtils.keywords.insert({
+ keyword: "?question",
+ url: TEST_URL + "?q3=%s",
+ });
+ // Avoid fetching search suggestions.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", false]],
+ });
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ setAsDefault: true,
+ });
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.keywords.remove("get");
+ await PlacesUtils.keywords.remove("post");
+ await PlacesUtils.keywords.remove("question?");
+ await PlacesUtils.keywords.remove("?question");
+ while (gBrowser.tabs.length > 1) {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+ });
+});
+
+add_task(async function test_display_keyword_without_query() {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ // Test a keyword that also has blank spaces to ensure they are ignored as well.
+ let result = await promise_first_result("get ");
+
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.KEYWORD,
+ "Should have a keyword result"
+ );
+ Assert.equal(
+ result.displayed.title,
+ "https://example.com/browser/browser/components/urlbar/tests/browser/print_postdata.sjs?q=",
+ "Node should contain the url of the bookmark"
+ );
+ let [action] = await document.l10n.formatValues([
+ { id: "urlbar-result-action-visit" },
+ ]);
+ Assert.equal(result.displayed.action, action, "Should have visit indicated");
+});
+
+add_task(async function test_keyword_using_get() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+
+ let result = await promise_first_result("get something");
+
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.KEYWORD,
+ "Should have a keyword result"
+ );
+ Assert.equal(
+ result.displayed.title,
+ "example.com: something",
+ "Node should contain the name of the bookmark and query"
+ );
+ Assert.ok(!result.displayed.action, "Should have an empty action");
+
+ assertURL(result, TEST_URL + "?q=something", "get", "get something");
+
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+
+ // Click on the result
+ info("Normal click on result");
+ let tabPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeMouseAtCenter(element, {});
+ await tabPromise;
+ Assert.equal(
+ tab.linkedBrowser.currentURI.spec,
+ TEST_URL + "?q=something",
+ "Tab should have loaded from clicking on result"
+ );
+
+ // Middle-click on the result
+ info("Middle-click on result");
+ result = await promise_first_result("get somethingmore");
+
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.KEYWORD,
+ "Should have a keyword result"
+ );
+
+ assertURL(result, TEST_URL + "?q=somethingmore", "get", "get somethingmore");
+
+ tabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+ EventUtils.synthesizeMouseAtCenter(element, { button: 1 });
+ let tabOpenEvent = await tabPromise;
+ let newTab = tabOpenEvent.target;
+ await BrowserTestUtils.browserLoaded(newTab.linkedBrowser);
+ Assert.equal(
+ newTab.linkedBrowser.currentURI.spec,
+ TEST_URL + "?q=somethingmore",
+ "Tab should have loaded from middle-clicking on result"
+ );
+});
+
+add_task(async function test_keyword_using_post() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+
+ let result = await promise_first_result("post something");
+
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.KEYWORD,
+ "Should have a keyword result"
+ );
+ Assert.equal(
+ result.displayed.title,
+ "example.com: something",
+ "Node should contain the name of the bookmark and query"
+ );
+ Assert.ok(!result.displayed.action, "Should have an empty action");
+
+ assertURL(result, TEST_URL, "post", "post something", "q=something");
+
+ // Click on the result
+ info("Normal click on result");
+ let tabPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+ EventUtils.synthesizeMouseAtCenter(element, {});
+ info("waiting for tab");
+ await tabPromise;
+ Assert.equal(
+ tab.linkedBrowser.currentURI.spec,
+ TEST_URL,
+ "Tab should have loaded from clicking on result"
+ );
+
+ let postData = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ return content.document.body.textContent;
+ }
+ );
+ Assert.equal(postData, "q=something", "post data was submitted correctly");
+});
+
+add_task(async function test_keyword_with_question_mark() {
+ // TODO Bug 1517140: keywords containing restriction chars should not be
+ // allowed, or properly supported.
+ let result = await promise_first_result("question?");
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Result should be a search"
+ );
+ Assert.equal(result.searchParams.query, "question?", "Check search query");
+
+ result = await promise_first_result("question? something");
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.KEYWORD,
+ "Result should be a keyword"
+ );
+ Assert.equal(result.keyword, "question?", "Check search query");
+
+ result = await promise_first_result("?question");
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Result should be a search"
+ );
+ Assert.equal(result.searchParams.query, "question", "Check search query");
+
+ result = await promise_first_result("?question something");
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Result should be a search"
+ );
+ Assert.equal(
+ result.searchParams.query,
+ "question something",
+ "Check search query"
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_keywordBookmarklets.js b/browser/components/urlbar/tests/browser/browser_keywordBookmarklets.js
new file mode 100644
index 0000000000..c10fcdd9c3
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_keywordBookmarklets.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ let bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "bookmarklet",
+ url: "javascript:'%sx'%20",
+ });
+ await PlacesUtils.keywords.insert({ keyword: "bm", url: bm.url });
+ registerCleanupFunction(async function () {
+ await PlacesUtils.bookmarks.remove(bm);
+ });
+
+ let testFns = [
+ function () {
+ info("Type keyword and immediately press enter");
+ gURLBar.value = "bm";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_Enter");
+ return "x";
+ },
+ function () {
+ info("Type keyword with searchstring and immediately press enter");
+ gURLBar.value = "bm a";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_Enter");
+ return "ax";
+ },
+ async function () {
+ info("Search keyword, then press enter");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "bm",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(result.title, "javascript:'x' ", "Check title");
+ EventUtils.synthesizeKey("KEY_Enter");
+ return "x";
+ },
+ async function () {
+ info("Search keyword with searchstring, then press enter");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "bm a",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(result.title, "javascript:'ax' ", "Check title");
+ EventUtils.synthesizeKey("KEY_Enter");
+ return "ax";
+ },
+ async function () {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "bm",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(result.title, "javascript:'x' ", "Check title");
+ let element = UrlbarTestUtils.getSelectedRow(window);
+ EventUtils.synthesizeMouseAtCenter(element, {});
+ return "x";
+ },
+ async function () {
+ info("Search keyword with searchstring, then click");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "bm a",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(result.title, "javascript:'ax' ", "Check title");
+ let element = UrlbarTestUtils.getSelectedRow(window);
+ EventUtils.synthesizeMouseAtCenter(element, {});
+ return "ax";
+ },
+ ];
+ for (let testFn of testFns) {
+ await do_test(testFn);
+ }
+});
+
+async function do_test(loadFn) {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ },
+ async browser => {
+ let originalPrincipal = gBrowser.contentPrincipal;
+ let originalPrincipalURI = await getPrincipalURI(browser);
+
+ let promise = BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+ const expectedTextContent = await loadFn();
+ info("Awaiting pageshow event");
+ await promise;
+ // URI should not change when we run a javascript: URL.
+ Assert.equal(gBrowser.currentURI.spec, "about:blank");
+ const textContent = await ContentTask.spawn(browser, [], function () {
+ return content.document.documentElement.textContent;
+ });
+ Assert.equal(textContent, expectedTextContent);
+
+ let newPrincipalURI = await getPrincipalURI(browser);
+ Assert.equal(
+ newPrincipalURI,
+ originalPrincipalURI,
+ "content has the same principal"
+ );
+
+ // In e10s, null principals don't round-trip so the same null principal sent
+ // from the child will be a new null principal. Verify that this is the
+ // case.
+ if (browser.isRemoteBrowser) {
+ Assert.ok(
+ originalPrincipal.isNullPrincipal &&
+ gBrowser.contentPrincipal.isNullPrincipal,
+ "both principals should be null principals in the parent"
+ );
+ } else {
+ Assert.ok(
+ gBrowser.contentPrincipal.equals(originalPrincipal),
+ "javascript bookmarklet should inherit principal"
+ );
+ }
+ }
+ );
+}
+
+function getPrincipalURI(browser) {
+ return SpecialPowers.spawn(browser, [], function () {
+ return content.document.nodePrincipal.spec;
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_keywordSearch.js b/browser/components/urlbar/tests/browser/browser_keywordSearch.js
new file mode 100644
index 0000000000..b8402a4e90
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_keywordSearch.js
@@ -0,0 +1,57 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var gTests = [
+ {
+ name: "normal search (search service)",
+ text: "test search",
+ expectText: "test+search",
+ },
+ {
+ name: "?-prefixed search (search service)",
+ text: "? foo ",
+ expectText: "foo",
+ },
+];
+
+add_setup(async function () {
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ setAsDefault: true,
+ });
+});
+
+add_task(async function () {
+ // Test both directly setting a value and pressing enter, or setting the
+ // value through input events, like the user would do.
+ const setValueFns = [
+ value => {
+ gURLBar.value = value;
+ },
+ value => {
+ return UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ });
+ },
+ ];
+
+ for (let test of gTests) {
+ info("Testing: " + test.name);
+ await BrowserTestUtils.withNewTab({ gBrowser }, async browser => {
+ for (let setValueFn of setValueFns) {
+ gURLBar.select();
+ await setValueFn(test.text);
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ let expectedUrl = "http://mochi.test:8888/?terms=" + test.expectText;
+ info("Waiting for load: " + expectedUrl);
+ await BrowserTestUtils.browserLoaded(browser, false, expectedUrl);
+ // At least one test.
+ Assert.equal(browser.currentURI.spec, expectedUrl);
+ }
+ });
+ }
+});
diff --git a/browser/components/urlbar/tests/browser/browser_keywordSearch_postData.js b/browser/components/urlbar/tests/browser/browser_keywordSearch_postData.js
new file mode 100644
index 0000000000..d2b3aa253a
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_keywordSearch_postData.js
@@ -0,0 +1,74 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var gTests = [
+ {
+ name: "single word search (search service)",
+ text: "pizza",
+ expectText: "pizza",
+ },
+ {
+ name: "multi word search (search service)",
+ text: "test search",
+ expectText: "test+search",
+ },
+ {
+ name: "?-prefixed search (search service)",
+ text: "? foo ",
+ expectText: "foo",
+ },
+];
+
+add_setup(async function () {
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "POSTSearchEngine.xml",
+ setAsDefault: true,
+ });
+});
+
+add_task(async function () {
+ // Test both directly setting a value and pressing enter, or setting the
+ // value through input events, like the user would do.
+ const setValueFns = [
+ value => {
+ gURLBar.value = value;
+ },
+ value => {
+ return UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ });
+ },
+ ];
+
+ for (let test of gTests) {
+ info("Testing: " + test.name);
+ await BrowserTestUtils.withNewTab({ gBrowser }, async browser => {
+ for (let setValueFn of setValueFns) {
+ gURLBar.select();
+ await setValueFn(test.text);
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ await BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ "http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/print_postdata.sjs"
+ );
+
+ let textContent = await SpecialPowers.spawn(browser, [], async () => {
+ return content.document.body.textContent;
+ });
+
+ Assert.ok(textContent, "search page loaded");
+ let needle = "searchterms=" + test.expectText;
+ Assert.equal(
+ textContent,
+ needle,
+ "The query POST data should be returned in the response"
+ );
+ }
+ });
+ }
+});
diff --git a/browser/components/urlbar/tests/browser/browser_keyword_override.js b/browser/components/urlbar/tests/browser/browser_keyword_override.js
new file mode 100644
index 0000000000..b358f3a4ac
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_keyword_override.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This tests that the display of keyword results are not changed when the user
+ * presses the override button.
+ */
+
+add_task(async function () {
+ let bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/?q=%s",
+ title: "test",
+ });
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example.com/?q=%s",
+ });
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.bookmarks.remove(bm);
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "keyword search",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+
+ info("Before override");
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.KEYWORD,
+ "Should have a keyword result"
+ );
+ Assert.equal(
+ result.displayed.title,
+ "example.com: search",
+ "Node should contain the name of the bookmark and query"
+ );
+ Assert.ok(!result.displayed.action, "Should have an empty action");
+
+ info("During override");
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keydown" });
+
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.KEYWORD,
+ "Should have a keyword result"
+ );
+ Assert.equal(
+ result.displayed.title,
+ "example.com: search",
+ "Node should contain the name of the bookmark and query"
+ );
+ Assert.ok(!result.displayed.action, "Should have an empty action");
+
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keyup" });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_keyword_select_and_type.js b/browser/components/urlbar/tests/browser/browser_keyword_select_and_type.js
new file mode 100644
index 0000000000..a3222c293f
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_keyword_select_and_type.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This tests that changing away from a keyword result and back again, still
+ * operates correctly.
+ */
+
+add_task(async function () {
+ let bookmarks = [];
+ bookmarks.push(
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/?q=%s",
+ title: "test",
+ })
+ );
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example.com/?q=%s",
+ });
+
+ // This item is only needed so we can select the keyword item, select something
+ // else, then select the keyword item again.
+ bookmarks.push(
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/keyword",
+ title: "keyword abc",
+ })
+ );
+
+ registerCleanupFunction(async function () {
+ for (let bm of bookmarks) {
+ await PlacesUtils.bookmarks.remove(bm);
+ }
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "keyword a",
+ });
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+
+ // First item should already be selected
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "Should have the first item selected"
+ );
+
+ // Select next one (important!)
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 1,
+ "Should have the second item selected"
+ );
+
+ // Re-select keyword item
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "Should have the first item selected"
+ );
+
+ EventUtils.sendString("b");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ Assert.equal(
+ gURLBar.value,
+ "keyword ab",
+ "urlbar should have expected input"
+ );
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.KEYWORD,
+ "Should have a result of type keyword"
+ );
+ Assert.equal(
+ result.url,
+ "http://example.com/?q=ab",
+ "Should have the correct url"
+ );
+
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_loadRace.js b/browser/components/urlbar/tests/browser/browser_loadRace.js
new file mode 100644
index 0000000000..b257625f30
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_loadRace.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This test is for testing races of loading the Urlbar when loading shortcuts.
+// For example, ensuring that if a search query is entered, but something causes
+// a page load whilst we're getting the search url, then we don't handle the
+// original search query.
+
+add_setup(async function () {
+ sandbox = sinon.createSandbox();
+
+ registerCleanupFunction(async () => {
+ sandbox.restore();
+ });
+});
+
+async function checkShortcutLoading(modifierKeys) {
+ let deferred = PromiseUtils.defer();
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "about:robots",
+ });
+
+ // We stub getHeuristicResultFor to guarentee it doesn't resolve until after
+ // we've loaded a new page.
+ let original = UrlbarUtils.getHeuristicResultFor;
+ sandbox
+ .stub(UrlbarUtils, "getHeuristicResultFor")
+ .callsFake(async searchString => {
+ await deferred.promise;
+ return original.call(this, searchString);
+ });
+
+ // This load will be blocked until the deferred is resolved.
+ // Use a string that will be interepreted as a local URL to avoid hitting the
+ // network.
+ gURLBar.focus();
+ gURLBar.value = "example.com";
+ gURLBar.userTypedValue = true;
+ EventUtils.synthesizeKey("KEY_Enter", modifierKeys);
+
+ Assert.ok(
+ UrlbarUtils.getHeuristicResultFor.calledOnce,
+ "should have called getHeuristicResultFor"
+ );
+
+ // Now load a different page.
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, "about:license");
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ Assert.equal(gBrowser.visibleTabs.length, 2, "Should have 2 tabs");
+
+ // Now that the new page has loaded, unblock the previous urlbar load.
+ deferred.resolve();
+ if (modifierKeys) {
+ let openedTab = await new Promise(resolve => {
+ window.addEventListener(
+ "TabOpen",
+ event => {
+ resolve(event.target);
+ },
+ { once: true }
+ );
+ });
+ await BrowserTestUtils.browserLoaded(openedTab.linkedBrowser);
+ Assert.ok(
+ openedTab.linkedBrowser.currentURI.spec.includes("example.com"),
+ "Should have attempted to open the shortcut page"
+ );
+ BrowserTestUtils.removeTab(openedTab);
+ }
+
+ Assert.equal(
+ tab.linkedBrowser.currentURI.spec,
+ "about:license",
+ "Tab url should not have changed"
+ );
+ Assert.equal(gBrowser.visibleTabs.length, 2, "Should still have 2 tabs");
+
+ BrowserTestUtils.removeTab(tab);
+ sandbox.restore();
+}
+
+add_task(async function test_location_change_stops_load() {
+ await checkShortcutLoading();
+});
+
+add_task(async function test_opening_different_tab_with_location_change() {
+ await checkShortcutLoading({ altKey: true });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_locationBarCommand.js b/browser/components/urlbar/tests/browser/browser_locationBarCommand.js
new file mode 100644
index 0000000000..f0077c3334
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_locationBarCommand.js
@@ -0,0 +1,291 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test is designed to ensure that the correct command/operation happens
+ * when pressing enter with various key combinations in the urlbar.
+ */
+
+const TEST_VALUE = "example.com";
+const START_VALUE = "example.org";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.altClickSave", true],
+ ["browser.urlbar.autoFill", false],
+ ],
+ });
+});
+
+add_task(async function alt_left_click_test() {
+ info("Running test: Alt left click");
+
+ // Monkey patch saveURL() to avoid dealing with file save code paths.
+ let oldSaveURL = saveURL;
+ let saveURLPromise = new Promise(resolve => {
+ saveURL = () => {
+ // Restore old saveURL() value.
+ saveURL = oldSaveURL;
+ resolve();
+ };
+ });
+
+ await triggerCommand("click", { altKey: true });
+
+ await saveURLPromise;
+ ok(true, "SaveURL was called");
+ is(gURLBar.value, "", "Urlbar reverted to original value");
+});
+
+add_task(async function shift_left_click_test() {
+ info("Running test: Shift left click");
+
+ let destinationURL = "http://" + TEST_VALUE + "/";
+ let newWindowPromise = BrowserTestUtils.waitForNewWindow({
+ url: destinationURL,
+ });
+ await triggerCommand("click", { shiftKey: true });
+ let win = await newWindowPromise;
+
+ info("URL should be loaded in a new window");
+ is(gURLBar.value, "", "Urlbar reverted to original value");
+ await promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser);
+ is(
+ document.activeElement,
+ gBrowser.selectedBrowser,
+ "Content window should be focused"
+ );
+ is(win.gURLBar.value, TEST_VALUE, "New URL is loaded in new window");
+
+ // Cleanup.
+ let ourWindowRefocusedPromise = Promise.all([
+ BrowserTestUtils.waitForEvent(window, "activate"),
+ BrowserTestUtils.waitForEvent(window, "focus", true),
+ ]);
+ await BrowserTestUtils.closeWindow(win);
+ await ourWindowRefocusedPromise;
+});
+
+add_task(async function right_click_test() {
+ info("Running test: Right click on go button");
+
+ // Add a new tab.
+ await promiseOpenNewTab();
+
+ await triggerCommand("click", { button: 2 });
+
+ // Right click should do nothing (context menu will be shown).
+ is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered");
+
+ // Cleanup.
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function shift_accel_left_click_test() {
+ info("Running test: Shift+Ctrl/Cmd left click on go button");
+
+ // Add a new tab.
+ let tab = await promiseOpenNewTab();
+
+ let loadStartedPromise = promiseLoadStarted();
+ await triggerCommand("click", { accelKey: true, shiftKey: true });
+ await loadStartedPromise;
+
+ // Check the load occurred in a new background tab.
+ info("URL should be loaded in a new background tab");
+ is(gURLBar.value, "", "Urlbar reverted to original value");
+ ok(!gURLBar.focused, "Urlbar is no longer focused after urlbar command");
+ is(gBrowser.selectedTab, tab, "Focus did not change to the new tab");
+
+ // Select the new background tab
+ gBrowser.selectedTab = gBrowser.selectedTab.nextElementSibling;
+ is(gURLBar.value, TEST_VALUE, "New URL is loaded in new tab");
+
+ // Cleanup.
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function load_in_current_tab_test() {
+ let tests = [
+ {
+ desc: "Simple return keypress",
+ type: "keypress",
+ },
+ {
+ desc: "Left click on go button",
+ type: "click",
+ },
+ {
+ desc: "Ctrl/Cmd+Return keypress",
+ type: "keypress",
+ details: { accelKey: true },
+ },
+ {
+ desc: "Alt+Return keypress in a blank tab",
+ type: "keypress",
+ details: { altKey: true },
+ },
+ {
+ desc: "AltGr+Return keypress in a blank tab",
+ type: "keypress",
+ details: { altGraphKey: true },
+ },
+ ];
+
+ for (let { desc, type, details } of tests) {
+ info(`Running test: ${desc}`);
+
+ // Add a new tab.
+ let tab = await promiseOpenNewTab();
+
+ // Trigger a load and check it occurs in the current tab.
+ let loadStartedPromise = promiseLoadStarted();
+ await triggerCommand(type, details);
+ await loadStartedPromise;
+
+ info("URL should be loaded in the current tab");
+ is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered");
+ await promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser);
+ is(
+ document.activeElement,
+ gBrowser.selectedBrowser,
+ "Content window should be focused"
+ );
+ is(gBrowser.selectedTab, tab, "New URL was loaded in the current tab");
+
+ // Cleanup.
+ gBrowser.removeCurrentTab();
+ }
+});
+
+add_task(async function load_in_new_tab_test() {
+ let tests = [
+ {
+ desc: "Ctrl/Cmd left click on go button",
+ type: "click",
+ details: { accelKey: true },
+ url: "about:blank",
+ },
+ {
+ desc: "Alt+Return keypress in a dirty tab",
+ type: "keypress",
+ details: { altKey: true },
+ url: START_VALUE,
+ },
+ {
+ desc: "AltGr+Return keypress in a dirty tab",
+ type: "keypress",
+ details: { altGraphKey: true },
+ url: START_VALUE,
+ },
+ ];
+
+ for (let { desc, type, details, url } of tests) {
+ info(`Running test: ${desc}`);
+
+ // Add a new tab.
+ let tab = await promiseOpenNewTab(url);
+
+ // Trigger a load and check it occurs in a new tab.
+ let tabSwitchedPromise = promiseNewTabSwitched();
+ await triggerCommand(type, details);
+ await tabSwitchedPromise;
+
+ // Check the load occurred in a new tab.
+ info("URL should be loaded in a new focused tab");
+ is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered");
+ await promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser);
+ is(
+ document.activeElement,
+ gBrowser.selectedBrowser,
+ "Content window should be focused"
+ );
+ isnot(gBrowser.selectedTab, tab, "New URL was loaded in a new tab");
+
+ // Cleanup.
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+ }
+});
+
+async function triggerCommand(type, details = {}) {
+ gURLBar.focus();
+ gURLBar.value = "";
+ EventUtils.sendString(TEST_VALUE);
+
+ Assert.equal(
+ await UrlbarTestUtils.promiseUserContextId(window),
+ gBrowser.selectedTab.getAttribute("usercontextid"),
+ "userContextId must be the same as the originating tab"
+ );
+
+ if (type == "click") {
+ ok(
+ gURLBar.hasAttribute("usertyping"),
+ "usertyping attribute must be set for the go button to be visible"
+ );
+ EventUtils.synthesizeMouseAtCenter(gURLBar.goButton, details);
+ } else if (type == "keypress") {
+ EventUtils.synthesizeKey("KEY_Enter", details);
+ } else {
+ throw new Error("Unsupported event type");
+ }
+}
+
+function promiseLoadStarted() {
+ return new Promise(resolve => {
+ gBrowser.addTabsProgressListener({
+ onStateChange(browser, webProgress, req, flags, status) {
+ if (flags & Ci.nsIWebProgressListener.STATE_START) {
+ gBrowser.removeTabsProgressListener(this);
+ resolve();
+ }
+ },
+ });
+ });
+}
+
+let gUserContextIdSerial = 1;
+async function promiseOpenNewTab(url = "about:blank") {
+ let tab = BrowserTestUtils.addTab(gBrowser, url, {
+ userContextId: gUserContextIdSerial++,
+ });
+ let tabSwitchPromise = promiseNewTabSwitched(tab);
+ gBrowser.selectedTab = tab;
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ await tabSwitchPromise;
+ return tab;
+}
+
+function promiseNewTabSwitched() {
+ return new Promise(resolve => {
+ gBrowser.addEventListener(
+ "TabSwitchDone",
+ function () {
+ executeSoon(resolve);
+ },
+ { once: true }
+ );
+ });
+}
+
+function promiseCheckChildNoFocusedElement(browser) {
+ if (!gMultiProcessBrowser) {
+ Assert.equal(
+ Services.focus.focusedElement,
+ null,
+ "There should be no focused element"
+ );
+ return null;
+ }
+
+ return ContentTask.spawn(browser, null, async function () {
+ Assert.equal(
+ Services.focus.focusedElement,
+ null,
+ "There should be no focused element"
+ );
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_locationBarExternalLoad.js b/browser/components/urlbar/tests/browser/browser_locationBarExternalLoad.js
new file mode 100644
index 0000000000..5a44db54ce
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_locationBarExternalLoad.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.autoFill", false]],
+ });
+ const url = "data:text/html,<body>hi";
+ await testURL(url, urlEnter);
+ await testURL(url, urlClick);
+});
+
+function urlEnter(url) {
+ gURLBar.value = url;
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_Enter");
+}
+
+function urlClick(url) {
+ gURLBar.focus();
+ gURLBar.value = "";
+ EventUtils.sendString(url);
+ EventUtils.synthesizeMouseAtCenter(gURLBar.goButton, {});
+}
+
+function promiseNewTabSwitched() {
+ return new Promise(resolve => {
+ gBrowser.addEventListener(
+ "TabSwitchDone",
+ function () {
+ executeSoon(resolve);
+ },
+ { once: true }
+ );
+ });
+}
+
+function promiseLoaded(browser) {
+ return SpecialPowers.spawn(browser, [undefined], async () => {
+ if (!["interactive", "complete"].includes(content.document.readyState)) {
+ await new Promise(resolve =>
+ docShell.chromeEventHandler.addEventListener(
+ "DOMContentLoaded",
+ resolve,
+ {
+ once: true,
+ capture: true,
+ }
+ )
+ );
+ }
+ });
+}
+
+async function testURL(url, loadFunc, endFunc) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let browser = tab.linkedBrowser;
+
+ let pagePrincipal = gBrowser.contentPrincipal;
+ // We need to ensure that we set the pageshow event listener before running
+ // loadFunc, otherwise there's a chance that the content process will finish
+ // loading the page and fire pageshow before the event listener gets set.
+ let pageShowPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow"
+ );
+ loadFunc(url);
+ await pageShowPromise;
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ isRemote: gMultiProcessBrowser }],
+ async function (arg) {
+ Assert.equal(
+ Services.focus.focusedElement,
+ null,
+ "focusedElement not null"
+ );
+ }
+ );
+
+ is(document.activeElement, browser, "content window should be focused");
+
+ ok(
+ !gBrowser.contentPrincipal.equals(pagePrincipal),
+ "load of " +
+ url +
+ " by " +
+ loadFunc.name +
+ " should produce a page with a different principal"
+ );
+
+ await BrowserTestUtils.removeTab(tab);
+}
diff --git a/browser/components/urlbar/tests/browser/browser_locationchange_urlbar_edit_dos.js b/browser/components/urlbar/tests/browser/browser_locationchange_urlbar_edit_dos.js
new file mode 100644
index 0000000000..b50446a4c9
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_locationchange_urlbar_edit_dos.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URL = `${TEST_BASE_URL}file_urlbar_edit_dos.html`;
+
+async function checkURLBarValueStays(browser) {
+ gURLBar.select();
+ EventUtils.sendString("a");
+ is(gURLBar.value, "a", "URL bar value should match after sending a key");
+ await new Promise(resolve => {
+ let listener = {
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ ok(
+ aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT,
+ "Should only get a same document location change"
+ );
+ gBrowser.selectedBrowser.removeProgressListener(filter);
+ filter = null;
+ // Wait an extra tick before resolving. We want to make sure that other
+ // web progress listeners queued after this one are called before we
+ // continue the test, in case the remainder of the test depends on those
+ // listeners. That should happen anyway since promises are resolved on
+ // the next tick, but do this to be a little safer. In particular we
+ // want to avoid having the test pass when it should fail.
+ executeSoon(resolve);
+ },
+ };
+ let filter = Cc[
+ "@mozilla.org/appshell/component/browser-status-filter;1"
+ ].createInstance(Ci.nsIWebProgress);
+ filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
+ gBrowser.selectedBrowser.addProgressListener(filter);
+ });
+ is(
+ gURLBar.value,
+ "a",
+ "URL bar should not have been changed by location changes."
+ );
+}
+
+add_task(async function () {
+ // Disable autofill so that when checkURLBarValueStays types "a", it's not
+ // autofilled to addons.mozilla.org (or anything else).
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.autoFill", false]],
+ });
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_URL,
+ },
+ async function (browser) {
+ let promise1 = checkURLBarValueStays(browser);
+ SpecialPowers.spawn(browser, [""], function () {
+ content.wrappedJSObject.dos_hash();
+ });
+ await promise1;
+ let promise2 = checkURLBarValueStays(browser);
+ SpecialPowers.spawn(browser, [""], function () {
+ content.wrappedJSObject.dos_pushState();
+ });
+ await promise2;
+ }
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_middleClick.js b/browser/components/urlbar/tests/browser/browser_middleClick.js
new file mode 100644
index 0000000000..1a5088f827
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_middleClick.js
@@ -0,0 +1,255 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test for middle click behavior.
+ */
+
+add_task(async function test_setup() {
+ CustomizableUI.addWidgetToArea("home-button", "nav-bar");
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.tabs.searchclipboardfor.middleclick", false]],
+ });
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("middlemouse.paste");
+ Services.prefs.clearUserPref("middlemouse.openNewWindow");
+ Services.prefs.clearUserPref("browser.tabs.opentabfor.middleclick");
+ Services.prefs.clearUserPref("browser.startup.homepage");
+ Services.prefs.clearUserPref("browser.tabs.loadBookmarksInBackground");
+ SpecialPowers.clipboardCopyString("");
+
+ CustomizableUI.removeWidgetFromArea("home-button");
+ });
+});
+
+add_task(async function test_middleClickOnTab() {
+ await testMiddleClickOnTab(false);
+ await testMiddleClickOnTab(true);
+});
+
+add_task(async function test_middleClickToOpenNewTab() {
+ await testMiddleClickToOpenNewTab(false, "#tabs-newtab-button");
+ await testMiddleClickToOpenNewTab(true, "#tabs-newtab-button");
+ await testMiddleClickToOpenNewTab(false, "#TabsToolbar");
+ await testMiddleClickToOpenNewTab(true, "#TabsToolbar");
+});
+
+add_task(async function test_middleClickOnURLBar() {
+ await testMiddleClickOnURLBar(false);
+ await testMiddleClickOnURLBar(true);
+});
+
+add_task(async function test_middleClickOnHomeButton() {
+ const TEST_DATA = [
+ {
+ isMiddleMousePastePrefOn: false,
+ isLoadInBackground: false,
+ startPagePref: "about:home",
+ expectedURLBarFocus: true,
+ expectedURLBarValue: "",
+ },
+ {
+ isMiddleMousePastePrefOn: false,
+ isLoadInBackground: false,
+ startPagePref: "about:blank",
+ expectedURLBarFocus: true,
+ expectedURLBarValue: "",
+ },
+ {
+ isMiddleMousePastePrefOn: false,
+ isLoadInBackground: false,
+ startPagePref: "https://example.com",
+ expectedURLBarFocus: false,
+ expectedURLBarValue: "https://example.com",
+ },
+ {
+ isMiddleMousePastePrefOn: true,
+ isLoadInBackground: false,
+ startPagePref: "about:home",
+ expectedURLBarFocus: true,
+ expectedURLBarValue: "",
+ },
+ {
+ isMiddleMousePastePrefOn: true,
+ isLoadInBackground: false,
+ startPagePref: "https://example.com",
+ expectedURLBarFocus: false,
+ expectedURLBarValue: "https://example.com",
+ },
+ {
+ isMiddleMousePastePrefOn: false,
+ isLoadInBackground: true,
+ startPagePref: "about:home",
+ expectedURLBarFocus: true,
+ expectedURLBarValue: "",
+ },
+ {
+ isMiddleMousePastePrefOn: false,
+ isLoadInBackground: true,
+ startPagePref: "https://example.com",
+ expectedURLBarFocus: true,
+ expectedURLBarValue: "",
+ },
+ {
+ isMiddleMousePastePrefOn: true,
+ isLoadInBackground: true,
+ startPagePref: "about:home",
+ expectedURLBarFocus: true,
+ expectedURLBarValue: "",
+ },
+ {
+ isMiddleMousePastePrefOn: true,
+ isLoadInBackground: true,
+ startPagePref: "https://example.com",
+ expectedURLBarFocus: true,
+ expectedURLBarValue: "",
+ },
+ ];
+
+ for (const testData of TEST_DATA) {
+ await testMiddleClickOnHomeButton(testData);
+ }
+});
+
+add_task(async function test_middleClickOnHomeButtonWithNewWindow() {
+ await testMiddleClickOnHomeButtonWithNewWindow(false);
+ await testMiddleClickOnHomeButtonWithNewWindow(true);
+});
+
+async function testMiddleClickOnTab(isMiddleMousePastePrefOn) {
+ info(`Set middlemouse.paste [${isMiddleMousePastePrefOn}]`);
+ Services.prefs.setBoolPref("middlemouse.paste", isMiddleMousePastePrefOn);
+
+ info("Set initial value");
+ SpecialPowers.clipboardCopyString("test\nsample");
+ gURLBar.value = "";
+ gURLBar.focus();
+
+ info("Open two tabs");
+ const tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ const tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ info("Middle click on tab2 to remove it");
+ EventUtils.synthesizeMouseAtCenter(tab2, { button: 1 });
+
+ info("Wait until the tab1 is selected");
+ await TestUtils.waitForCondition(() => gBrowser.selectedTab === tab1);
+
+ Assert.equal(gURLBar.value, "", "URLBar has no pasted value");
+
+ BrowserTestUtils.removeTab(tab1);
+}
+
+async function testMiddleClickToOpenNewTab(isMiddleMousePastePrefOn, selector) {
+ info(`Set middlemouse.paste [${isMiddleMousePastePrefOn}]`);
+ Services.prefs.setBoolPref("middlemouse.paste", isMiddleMousePastePrefOn);
+
+ info("Set initial value");
+ SpecialPowers.clipboardCopyString("test\nsample");
+ gURLBar.value = "";
+ gURLBar.focus();
+
+ info(`Click on ${selector}`);
+ const originalTab = gBrowser.selectedTab;
+ const element = document.querySelector(selector);
+ EventUtils.synthesizeMouseAtCenter(element, { button: 1 });
+
+ info("Wait until the new tab is opened");
+ await TestUtils.waitForCondition(() => gBrowser.selectedTab !== originalTab);
+
+ Assert.equal(gURLBar.value, "", "URLBar has no pasted value");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}
+
+async function testMiddleClickOnURLBar(isMiddleMousePastePrefOn) {
+ info(`Set middlemouse.paste [${isMiddleMousePastePrefOn}]`);
+ Services.prefs.setBoolPref("middlemouse.paste", isMiddleMousePastePrefOn);
+
+ info("Set initial value");
+ SpecialPowers.clipboardCopyString("test\nsample");
+ gURLBar.value = "";
+ gURLBar.focus();
+
+ info("Middle click on the urlbar");
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, { button: 1 });
+
+ if (isMiddleMousePastePrefOn) {
+ Assert.equal(gURLBar.value, "test sample", "URLBar has pasted value");
+ } else {
+ Assert.equal(gURLBar.value, "", "URLBar has no pasted value");
+ }
+}
+
+async function testMiddleClickOnHomeButton({
+ isMiddleMousePastePrefOn,
+ isLoadInBackground,
+ startPagePref,
+ expectedURLBarFocus,
+ expectedURLBarValue,
+}) {
+ info(`middlemouse.paste [${isMiddleMousePastePrefOn}]`);
+ info(`browser.startup.homepage [${startPagePref}]`);
+ info(`browser.tabs.loadBookmarksInBackground [${isLoadInBackground}]`);
+
+ info("Set initial value");
+ Services.prefs.setCharPref("browser.startup.homepage", startPagePref);
+ Services.prefs.setBoolPref(
+ "browser.tabs.loadBookmarksInBackground",
+ isLoadInBackground
+ );
+ Services.prefs.setBoolPref("middlemouse.paste", isMiddleMousePastePrefOn);
+ SpecialPowers.clipboardCopyString("test\nsample");
+ gURLBar.value = "";
+ gURLBar.focus();
+
+ info("Middle click on the home button");
+ const currentTab = gBrowser.selectedTab;
+ const homeButton = document.getElementById("home-button");
+ EventUtils.synthesizeMouseAtCenter(homeButton, { button: 1 });
+
+ if (!isLoadInBackground) {
+ info("Wait until the a new tab is selected");
+ await TestUtils.waitForCondition(() => gBrowser.selectedTab !== currentTab);
+ }
+
+ info("Wait until the focus moves");
+ await TestUtils.waitForCondition(
+ () =>
+ (document.activeElement === gURLBar.inputField) === expectedURLBarFocus
+ );
+
+ Assert.ok(true, "The focus is correct");
+ Assert.equal(gURLBar.value, expectedURLBarValue, "URLBar value is correct");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}
+
+async function testMiddleClickOnHomeButtonWithNewWindow(
+ isMiddleMousePastePrefOn
+) {
+ info(`Set middlemouse.paste [${isMiddleMousePastePrefOn}]`);
+ Services.prefs.setBoolPref("middlemouse.paste", isMiddleMousePastePrefOn);
+
+ info("Set prefs to open in a new window");
+ Services.prefs.setBoolPref("middlemouse.openNewWindow", true);
+ Services.prefs.setBoolPref("browser.tabs.opentabfor.middleclick", false);
+
+ info("Set initial value");
+ SpecialPowers.clipboardCopyString("test\nsample");
+ gURLBar.value = "";
+ gURLBar.focus();
+
+ info("Middle click on the home button");
+ const homeButton = document.getElementById("home-button");
+ const onNewWindowOpened = BrowserTestUtils.waitForNewWindow();
+ EventUtils.synthesizeMouseAtCenter(homeButton, { button: 1 });
+
+ const newWindow = await onNewWindowOpened;
+ Assert.equal(newWindow.gURLBar.value, "", "URLBar value is correct");
+
+ await BrowserTestUtils.closeWindow(newWindow);
+}
diff --git a/browser/components/urlbar/tests/browser/browser_new_tab_urlbar_reset.js b/browser/components/urlbar/tests/browser/browser_new_tab_urlbar_reset.js
new file mode 100644
index 0000000000..b2bce4b22e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_new_tab_urlbar_reset.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Verify that urlbar state is reset when opening a new tab, so searching for the
+ * same text will reopen the results popup.
+ */
+add_task(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank",
+ false
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "m",
+ });
+ assertOpen();
+
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank",
+ false
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "m",
+ });
+ assertOpen();
+
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+function assertOpen() {
+ Assert.equal(gURLBar.view.isOpen, true, "Should be showing the popup");
+}
diff --git a/browser/components/urlbar/tests/browser/browser_oneOffs.js b/browser/components/urlbar/tests/browser/browser_oneOffs.js
new file mode 100644
index 0000000000..d9a6a8d416
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_oneOffs.js
@@ -0,0 +1,980 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the one-off search buttons in the urlbar.
+ */
+
+"use strict";
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+let gMaxResults;
+let engine;
+
+XPCOMUtils.defineLazyGetter(this, "oneOffSearchButtons", () => {
+ return UrlbarTestUtils.getOneOffSearchButtons(window);
+});
+
+add_setup(async function () {
+ gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+
+ // Add a search suggestion engine and move it to the front so that it appears
+ // as the first one-off.
+ engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ });
+ await Services.search.moveEngine(engine, 0);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", false],
+ ["browser.urlbar.suggest.quickactions", false],
+ ["browser.urlbar.shortcuts.quickactions", true],
+ ],
+ });
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ });
+
+ // Initialize history with enough visits to fill up the view.
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ for (let i = 0; i < gMaxResults; i++) {
+ await PlacesTestUtils.addVisits(
+ "http://example.com/browser_urlbarOneOffs.js/?" + i
+ );
+ }
+
+ // Add some more visits to the last URL added above so that the top-sites view
+ // will be non-empty.
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits(
+ "http://example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - 1)
+ );
+ }
+ await updateTopSites(sites => {
+ return sites && sites[0] && sites[0].url.startsWith("http://example.com/");
+ });
+
+ // Move the mouse away from the view so that a result or one-off isn't
+ // inadvertently highlighted. See bug 1659011.
+ EventUtils.synthesizeMouse(
+ gURLBar.inputField,
+ 0,
+ 0,
+ { type: "mousemove" },
+ window
+ );
+});
+
+// Opens the view without showing the one-offs. They should be hidden and arrow
+// key selection should work properly.
+add_task(async function noOneOffs() {
+ // Do a search for "@" since we hide the one-offs in that case.
+ let value = "@";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ fireInputEvent: true,
+ });
+ await TestUtils.waitForCondition(
+ () => !oneOffSearchButtons._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ false,
+ "One-offs should be hidden"
+ );
+ assertState(-1, -1, value);
+
+ // Get the result count. We don't particularly care what the results are,
+ // just what the count is so that we can key through them all.
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+
+ // Key down through all results.
+ for (let i = 0; i < resultCount; i++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ assertState(i, -1);
+ }
+
+ // Key down again. Nothing should be selected.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ assertState(-1, -1, value);
+
+ // Key down again. The first result should be selected.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ assertState(0, -1);
+
+ // Key up. Nothing should be selected.
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertState(-1, -1, value);
+
+ // Key up through all the results.
+ for (let i = resultCount - 1; i >= 0; i--) {
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertState(i, -1);
+ }
+
+ // Key up again. Nothing should be selected.
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertState(-1, -1, value);
+
+ await hidePopup();
+});
+
+// Opens the top-sites view. The one-offs should be shown.
+add_task(async function topSites() {
+ // Do a search that shows top sites.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ fireInputEvent: true,
+ });
+ await TestUtils.waitForCondition(
+ () => !oneOffSearchButtons._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+
+ // There's one top sites result, the page with a lot of visits from init.
+ let resultURL = "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - 1);
+ Assert.equal(UrlbarTestUtils.getResultCount(window), 1, "Result count");
+
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ true,
+ "One-offs are visible"
+ );
+
+ assertState(-1, -1, "");
+
+ // Key down into the result.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ assertState(0, -1, resultURL);
+
+ // Key down through each one-off.
+ let numButtons = oneOffSearchButtons.getSelectableButtons(true).length;
+ for (let i = 0; i < numButtons; i++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ assertState(-1, i, "");
+ }
+
+ // Key down again. The selection should go away.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ assertState(-1, -1, "");
+
+ // Key down again. The result should be selected.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ assertState(0, -1, resultURL);
+
+ // Key back up. The selection should go away.
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertState(-1, -1, "");
+
+ // Key up again. The selection should wrap back around to the one-offs. Key
+ // up through all the one-offs.
+ for (let i = numButtons - 1; i >= 0; i--) {
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertState(-1, i, "");
+ }
+
+ // Key up. The result should be selected.
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertState(0, -1, resultURL);
+
+ // Key up again. The selection should go away.
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertState(-1, -1, "");
+
+ await hidePopup();
+});
+
+// Keys up and down through the non-top-sites view, i.e., the view that's shown
+// when the input has been edited.
+add_task(async function editedView() {
+ // Use a typed value that returns the visits added above but that doesn't
+ // trigger autofill since that would complicate the test.
+ let typedValue = "browser_urlbarOneOffs";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typedValue,
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, gMaxResults - 1);
+ let heuristicResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ assertState(0, -1, typedValue);
+
+ // Key down through each result. The first result is already selected, which
+ // is why gMaxResults - 1 is the correct number of times to do this.
+ for (let i = 0; i < gMaxResults - 1; i++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ // i starts at zero so that the textValue passed to assertState is correct.
+ // But that means that i + 1 is the expected selected index, since initially
+ // (when this loop starts) the first result is selected.
+ assertState(
+ i + 1,
+ -1,
+ "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1)
+ );
+ Assert.ok(
+ !BrowserTestUtils.is_visible(heuristicResult.element.action),
+ "The heuristic action should not be visible"
+ );
+ }
+
+ // Key down through each one-off.
+ let numButtons = oneOffSearchButtons.getSelectableButtons(true).length;
+ for (let i = 0; i < numButtons; i++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ assertState(-1, i, typedValue);
+ Assert.equal(
+ BrowserTestUtils.is_visible(heuristicResult.element.action),
+ !oneOffSearchButtons.selectedButton.classList.contains(
+ "search-setting-button"
+ ),
+ "The heuristic action should be visible when a one-off button is selected"
+ );
+ }
+
+ // Key down once more. The selection should wrap around to the first result.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ assertState(0, -1, typedValue);
+ Assert.ok(
+ BrowserTestUtils.is_visible(heuristicResult.element.action),
+ "The heuristic action should be visible"
+ );
+
+ // Now key up. The selection should wrap back around to the one-offs. Key
+ // up through all the one-offs.
+ for (let i = numButtons - 1; i >= 0; i--) {
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertState(-1, i, typedValue);
+ Assert.equal(
+ BrowserTestUtils.is_visible(heuristicResult.element.action),
+ !oneOffSearchButtons.selectedButton.classList.contains(
+ "search-setting-button"
+ ),
+ "The heuristic action should be visible when a one-off button is selected"
+ );
+ }
+
+ // Key up through each non-heuristic result.
+ for (let i = gMaxResults - 2; i >= 0; i--) {
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertState(
+ i + 1,
+ -1,
+ "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1)
+ );
+ Assert.ok(
+ !BrowserTestUtils.is_visible(heuristicResult.element.action),
+ "The heuristic action should not be visible"
+ );
+ }
+
+ // Key up once more. The heuristic result should be selected.
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertState(0, -1, typedValue);
+ Assert.ok(
+ BrowserTestUtils.is_visible(heuristicResult.element.action),
+ "The heuristic action should be visible"
+ );
+
+ await hidePopup();
+});
+
+// Checks that "Search with Current Search Engine" items are updated to "Search
+// with One-Off Engine" when a one-off is selected.
+add_task(async function searchWith() {
+ // Enable suggestions for this subtest so we can check non-heuristic results.
+ let oldDefaultEngine = await Services.search.getDefault();
+ await Services.search.setDefault(
+ engine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", true]],
+ });
+
+ let typedValue = "foo";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typedValue,
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ assertState(0, -1, typedValue);
+
+ Assert.equal(
+ result.displayed.action,
+ "Search with " + (await Services.search.getDefault()).name,
+ "Sanity check: first result's action text"
+ );
+
+ // Alt+Down to the second one-off. Now the first result and the second
+ // one-off should both be selected.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, repeat: 2 });
+ assertState(0, 1, typedValue);
+
+ let engineName = oneOffSearchButtons.selectedButton.engine.name;
+ Assert.notEqual(
+ engineName,
+ (await Services.search.getDefault()).name,
+ "Sanity check: Second one-off engine should not be the current engine"
+ );
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ result.displayed.action,
+ "Search with " + engineName,
+ "First result's action text should be updated"
+ );
+
+ // Check non-heuristic results.
+ await hidePopup();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typedValue,
+ });
+
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ assertState(1, -1, typedValue + "foo");
+ Assert.equal(
+ result.displayed.action,
+ "Search with " + engine.name,
+ "Sanity check: second result's action text"
+ );
+ Assert.ok(!result.heuristic, "The second result is not heuristic.");
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, repeat: 2 });
+ assertState(1, 1, typedValue + "foo");
+
+ engineName = oneOffSearchButtons.selectedButton.engine.name;
+ Assert.notEqual(
+ engineName,
+ (await Services.search.getDefault()).name,
+ "Sanity check: Second one-off engine should not be the current engine"
+ );
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+
+ Assert.equal(
+ result.displayed.action,
+ "Search with " + engineName,
+ "Second result's action text should be updated"
+ );
+
+ await SpecialPowers.popPrefEnv();
+ await Services.search.setDefault(
+ oldDefaultEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await hidePopup();
+});
+
+// Clicks a one-off with an engine.
+add_task(async function oneOffClick() {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+
+ // We are explicitly using something that looks like a url, to make the test
+ // stricter. Even if it looks like a url, we should search.
+ let typedValue = "foo.bar";
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typedValue,
+ });
+ await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ assertState(0, -1, typedValue);
+ let oneOffs = oneOffSearchButtons.getSelectableButtons(true);
+
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(oneOffs[0], {});
+ await searchPromise;
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Urlbar view is still open.");
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: oneOffs[0].engine.name,
+ entry: "oneoff",
+ });
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+ await UrlbarTestUtils.formHistory.clear();
+});
+
+// Presses the Return key when a one-off with an engine is selected.
+add_task(async function oneOffReturn() {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+
+ // We are explicitly using something that looks like a url, to make the test
+ // stricter. Even if it looks like a url, we should search.
+ let typedValue = "foo.bar";
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typedValue,
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ assertState(0, -1, typedValue);
+ let oneOffs = oneOffSearchButtons.getSelectableButtons(true);
+
+ // Alt+Down to select the first one-off.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ assertState(0, 0, typedValue);
+
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Urlbar view is still open.");
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: oneOffs[0].engine.name,
+ entry: "oneoff",
+ });
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+ await UrlbarTestUtils.formHistory.clear();
+ await hidePopup();
+});
+
+// When all engines and local shortcuts are hidden except for the current
+// engine, the one-offs container should be hidden.
+add_task(async function allOneOffsHiddenExceptCurrentEngine() {
+ // Disable all the engines but the current one, check the oneoffs are
+ // hidden and that moving up selects the last match.
+ let defaultEngine = await Services.search.getDefault();
+ let engines = (await Services.search.getVisibleEngines()).filter(
+ e => e.name != defaultEngine.name
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.hiddenOneOffs", engines.map(e => e.name).join(",")],
+ ...UrlbarUtils.LOCAL_SEARCH_MODES.map(m => [
+ `browser.urlbar.${m.pref}`,
+ false,
+ ]),
+ ],
+ });
+
+ let typedValue = "foo";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typedValue,
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ assertState(0, -1);
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ false,
+ "The one-off buttons should be hidden"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ assertState(0, -1);
+ await hidePopup();
+ await SpecialPowers.popPrefEnv();
+});
+
+// The one-offs should be hidden when searching with an "@engine" search engine
+// alias.
+add_task(async function hiddenWhenUsingSearchAlias() {
+ let typedValue = "@example";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typedValue,
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ false,
+ "Should not be showing the one-off buttons"
+ );
+ await hidePopup();
+
+ typedValue = "not an engine alias";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typedValue,
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ true,
+ "Should be showing the one-off buttons"
+ );
+ await hidePopup();
+});
+
+// Makes sure the local shortcuts exist.
+add_task(async function localShortcuts() {
+ oneOffSearchButtons.invalidateCache();
+ await doLocalShortcutsShownTest();
+});
+
+// Clicks a local shortcut button.
+add_task(async function localShortcutClick() {
+ // We are explicitly using something that looks like a url, to make the test
+ // stricter. Even if it looks like a url, we should search.
+ let typedValue = "foo.bar";
+
+ oneOffSearchButtons.invalidateCache();
+ let rebuildPromise = BrowserTestUtils.waitForEvent(
+ oneOffSearchButtons,
+ "rebuild"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typedValue,
+ });
+ await rebuildPromise;
+
+ let buttons = oneOffSearchButtons.localButtons;
+ Assert.ok(buttons.length, "Sanity check: Local shortcuts exist");
+
+ for (let button of buttons) {
+ Assert.ok(button.source, "Sanity check: Button has a source");
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ await searchPromise;
+ Assert.ok(
+ UrlbarTestUtils.isPopupOpen(window),
+ "Urlbar view is still open."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: button.source,
+ entry: "oneoff",
+ });
+ }
+
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+ await hidePopup();
+});
+
+// Presses the Return key when a local shortcut is selected.
+add_task(async function localShortcutReturn() {
+ // We are explicitly using something that looks like a url, to make the test
+ // stricter. Even if it looks like a url, we should search.
+ let typedValue = "foo.bar";
+
+ oneOffSearchButtons.invalidateCache();
+ let rebuildPromise = BrowserTestUtils.waitForEvent(
+ oneOffSearchButtons,
+ "rebuild"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typedValue,
+ });
+ await rebuildPromise;
+
+ let buttons = oneOffSearchButtons.localButtons;
+ Assert.ok(buttons.length, "Sanity check: Local shortcuts exist");
+
+ let allButtons = oneOffSearchButtons.getSelectableButtons(false);
+ let firstLocalIndex = allButtons.length - buttons.length;
+
+ for (let i = 0; i < buttons.length; i++) {
+ let button = buttons[i];
+
+ // Alt+Down enough times to select the button.
+ let index = firstLocalIndex + i;
+ EventUtils.synthesizeKey("KEY_ArrowDown", {
+ altKey: true,
+ repeat: index + 1,
+ });
+ await TestUtils.waitForCondition(
+ () => oneOffSearchButtons.selectedButtonIndex == index,
+ "Waiting for local shortcut to become selected"
+ );
+
+ let expectedSelectedResultIndex = -1;
+ let count = UrlbarTestUtils.getResultCount(window);
+ if (count > 0) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ if (result.heuristic) {
+ expectedSelectedResultIndex = 0;
+ }
+ }
+ assertState(expectedSelectedResultIndex, index, typedValue);
+
+ Assert.ok(button.source, "Sanity check: Button has a source");
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+ Assert.ok(
+ UrlbarTestUtils.isPopupOpen(window),
+ "Urlbar view is still open."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: button.source,
+ entry: "oneoff",
+ });
+ }
+
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+ await hidePopup();
+});
+
+// With an empty search string, clicking a local shortcut should result in no
+// heuristic result.
+add_task(async function localShortcutEmptySearchString() {
+ oneOffSearchButtons.invalidateCache();
+ let rebuildPromise = BrowserTestUtils.waitForEvent(
+ oneOffSearchButtons,
+ "rebuild"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await rebuildPromise;
+
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ true,
+ "One-offs are visible"
+ );
+
+ let buttons = oneOffSearchButtons.localButtons;
+ Assert.ok(buttons.length, "Sanity check: Local shortcuts exist");
+
+ for (let button of buttons) {
+ Assert.ok(button.source, "Sanity check: Button has a source");
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ await searchPromise;
+ Assert.ok(
+ UrlbarTestUtils.isPopupOpen(window),
+ "Urlbar view is still open."
+ );
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ true,
+ "One-offs are visible"
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: button.source,
+ entry: "oneoff",
+ });
+
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+ if (!resultCount) {
+ Assert.equal(
+ gURLBar.panel.getAttribute("noresults"),
+ "true",
+ "Panel has no results, therefore should have noresults attribute"
+ );
+ continue;
+ }
+ Assert.ok(
+ !gURLBar.panel.hasAttribute("noresults"),
+ "Panel has results, therefore should not have noresults attribute"
+ );
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(!result.heuristic, "The first result should not be heuristic");
+ }
+
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+
+ await hidePopup();
+});
+
+// Trigger SearchOneOffs.willHide() outside of SearchOneOffs.__rebuild(). Ensure
+// that we always show the correct engines in the one-offs. This effectively
+// tests SearchOneOffs._engineInfo.domWasUpdated.
+add_task(async function avoidWillHideRace() {
+ // We set maxHistoricalSearchSuggestions to 0 since this test depends on
+ // UrlbarView calling SearchOneOffs.willHide(). That only happens when the
+ // Urlbar is in search mode after a query that returned no results.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.maxHistoricalSearchSuggestions", 0]],
+ });
+
+ oneOffSearchButtons.invalidateCache();
+
+ // Accel+K triggers SearchOneOffs.willHide() from UrlbarView instead of from
+ // SearchOneOffs.__rebuild.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("k", { accelKey: true });
+ await searchPromise;
+ Assert.ok(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ "One-offs should be visible"
+ );
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ info("Hide all engines but the test engine.");
+ let oldDefaultEngine = await Services.search.getDefault();
+ await Services.search.setDefault(
+ engine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ let engines = (await Services.search.getVisibleEngines()).filter(
+ e => e.name != engine.name
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.hiddenOneOffs", engines.map(e => e.name).join(",")],
+ ...UrlbarUtils.LOCAL_SEARCH_MODES.map(m => [
+ `browser.urlbar.${m.pref}`,
+ false,
+ ]),
+ ],
+ });
+ Assert.ok(
+ !oneOffSearchButtons._engineInfo,
+ "_engineInfo should be nulled out."
+ );
+
+ // This call to SearchOneOffs.willHide() should repopulate _engineInfo but not
+ // rebuild the one-offs. _engineInfo.willHide will be true and thus UrlbarView
+ // will not open.
+ EventUtils.synthesizeKey("k", { accelKey: true });
+ // We can't wait for UrlbarTestUtils.promiseSearchComplete here since we
+ // expect the popup will not open. We wait for _engineInfo to be populated
+ // instead.
+ await BrowserTestUtils.waitForCondition(
+ () => !!oneOffSearchButtons._engineInfo,
+ "_engineInfo is set."
+ );
+ Assert.ok(!UrlbarTestUtils.isPopupOpen(window), "The UrlbarView is closed.");
+ Assert.equal(
+ oneOffSearchButtons._engineInfo.willHide,
+ true,
+ "_engineInfo should be repopulated and willHide should be true."
+ );
+ Assert.equal(
+ oneOffSearchButtons._engineInfo.domWasUpdated,
+ undefined,
+ "domWasUpdated should not be populated since we haven't yet tried to rebuild the one-offs."
+ );
+
+ // Now search. The view will open and the one-offs will rebuild, although
+ // the one-offs will not be shown since there is only one engine.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ Assert.equal(
+ oneOffSearchButtons._engineInfo.domWasUpdated,
+ true,
+ "domWasUpdated should be true"
+ );
+ Assert.ok(
+ !UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ "One-offs should be hidden since there is only one engine."
+ );
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ await SpecialPowers.popPrefEnv();
+ await Services.search.setDefault(
+ oldDefaultEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+// Hides each of the local shortcuts one at a time. The search buttons should
+// automatically rebuild themselves.
+add_task(async function individualLocalShortcutsHidden() {
+ for (let { pref, source } of UrlbarUtils.LOCAL_SEARCH_MODES) {
+ await SpecialPowers.pushPrefEnv({
+ set: [[`browser.urlbar.${pref}`, false]],
+ });
+
+ let rebuildPromise = BrowserTestUtils.waitForEvent(
+ oneOffSearchButtons,
+ "rebuild"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await rebuildPromise;
+
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ true,
+ "One-offs are visible"
+ );
+
+ let buttons = oneOffSearchButtons.localButtons;
+ Assert.ok(buttons.length, "Sanity check: Local shortcuts exist");
+
+ let otherModes = UrlbarUtils.LOCAL_SEARCH_MODES.filter(
+ m => m.source != source
+ );
+ Assert.equal(
+ buttons.length,
+ otherModes.length,
+ "Expected number of enabled local shortcut buttons"
+ );
+
+ for (let i = 0; i < buttons.length; i++) {
+ Assert.equal(
+ buttons[i].source,
+ otherModes[i].source,
+ "Button has the expected source"
+ );
+ }
+
+ await hidePopup();
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+// Hides all the local shortcuts at once.
+add_task(async function allLocalShortcutsHidden() {
+ await SpecialPowers.pushPrefEnv({
+ set: UrlbarUtils.LOCAL_SEARCH_MODES.map(m => [
+ `browser.urlbar.${m.pref}`,
+ false,
+ ]),
+ });
+
+ let rebuildPromise = BrowserTestUtils.waitForEvent(
+ oneOffSearchButtons,
+ "rebuild"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await rebuildPromise;
+
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ true,
+ "One-offs are visible"
+ );
+
+ Assert.equal(
+ oneOffSearchButtons.localButtons.length,
+ 0,
+ "All local shortcuts should be hidden"
+ );
+
+ Assert.greater(
+ oneOffSearchButtons.getSelectableButtons(false).filter(b => b.engine)
+ .length,
+ 0,
+ "Engine one-offs should not be hidden"
+ );
+
+ await hidePopup();
+ await SpecialPowers.popPrefEnv();
+});
+
+// Hides all the engines but none of the local shortcuts.
+add_task(async function localShortcutsShownWhenEnginesHidden() {
+ let engines = await Services.search.getVisibleEngines();
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.hiddenOneOffs", engines.map(e => e.name).join(",")]],
+ });
+
+ let rebuildPromise = BrowserTestUtils.waitForEvent(
+ oneOffSearchButtons,
+ "rebuild"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await rebuildPromise;
+
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ true,
+ "One-offs are visible"
+ );
+
+ Assert.equal(
+ oneOffSearchButtons.localButtons.length,
+ UrlbarUtils.LOCAL_SEARCH_MODES.length,
+ "All local shortcuts are visible"
+ );
+
+ Assert.equal(
+ oneOffSearchButtons.getSelectableButtons(false).filter(b => b.engine)
+ .length,
+ 0,
+ "All engine one-offs are hidden"
+ );
+
+ await hidePopup();
+ await SpecialPowers.popPrefEnv();
+});
+
+/**
+ * Checks that the local shortcuts are shown correctly.
+ */
+async function doLocalShortcutsShownTest() {
+ let rebuildPromise = BrowserTestUtils.waitForEvent(
+ oneOffSearchButtons,
+ "rebuild"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "doLocalShortcutsShownTest",
+ });
+ await rebuildPromise;
+
+ let buttons = oneOffSearchButtons.localButtons;
+ Assert.equal(buttons.length, 4, "Expected number of local shortcuts");
+
+ let expectedSource;
+ let seenIDs = new Set();
+ for (let button of buttons) {
+ Assert.ok(
+ !seenIDs.has(button.id),
+ "Should not have already seen button.id"
+ );
+ seenIDs.add(button.id);
+ switch (button.id) {
+ case "urlbar-engine-one-off-item-bookmarks":
+ expectedSource = UrlbarUtils.RESULT_SOURCE.BOOKMARKS;
+ break;
+ case "urlbar-engine-one-off-item-tabs":
+ expectedSource = UrlbarUtils.RESULT_SOURCE.TABS;
+ break;
+ case "urlbar-engine-one-off-item-history":
+ expectedSource = UrlbarUtils.RESULT_SOURCE.HISTORY;
+ break;
+ case "urlbar-engine-one-off-item-actions":
+ expectedSource = UrlbarUtils.RESULT_SOURCE.ACTIONS;
+ break;
+ default:
+ Assert.ok(false, `Unexpected local shortcut ID: ${button.id}`);
+ break;
+ }
+ Assert.equal(button.source, expectedSource, "Expected button.source");
+ }
+
+ await hidePopup();
+}
+
+function assertState(result, oneOff, textValue = undefined) {
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ result,
+ "Expected result should be selected"
+ );
+ Assert.equal(
+ oneOffSearchButtons.selectedButtonIndex,
+ oneOff,
+ "Expected one-off should be selected"
+ );
+ if (textValue !== undefined) {
+ Assert.equal(gURLBar.value, textValue, "Expected value");
+ }
+}
+
+function hidePopup() {
+ return UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_Escape");
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_oneOffs_contextMenu.js b/browser/components/urlbar/tests/browser/browser_oneOffs_contextMenu.js
new file mode 100644
index 0000000000..60d46608cd
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_oneOffs_contextMenu.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that the right-click menu works correctly for the one-off buttons.
+ */
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+let gMaxResults;
+
+XPCOMUtils.defineLazyGetter(this, "oneOffSearchButtons", () => {
+ return UrlbarTestUtils.getOneOffSearchButtons(window);
+});
+
+let originalEngine;
+let newEngine;
+
+// The one-off context menu should not be shown.
+add_task(async function contextMenu_not_shown() {
+ // Add a popupshown listener on the context menu that sets this
+ // popupshownFired boolean.
+ let popupshownFired = false;
+ let onPopupshown = () => {
+ popupshownFired = true;
+ };
+ let contextMenu = oneOffSearchButtons.querySelector(
+ ".search-one-offs-context-menu"
+ );
+ contextMenu.addEventListener("popupshown", onPopupshown);
+
+ // Do a search to open the view.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+
+ // First, try to open the context menu on a remote engine.
+ let allOneOffs = oneOffSearchButtons.getSelectableButtons(true);
+ Assert.greater(allOneOffs.length, 0, "There should be at least one one-off");
+ Assert.ok(
+ allOneOffs[0].engine,
+ "The first one-off should be a remote one-off"
+ );
+ EventUtils.synthesizeMouseAtCenter(allOneOffs[0], {
+ type: "contextmenu",
+ button: 2,
+ });
+ let timeout = 500;
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, timeout));
+ Assert.ok(
+ !popupshownFired,
+ "popupshown should not be fired on a remote one-off"
+ );
+
+ // Now try to open the context menu on a local one-off.
+ let localOneOffs = oneOffSearchButtons.localButtons;
+ Assert.greater(
+ localOneOffs.length,
+ 0,
+ "There should be at least one local one-off"
+ );
+ EventUtils.synthesizeMouseAtCenter(localOneOffs[0], {
+ type: "contextmenu",
+ button: 2,
+ });
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, timeout));
+ Assert.ok(
+ !popupshownFired,
+ "popupshown should not be fired on a local one-off"
+ );
+
+ contextMenu.removeEventListener("popupshown", onPopupshown);
+ await UrlbarTestUtils.promisePopupClose(window);
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js b/browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js
new file mode 100644
index 0000000000..3513fd2dac
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js
@@ -0,0 +1,516 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that heuristic results are updated/restyled to search results when a
+ * one-off is selected.
+ */
+
+"use strict";
+
+XPCOMUtils.defineLazyGetter(this, "oneOffSearchButtons", () => {
+ return UrlbarTestUtils.getOneOffSearchButtons(window);
+});
+
+const TEST_DEFAULT_ENGINE_NAME = "Test";
+
+const HISTORY_URL = "https://mozilla.org/";
+
+const KEYWORD = "kw";
+const KEYWORD_URL = "https://mozilla.org/search?q=%s";
+
+// Expected result data for our test results.
+const RESULT_DATA_BY_TYPE = {
+ [UrlbarUtils.RESULT_TYPE.URL]: {
+ icon: `page-icon:${HISTORY_URL}`,
+ actionL10n: {
+ id: "urlbar-result-action-visit",
+ },
+ },
+ [UrlbarUtils.RESULT_TYPE.SEARCH]: {
+ icon: "chrome://global/skin/icons/search-glass.svg",
+ actionL10n: {
+ id: "urlbar-result-action-search-w-engine",
+ args: { engine: TEST_DEFAULT_ENGINE_NAME },
+ },
+ },
+ [UrlbarUtils.RESULT_TYPE.KEYWORD]: {
+ icon: `page-icon:${KEYWORD_URL}`,
+ },
+};
+
+function getSourceIcon(source) {
+ switch (source) {
+ case UrlbarUtils.RESULT_SOURCE.BOOKMARKS:
+ return "chrome://browser/skin/bookmark.svg";
+ case UrlbarUtils.RESULT_SOURCE.HISTORY:
+ return "chrome://browser/skin/history.svg";
+ case UrlbarUtils.RESULT_SOURCE.TABS:
+ return "chrome://browser/skin/tab.svg";
+ default:
+ return null;
+ }
+}
+
+/**
+ * Asserts that the heuristic result is *not* restyled to look like a search
+ * result.
+ *
+ * @param {UrlbarUtils.RESULT_TYPE} expectedType
+ * The expected type of the heuristic.
+ * @param {object} resultDetails
+ * The return value of UrlbarTestUtils.getDetailsOfResultAt(window, 0).
+ */
+async function heuristicIsNotRestyled(expectedType, resultDetails) {
+ Assert.equal(
+ resultDetails.type,
+ expectedType,
+ "The restyled result is the expected type."
+ );
+
+ Assert.equal(
+ resultDetails.displayed.title,
+ resultDetails.title,
+ "The displayed title is equal to the payload title."
+ );
+
+ let data = RESULT_DATA_BY_TYPE[expectedType];
+ Assert.ok(data, "Sanity check: Expected type is recognized");
+
+ let [actionText] = data.actionL10n
+ ? await document.l10n.formatValues([data.actionL10n])
+ : [""];
+
+ if (
+ expectedType === UrlbarUtils.RESULT_TYPE.URL &&
+ resultDetails.result.heuristic &&
+ resultDetails.result.payload.title
+ ) {
+ Assert.equal(
+ resultDetails.displayed.url,
+ resultDetails.result.payload.displayUrl
+ );
+ } else {
+ Assert.equal(
+ resultDetails.displayed.action,
+ actionText,
+ "The result has the expected non-styled action text."
+ );
+ }
+
+ Assert.equal(
+ BrowserTestUtils.is_visible(resultDetails.element.separator),
+ !!actionText,
+ "The title separator is " + (actionText ? "visible" : "hidden")
+ );
+ Assert.equal(
+ BrowserTestUtils.is_visible(resultDetails.element.action),
+ !!actionText,
+ "The action text is " + (actionText ? "visible" : "hidden")
+ );
+
+ Assert.equal(
+ resultDetails.image,
+ data.icon,
+ "The result has the expected non-styled icon."
+ );
+}
+
+/**
+ * Asserts that the heuristic result is restyled to look like a search result.
+ *
+ * @param {UrlbarUtils.RESULT_TYPE} expectedType
+ * The expected type of the heuristic.
+ * @param {object} resultDetails
+ * The return value of UrlbarTestUtils.getDetailsOfResultAt(window, 0).
+ * @param {string} searchString
+ * The current search string. The restyled heuristic result's title is
+ * expected to be this string.
+ * @param {element} selectedOneOff
+ * The selected one-off button.
+ */
+async function heuristicIsRestyled(
+ expectedType,
+ resultDetails,
+ searchString,
+ selectedOneOff
+) {
+ let engine = selectedOneOff.engine;
+ let source = selectedOneOff.source;
+ if (!engine && !source) {
+ Assert.ok(false, "An invalid one-off was passed to urlbarResultIsRestyled");
+ return;
+ }
+ Assert.equal(
+ resultDetails.type,
+ expectedType,
+ "The restyled result is still the expected type."
+ );
+
+ let actionText;
+ if (engine) {
+ [actionText] = await document.l10n.formatValues([
+ {
+ id: "urlbar-result-action-search-w-engine",
+ args: { engine: engine.name },
+ },
+ ]);
+ } else if (source) {
+ [actionText] = await document.l10n.formatValues([
+ {
+ id: `urlbar-result-action-search-${UrlbarUtils.getResultSourceName(
+ source
+ )}`,
+ },
+ ]);
+ }
+ Assert.equal(
+ resultDetails.displayed.action,
+ actionText,
+ "Restyled result's action text should be updated"
+ );
+
+ Assert.equal(
+ resultDetails.displayed.title,
+ searchString,
+ "The restyled result's title should be equal to the search string."
+ );
+
+ Assert.ok(
+ BrowserTestUtils.is_visible(resultDetails.element.separator),
+ "The restyled result's title separator should be visible"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_visible(resultDetails.element.action),
+ "The restyled result's action text should be visible"
+ );
+
+ if (engine) {
+ Assert.equal(
+ resultDetails.image,
+ engine.iconURI?.spec || UrlbarUtils.ICON.SEARCH_GLASS,
+ "The restyled result's icon should be the engine's icon."
+ );
+ } else if (source) {
+ Assert.equal(
+ resultDetails.image,
+ getSourceIcon(source),
+ "The restyled result's icon should be the local one-off's icon."
+ );
+ }
+}
+
+/**
+ * Asserts that the specified one-off (if any) is selected and that the
+ * heuristic result is either restyled or not restyled as appropriate. If
+ * there's a selected one-off, then the heuristic is expected to be restyled; if
+ * there's no selected one-off, then it's expected not to be restyled.
+ *
+ * @param {string} searchString
+ * The current search string. If a one-off is selected, then the restyled
+ * heuristic result's title is expected to be this string.
+ * @param {UrlbarUtils.RESULT_TYPE} expectedHeuristicType
+ * The expected type of the heuristic.
+ * @param {number} expectedSelectedOneOffIndex
+ * The index of the expected selected one-off button. If no one-off is
+ * expected to be selected, then pass -1.
+ */
+async function assertState(
+ searchString,
+ expectedHeuristicType,
+ expectedSelectedOneOffIndex
+) {
+ Assert.equal(
+ oneOffSearchButtons.selectedButtonIndex,
+ expectedSelectedOneOffIndex,
+ "Expected one-off should be selected"
+ );
+
+ let resultDetails = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ if (expectedSelectedOneOffIndex >= 0) {
+ await heuristicIsRestyled(
+ expectedHeuristicType,
+ resultDetails,
+ searchString,
+ oneOffSearchButtons.selectedButton
+ );
+ } else {
+ await heuristicIsNotRestyled(expectedHeuristicType, resultDetails);
+ }
+}
+
+add_setup(async function () {
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: TEST_DEFAULT_ENGINE_NAME,
+ keyword: "@test",
+ },
+ { setAsDefault: true }
+ );
+ let engine = Services.search.getEngineByName(TEST_DEFAULT_ENGINE_NAME);
+ await Services.search.moveEngine(engine, 0);
+
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits(HISTORY_URL);
+ }
+
+ await PlacesUtils.keywords.insert({
+ keyword: KEYWORD,
+ url: KEYWORD_URL,
+ });
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ await PlacesUtils.keywords.remove(KEYWORD);
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", false]],
+ });
+
+ // Move the mouse away from the view so that a result or one-off isn't
+ // inadvertently highlighted. See bug 1659011.
+ EventUtils.synthesizeMouse(
+ gURLBar.inputField,
+ 0,
+ 0,
+ { type: "mousemove" },
+ window
+ );
+});
+
+add_task(async function arrow_engine_url() {
+ await doArrowTest("mozilla.or", UrlbarUtils.RESULT_TYPE.URL, false);
+});
+
+add_task(async function arrow_engine_search() {
+ await doArrowTest("test", UrlbarUtils.RESULT_TYPE.SEARCH, false);
+});
+
+add_task(async function arrow_engine_keyword() {
+ await doArrowTest(`${KEYWORD} test`, UrlbarUtils.RESULT_TYPE.KEYWORD, false);
+});
+
+add_task(async function arrow_local_url() {
+ await doArrowTest("mozilla.or", UrlbarUtils.RESULT_TYPE.URL, true);
+});
+
+add_task(async function arrow_local_search() {
+ await doArrowTest("test", UrlbarUtils.RESULT_TYPE.SEARCH, true);
+});
+
+add_task(async function arrow_local_keyword() {
+ await doArrowTest(`${KEYWORD} test`, UrlbarUtils.RESULT_TYPE.KEYWORD, true);
+});
+
+/**
+ * Arrows down to the one-offs, checks the heuristic, and clicks it.
+ *
+ * @param {string} searchString
+ * The search string to use.
+ * @param {UrlbarUtils.RESULT_TYPE} expectedHeuristicType
+ * The type of heuristic result that the search string is expected to trigger.
+ * @param {boolean} useLocal
+ * Whether to test a local one-off or an engine one-off. If true, test a
+ * local one-off. If false, test an engine one-off.
+ */
+async function doArrowTest(searchString, expectedHeuristicType, useLocal) {
+ await doTest(searchString, expectedHeuristicType, useLocal, async () => {
+ info(
+ "Arrow down to the one-offs, observe heuristic is restyled as a search result."
+ );
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: resultCount });
+ await searchPromise;
+ await assertState(searchString, expectedHeuristicType, 0);
+
+ let depth = 1;
+ if (useLocal) {
+ for (; !oneOffSearchButtons.selectedButton.source; depth++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ Assert.ok(
+ oneOffSearchButtons.selectedButton.source,
+ "Selected one-off is local"
+ );
+ await assertState(searchString, expectedHeuristicType, depth - 1);
+ }
+
+ info(
+ "Arrow up out of the one-offs, observe heuristic styling is restored."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowUp", { repeat: depth });
+ await assertState(searchString, expectedHeuristicType, -1);
+
+ info(
+ "Arrow back down into the one-offs, observe heuristic is restyled as a search result."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: depth });
+ await assertState(searchString, expectedHeuristicType, depth - 1);
+ });
+}
+
+add_task(async function altArrow_engine_url() {
+ await doAltArrowTest("mozilla.or", UrlbarUtils.RESULT_TYPE.URL, false);
+});
+
+add_task(async function altArrow_engine_search() {
+ await doAltArrowTest("test", UrlbarUtils.RESULT_TYPE.SEARCH, false);
+});
+
+add_task(async function altArrow_engine_keyword() {
+ await doAltArrowTest(
+ `${KEYWORD} test`,
+ UrlbarUtils.RESULT_TYPE.KEYWORD,
+ false
+ );
+});
+
+add_task(async function altArrow_local_url() {
+ await doAltArrowTest("mozilla.or", UrlbarUtils.RESULT_TYPE.URL, true);
+});
+
+add_task(async function altArrow_local_search() {
+ await doAltArrowTest("test", UrlbarUtils.RESULT_TYPE.SEARCH, true);
+});
+
+add_task(async function altArrow_local_keyword() {
+ await doAltArrowTest(
+ `${KEYWORD} test`,
+ UrlbarUtils.RESULT_TYPE.KEYWORD,
+ true
+ );
+});
+
+/**
+ * Alt-arrows down to the one-offs so that the heuristic remains selected,
+ * checks the heuristic, and clicks it.
+ *
+ * @param {string} searchString
+ * The search string to use.
+ * @param {UrlbarUtils.RESULT_TYPE} expectedHeuristicType
+ * The type of heuristic result that the search string is expected to trigger.
+ * @param {boolean} useLocal
+ * Whether to test a local one-off or an engine one-off. If true, test a
+ * local one-off. If false, test an engine one-off.
+ */
+async function doAltArrowTest(searchString, expectedHeuristicType, useLocal) {
+ await doTest(searchString, expectedHeuristicType, useLocal, async () => {
+ info(
+ "Alt+down into the one-offs, observe heuristic is restyled as a search result."
+ );
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ await searchPromise;
+ await assertState(searchString, expectedHeuristicType, 0);
+
+ let depth = 1;
+ if (useLocal) {
+ for (; !oneOffSearchButtons.selectedButton.source; depth++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ }
+ Assert.ok(
+ oneOffSearchButtons.selectedButton.source,
+ "Selected one-off is local"
+ );
+ await assertState(searchString, expectedHeuristicType, depth - 1);
+ }
+
+ info(
+ "Arrow down and then up to re-select the heuristic, observe its styling is restored."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ await assertState(searchString, expectedHeuristicType, -1);
+
+ info(
+ "Alt+down into the one-offs, observe the heuristic is restyled as a search result."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, repeat: depth });
+ await assertState(searchString, expectedHeuristicType, depth - 1);
+
+ info("Alt+up out of the one-offs, observe the heuristic is restored.");
+ EventUtils.synthesizeKey("KEY_ArrowUp", { altKey: true, repeat: depth });
+ await assertState(searchString, expectedHeuristicType, -1);
+
+ info(
+ "Alt+down into the one-offs, observe the heuristic is restyled as a search result."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, repeat: depth });
+ await assertState(searchString, expectedHeuristicType, depth - 1);
+ });
+}
+
+/**
+ * The main test function. Starts a search, asserts that the heuristic has the
+ * expected type, calls a callback to run more checks, and then finally clicks
+ * the restyled heuristic to make sure search mode is confirmed.
+ *
+ * @param {string} searchString
+ * The search string to use.
+ * @param {UrlbarUtils.RESULT_TYPE} expectedHeuristicType
+ * The type of heuristic result that the search string is expected to trigger.
+ * @param {boolean} useLocal
+ * Whether to test a local one-off or an engine one-off. If true, test a
+ * local one-off. If false, test an engine one-off.
+ * @param {Function} callback
+ * This is called after the search completes. It should perform whatever
+ * checks are necessary for the test task. Important: When it returns, it
+ * should make sure that the first one-off is selected.
+ */
+async function doTest(searchString, expectedHeuristicType, useLocal, callback) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ await TestUtils.waitForCondition(
+ () => !oneOffSearchButtons._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(result.heuristic, "First result is heuristic");
+ Assert.equal(
+ result.type,
+ expectedHeuristicType,
+ "Heuristic is expected type"
+ );
+ await assertState(searchString, expectedHeuristicType, -1);
+
+ await callback();
+
+ Assert.ok(
+ oneOffSearchButtons.selectedButton,
+ "The callback should leave a one-off selected so that the heuristic remains re-styled"
+ );
+
+ info("Click the heuristic result and observe it confirms search mode.");
+ let selectedButton = oneOffSearchButtons.selectedButton;
+ let expectedSearchMode = {
+ entry: "oneoff",
+ isPreview: true,
+ };
+ if (useLocal) {
+ expectedSearchMode.source = selectedButton.source;
+ } else {
+ expectedSearchMode.engineName = selectedButton.engine.name;
+ }
+
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+
+ let heuristicRow = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ 0
+ );
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(heuristicRow, {});
+ await searchPromise;
+
+ expectedSearchMode.isPreview = false;
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+}
diff --git a/browser/components/urlbar/tests/browser/browser_oneOffs_keyModifiers.js b/browser/components/urlbar/tests/browser/browser_oneOffs_keyModifiers.js
new file mode 100644
index 0000000000..30b241b3b3
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_oneOffs_keyModifiers.js
@@ -0,0 +1,392 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that one-offs behave differently with key modifiers.
+ */
+
+"use strict";
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+const SEARCH_STRING = "foo.bar";
+
+XPCOMUtils.defineLazyGetter(this, "oneOffSearchButtons", () => {
+ return UrlbarTestUtils.getOneOffSearchButtons(window);
+});
+
+let engine;
+
+async function searchAndOpenPopup(value) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ fireInputEvent: true,
+ });
+ await TestUtils.waitForCondition(
+ () => !oneOffSearchButtons._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+}
+
+add_setup(async function () {
+ // Add a search suggestion engine and move it to the front so that it appears
+ // as the first one-off.
+ engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ });
+ await Services.search.moveEngine(engine, 0);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", false],
+ ["browser.urlbar.suggest.quickactions", false],
+ ],
+ });
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ });
+
+ // Initialize history with enough visits to fill up the view.
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+ for (let i = 0; i < maxResults; i++) {
+ await PlacesTestUtils.addVisits(
+ "http://mochi.test:8888/browser_urlbarOneOffs.js/?" + i
+ );
+ }
+
+ // Add some more visits to the last URL added above so that the top-sites view
+ // will be non-empty.
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits(
+ "http://mochi.test:8888/browser_urlbarOneOffs.js/?" + (maxResults - 1)
+ );
+ }
+ await updateTopSites(sites => {
+ return (
+ sites && sites[0] && sites[0].url.startsWith("http://mochi.test:8888/")
+ );
+ });
+
+ // Move the mouse away from the view so that a result or one-off isn't
+ // inadvertently highlighted. See bug 1659011.
+ EventUtils.synthesizeMouse(
+ gURLBar.inputField,
+ 0,
+ 0,
+ { type: "mousemove" },
+ window
+ );
+});
+
+// Shift clicking with no search string should open search mode, like an
+// unmodified click.
+add_task(async function shift_click_empty() {
+ await searchAndOpenPopup("");
+ let oneOffs = oneOffSearchButtons.getSelectableButtons(true);
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(oneOffs[0], { shiftKey: true });
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: oneOffs[0].engine.name,
+ entry: "oneoff",
+ });
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Shift clicking with a search string should perform a search in the current
+// tab.
+add_task(async function shift_click_search() {
+ await searchAndOpenPopup(SEARCH_STRING);
+ let resultsPromise = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ "http://mochi.test:8888/?terms=foo.bar"
+ );
+ let oneOffs = oneOffSearchButtons.getSelectableButtons(true);
+ EventUtils.synthesizeMouseAtCenter(oneOffs[0], { shiftKey: true });
+ await resultsPromise;
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Pressing Shift+Enter on a one-off with no search string should open search
+// mode, like an unmodified click.
+add_task(async function shift_enter_empty() {
+ await searchAndOpenPopup("");
+ // Alt+Down to select the first one-off.
+ let oneOffs = oneOffSearchButtons.getSelectableButtons(true);
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter", { shiftKey: true });
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: oneOffs[0].engine.name,
+ entry: "oneoff",
+ });
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Pressing Shift+Enter on a one-off with a search string should perform a
+// search in the current tab.
+add_task(async function shift_enter_search() {
+ await searchAndOpenPopup(SEARCH_STRING);
+ let resultsPromise = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ "http://mochi.test:8888/?terms=foo.bar"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ EventUtils.synthesizeKey("KEY_Enter", { shiftKey: true });
+ await resultsPromise;
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Pressing Alt+Enter on a one-off on an "empty" page (e.g. new tab) should open
+// search mode in the current tab.
+add_task(async function alt_enter_emptypage() {
+ await BrowserTestUtils.withNewTab("about:home", async function (browser) {
+ await searchAndOpenPopup(SEARCH_STRING);
+ let oneOffs = oneOffSearchButtons.getSelectableButtons(true);
+ // Alt+Down to select the first one-off.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter", { altKey: true });
+ await searchPromise;
+ Assert.equal(
+ browser,
+ gBrowser.selectedBrowser,
+ "The foreground tab hasn't changed."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: oneOffs[0].engine.name,
+ entry: "oneoff",
+ });
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+// Pressing Alt+Enter on a one-off with no search string and on a "non-empty"
+// page should open search mode in a new foreground tab.
+add_task(async function alt_enter_empty() {
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ await searchAndOpenPopup("");
+ let oneOffs = oneOffSearchButtons.getSelectableButtons(true);
+ // Alt+Down to select the first one-off.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ let tabOpenPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabOpen"
+ );
+ EventUtils.synthesizeKey("KEY_Enter", { altKey: true });
+ await tabOpenPromise;
+ Assert.notEqual(
+ browser,
+ gBrowser.selectedBrowser,
+ "The current foreground tab is new."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: oneOffs[0].engine.name,
+ entry: "oneoff",
+ });
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ Assert.equal(
+ browser,
+ gBrowser.selectedBrowser,
+ "We're back in the original tab."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+// Pressing Alt+Enter on a remote one-off with a search string and on a
+// "non-empty" page should perform a search in a new foreground tab.
+add_task(async function alt_enter_search_remote() {
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ await searchAndOpenPopup(SEARCH_STRING);
+ // Alt+Down to select the first one-off.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ let tabOpenPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "http://mochi.test:8888/?terms=foo.bar",
+ true
+ );
+ EventUtils.synthesizeKey("KEY_Enter", { altKey: true });
+ // This implictly checks the correct page is loaded.
+ let newTab = await tabOpenPromise;
+ Assert.equal(
+ newTab,
+ gBrowser.selectedTab,
+ "The current foreground tab is new."
+ );
+ // Check search mode is not activated in the new tab.
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ BrowserTestUtils.removeTab(newTab);
+ Assert.equal(
+ browser,
+ gBrowser.selectedBrowser,
+ "We're back in the original tab."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+// Pressing Alt+Enter on a local one-off with a search string and on a
+// "non-empty" page should open search mode in a new foreground tab with the
+// search string already populated.
+add_task(async function alt_enter_search_local() {
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ await searchAndOpenPopup(SEARCH_STRING);
+ // Alt+Down to select the first local one-off.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ while (
+ oneOffSearchButtons.selectedButton.id !=
+ "urlbar-engine-one-off-item-bookmarks"
+ ) {
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ }
+ let tabOpenPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabOpen"
+ );
+ EventUtils.synthesizeKey("KEY_Enter", { altKey: true });
+ await tabOpenPromise;
+ Assert.notEqual(
+ browser,
+ gBrowser.selectedBrowser,
+ "The current foreground tab is new."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ });
+ Assert.equal(
+ gURLBar.value,
+ SEARCH_STRING,
+ "The search term was duplicated to the new tab."
+ );
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ Assert.equal(
+ browser,
+ gBrowser.selectedBrowser,
+ "We're back in the original tab."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+// Accel+Clicking a one-off with an empty search string should open search mode
+// in a new background tab.
+add_task(async function accel_click_empty() {
+ await searchAndOpenPopup("");
+ let oneOffs = oneOffSearchButtons.getSelectableButtons(true);
+
+ // We have to listen for the new tab using this brute force method.
+ // about:newtab is preloaded in the background. When about:newtab is opened,
+ // the cached version is shown. Since the page is already loaded,
+ // waitForNewTab does not detect it. It also doesn't fire the TabOpen event.
+ let tabCount = gBrowser.tabs.length;
+ let tabOpenPromise = TestUtils.waitForCondition(
+ () =>
+ gBrowser.tabs.length == tabCount + 1
+ ? gBrowser.tabs[gBrowser.tabs.length - 1]
+ : false,
+ "Waiting for background about:newtab to open."
+ );
+ EventUtils.synthesizeMouseAtCenter(oneOffs[0], { accelKey: true });
+ let newTab = await tabOpenPromise;
+ Assert.notEqual(
+ newTab.linkedBrowser,
+ gBrowser.selectedBrowser,
+ "The foreground tab hasn't changed."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ BrowserTestUtils.switchTab(gBrowser, newTab);
+ // Check the new background tab is already in search mode.
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: oneOffs[0].engine.name,
+ entry: "oneoff",
+ });
+ BrowserTestUtils.removeTab(newTab);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Accel+Clicking a remote one-off with a search string should execute a search
+// in a new background tab.
+add_task(async function accel_click_search_remote() {
+ await searchAndOpenPopup(SEARCH_STRING);
+ let oneOffs = oneOffSearchButtons.getSelectableButtons(true);
+ let tabOpenPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "http://mochi.test:8888/?terms=foo.bar",
+ true
+ );
+ EventUtils.synthesizeMouseAtCenter(oneOffs[0], { accelKey: true });
+ // This implictly checks the correct page is loaded.
+ let newTab = await tabOpenPromise;
+ Assert.notEqual(
+ gBrowser.selectedTab,
+ newTab,
+ "The foreground tab hasn't changed."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ // Switch to the background tab, which is the last tab in gBrowser.tabs.
+ BrowserTestUtils.switchTab(gBrowser, newTab);
+ // Check the new background tab is not search mode.
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ BrowserTestUtils.removeTab(newTab);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Accel+Clicking a local one-off with a search string should open search mode
+// in a new background tab with the search string already populated.
+add_task(async function accel_click_search_local() {
+ await searchAndOpenPopup(SEARCH_STRING);
+ let oneOffs = oneOffSearchButtons.getSelectableButtons(true);
+ let oneOff;
+ for (oneOff of oneOffs) {
+ if (oneOff.id == "urlbar-engine-one-off-item-bookmarks") {
+ break;
+ }
+ }
+ let tabCount = gBrowser.tabs.length;
+ let tabOpenPromise = TestUtils.waitForCondition(
+ () =>
+ gBrowser.tabs.length == tabCount + 1
+ ? gBrowser.tabs[gBrowser.tabs.length - 1]
+ : false,
+ "Waiting for background about:newtab to open."
+ );
+ EventUtils.synthesizeMouseAtCenter(oneOff, { accelKey: true });
+ let newTab = await tabOpenPromise;
+ Assert.notEqual(
+ newTab.linkedBrowser,
+ gBrowser.selectedBrowser,
+ "The foreground tab hasn't changed."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ BrowserTestUtils.switchTab(gBrowser, newTab);
+ // Check the new background tab is already in search mode.
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ });
+ // Check the search string is already populated.
+ Assert.equal(
+ gURLBar.value,
+ SEARCH_STRING,
+ "The search term was duplicated to the new tab."
+ );
+ BrowserTestUtils.removeTab(newTab);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_oneOffs_searchSuggestions.js b/browser/components/urlbar/tests/browser/browser_oneOffs_searchSuggestions.js
new file mode 100644
index 0000000000..ef324b08cd
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_oneOffs_searchSuggestions.js
@@ -0,0 +1,358 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests various actions relating to search suggestions and the one-off buttons.
+ */
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+const TEST_ENGINE2_BASENAME = "searchSuggestionEngine2.xml";
+
+const serverInfo = {
+ scheme: "http",
+ host: "localhost",
+ port: 20709, // Must be identical to what is in searchSuggestionEngine2.xml
+};
+
+var gEngine;
+var gEngine2;
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.searches", true],
+ ["browser.urlbar.maxHistoricalSearchSuggestions", 2],
+ ],
+ });
+ gEngine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ });
+ gEngine2 = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE2_BASENAME,
+ });
+ let oldDefaultEngine = await Services.search.getDefault();
+ await Services.search.moveEngine(gEngine2, 0);
+ await Services.search.moveEngine(gEngine, 0);
+ await Services.search.setDefault(
+ gEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ registerCleanupFunction(async function () {
+ await Services.search.setDefault(
+ oldDefaultEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ });
+});
+
+async function withSuggestions(testFn) {
+ // First run with remote suggestions, and then run with form history.
+ await withSuggestionOnce(false, testFn);
+ await withSuggestionOnce(true, testFn);
+}
+
+async function withSuggestionOnce(useFormHistory, testFn) {
+ if (useFormHistory) {
+ // Add foofoo twice so it's more frecent so it appears first so that the
+ // order of form history results matches the order of remote suggestion
+ // results.
+ await UrlbarTestUtils.formHistory.add(["foofoo", "foofoo", "foobar"]);
+ }
+ await BrowserTestUtils.withNewTab(gBrowser, async () => {
+ let value = "foo";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ fireInputEvent: true,
+ });
+ let index = await UrlbarTestUtils.promiseSuggestionsPresent(window);
+ await assertState({
+ inputValue: value,
+ resultIndex: 0,
+ });
+ await withHttpServer(serverInfo, () => {
+ return testFn(index, useFormHistory);
+ });
+ });
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+}
+
+async function selectSecondSuggestion(index, isFormHistory) {
+ // Down to select the first search suggestion.
+ for (let i = index; i > 0; --i) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ await assertState({
+ inputValue: "foofoo",
+ resultIndex: index,
+ suggestion: {
+ isFormHistory,
+ },
+ });
+
+ // Down to select the next search suggestion.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ await assertState({
+ inputValue: "foobar",
+ resultIndex: index + 1,
+ suggestion: {
+ isFormHistory,
+ },
+ });
+}
+
+// Presses the Return key when a one-off is selected after selecting a search
+// suggestion.
+add_task(async function test_returnAfterSuggestion() {
+ await withSuggestions(async (index, usingFormHistory) => {
+ await selectSecondSuggestion(index, usingFormHistory);
+
+ // Alt+Down to select the first one-off.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ await assertState({
+ inputValue: "foobar",
+ resultIndex: index + 1,
+ oneOffIndex: 0,
+ suggestion: {
+ isFormHistory: usingFormHistory,
+ },
+ });
+
+ let heuristicResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(
+ !BrowserTestUtils.is_visible(heuristicResult.element.action),
+ "The heuristic action should not be visible"
+ );
+
+ let resultsPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await resultsPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: gEngine.name,
+ entry: "oneoff",
+ });
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+ });
+});
+
+// Presses the Return key when a non-default one-off is selected after selecting
+// a search suggestion.
+add_task(async function test_returnAfterSuggestion_nonDefault() {
+ await withSuggestions(async (index, usingFormHistory) => {
+ await selectSecondSuggestion(index, usingFormHistory);
+
+ // Alt+Down twice to select the second one-off.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ await assertState({
+ inputValue: "foobar",
+ resultIndex: index + 1,
+ oneOffIndex: 1,
+ suggestion: {
+ isFormHistory: usingFormHistory,
+ },
+ });
+
+ let resultsPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await resultsPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: gEngine2.name,
+ entry: "oneoff",
+ });
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+ });
+});
+
+// Clicks a one-off engine after selecting a search suggestion.
+add_task(async function test_clickAfterSuggestion() {
+ await withSuggestions(async (index, usingFormHistory) => {
+ await selectSecondSuggestion(index, usingFormHistory);
+
+ let oneOffs =
+ UrlbarTestUtils.getOneOffSearchButtons(window).getSelectableButtons(true);
+ let resultsPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(oneOffs[1], {});
+ await resultsPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: gEngine2.name,
+ entry: "oneoff",
+ });
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+ });
+});
+
+// Clicks a non-default one-off engine after selecting a search suggestion.
+add_task(async function test_clickAfterSuggestion_nonDefault() {
+ await withSuggestions(async (index, usingFormHistory) => {
+ await selectSecondSuggestion(index, usingFormHistory);
+
+ let oneOffs =
+ UrlbarTestUtils.getOneOffSearchButtons(window).getSelectableButtons(true);
+ let resultsPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(oneOffs[1], {});
+ await resultsPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: gEngine2.name,
+ entry: "oneoff",
+ });
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+ });
+});
+
+// Selects a non-default one-off engine and then clicks a search suggestion.
+add_task(async function test_selectOneOffThenSuggestion() {
+ await withSuggestions(async (index, usingFormHistory) => {
+ // Select a non-default one-off engine.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ await assertState({
+ inputValue: "foo",
+ resultIndex: 0,
+ oneOffIndex: 1,
+ });
+
+ let heuristicResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(
+ BrowserTestUtils.is_visible(heuristicResult.element.action),
+ "The heuristic action should be visible because the result is selected"
+ );
+
+ // Now click the second suggestion.
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, index + 1);
+ // Note search history results don't change their engine when the selected
+ // one-off button changes!
+ let resultsPromise = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ usingFormHistory
+ ? `http://mochi.test:8888/?terms=foobar`
+ : `http://localhost:20709/?terms=foobar`
+ );
+ EventUtils.synthesizeMouseAtCenter(result.element.row, {});
+ await resultsPromise;
+ });
+});
+
+add_task(async function overridden_engine_not_reused() {
+ info(
+ "An overridden search suggestion item should not be reused by a search with another engine"
+ );
+ await BrowserTestUtils.withNewTab(gBrowser, async () => {
+ let typedValue = "foo";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typedValue,
+ fireInputEvent: true,
+ });
+ let index = await UrlbarTestUtils.promiseSuggestionsPresent(window);
+ // Down to select the first search suggestion.
+ for (let i = index; i > 0; --i) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ await assertState({
+ inputValue: "foofoo",
+ resultIndex: index,
+ suggestion: {
+ isFormHistory: false,
+ },
+ });
+
+ // ALT+Down to select the second search engine.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ await assertState({
+ inputValue: "foofoo",
+ resultIndex: index,
+ oneOffIndex: 1,
+ suggestion: {
+ isFormHistory: false,
+ },
+ });
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ let label = result.displayed.action;
+ // Run again the query, check the label has been replaced.
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typedValue,
+ fireInputEvent: true,
+ });
+ index = await UrlbarTestUtils.promiseSuggestionsPresent(window);
+ await assertState({
+ inputValue: "foo",
+ resultIndex: 0,
+ });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ Assert.notEqual(
+ result.displayed.action,
+ label,
+ "The label should have been updated"
+ );
+ });
+});
+
+async function assertState({
+ resultIndex,
+ inputValue,
+ oneOffIndex = -1,
+ suggestion = null,
+}) {
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ resultIndex,
+ "Expected result should be selected"
+ );
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtons(window).selectedButtonIndex,
+ oneOffIndex,
+ "Expected one-off should be selected"
+ );
+ if (inputValue !== undefined) {
+ Assert.equal(gURLBar.value, inputValue, "Expected input value");
+ }
+
+ if (suggestion) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ resultIndex
+ );
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Result type should be SEARCH"
+ );
+ if (suggestion.isFormHistory) {
+ Assert.equal(
+ result.source,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ "Result source should be HISTORY"
+ );
+ } else {
+ Assert.equal(
+ result.source,
+ UrlbarUtils.RESULT_SOURCE.SEARCH,
+ "Result source should be SEARCH"
+ );
+ }
+ Assert.equal(
+ typeof result.searchParams.suggestion,
+ "string",
+ "Result should have a suggestion"
+ );
+ Assert.equal(
+ result.searchParams.suggestion,
+ suggestion.value || inputValue,
+ "Result should have the expected suggestion"
+ );
+ }
+}
diff --git a/browser/components/urlbar/tests/browser/browser_oneOffs_settings.js b/browser/components/urlbar/tests/browser/browser_oneOffs_settings.js
new file mode 100644
index 0000000000..b4b1e7006e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_oneOffs_settings.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This tests that the settings button in the one-off buttons display correctly
+ * loads the search preferences.
+ */
+
+let gMaxResults;
+
+add_setup(async function () {
+ gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ });
+
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+
+ let visits = [];
+ for (let i = 0; i < gMaxResults; i++) {
+ visits.push({
+ uri: makeURI("http://example.com/browser_urlbarOneOffs.js/?" + i),
+ // TYPED so that the visit shows up when the urlbar's drop-down arrow is
+ // pressed.
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ });
+ }
+ await PlacesTestUtils.addVisits(visits);
+});
+
+async function selectSettings(win, activateFn) {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: win.gBrowser, url: "about:blank" },
+ async browser => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "example.com",
+ });
+ await UrlbarTestUtils.waitForAutocompleteResultAt(win, gMaxResults - 1);
+
+ await UrlbarTestUtils.promisePopupClose(win, async () => {
+ let prefPaneLoaded = TestUtils.topicObserved(
+ "sync-pane-loaded",
+ () => true
+ );
+
+ activateFn();
+
+ await prefPaneLoaded;
+ });
+
+ Assert.equal(
+ win.gBrowser.contentWindow.history.state,
+ "paneSearch",
+ "Should have opened the search preferences pane"
+ );
+ }
+ );
+}
+
+add_task(async function test_open_settings_with_enter() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ await selectSettings(win, () => {
+ EventUtils.synthesizeKey("KEY_ArrowUp", {}, win);
+
+ Assert.ok(
+ UrlbarTestUtils.getOneOffSearchButtons(
+ win
+ ).selectedButton.classList.contains("search-setting-button"),
+ "Should have selected the settings button"
+ );
+
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+ });
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_open_settings_with_click() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ await selectSettings(win, () => {
+ UrlbarTestUtils.getOneOffSearchButtons(win).settingsButton.click();
+ });
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_pasteAndGo.js b/browser/components/urlbar/tests/browser/browser_pasteAndGo.js
new file mode 100644
index 0000000000..8d2a27afc3
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_pasteAndGo.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests for the paste and go functionality of the urlbar.
+ */
+
+add_task(async function () {
+ const kURLs = [
+ "http://example.com/1",
+ "http://example.org/2\n",
+ "http://\nexample.com/3\n",
+ ];
+ for (let url of kURLs) {
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ gURLBar.focus();
+
+ await SimpleTest.promiseClipboardChange(url, () => {
+ clipboardHelper.copyString(url);
+ });
+ let menuitem = await promiseContextualMenuitem("paste-and-go");
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ url.replace(/\n/g, "")
+ );
+ menuitem.closest("menupopup").activateItem(menuitem);
+ // Using toSource in order to get the newlines escaped:
+ info("Paste and go, loading " + url.toSource());
+ await browserLoadedPromise;
+ ok(true, "Successfully loaded " + url);
+ });
+ }
+});
+
+add_task(async function test_invisible_char() {
+ const url = "http://example.com/4\u2028";
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ gURLBar.focus();
+ await SimpleTest.promiseClipboardChange(url, () => {
+ clipboardHelper.copyString(url);
+ });
+ let menuitem = await promiseContextualMenuitem("paste-and-go");
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ url.replace(/\u2028/g, "")
+ );
+ menuitem.closest("menupopup").activateItem(menuitem);
+ // Using toSource in order to get the newlines escaped:
+ info("Paste and go, loading " + url.toSource());
+ await browserLoadedPromise;
+ ok(true, "Successfully loaded " + url);
+ });
+});
+
+add_task(async function test_with_input_and_results() {
+ // Test paste and go When there's some input and the results pane is open.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ const url = "http://example.com/";
+ await SimpleTest.promiseClipboardChange(url, () => {
+ clipboardHelper.copyString(url);
+ });
+ let menuitem = await promiseContextualMenuitem("paste-and-go");
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ url
+ );
+ menuitem.closest("menupopup").activateItem(menuitem);
+ // Using toSource in order to get the newlines escaped:
+ info("Paste and go, loading " + url.toSource());
+ await browserLoadedPromise;
+ ok(true, "Successfully loaded " + url);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_paste_multi_lines.js b/browser/components/urlbar/tests/browser/browser_paste_multi_lines.js
new file mode 100644
index 0000000000..8c4be18a7b
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_paste_multi_lines.js
@@ -0,0 +1,239 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test handling whitespace chars such as "\n”.
+
+const TEST_DATA = [
+ {
+ input: "this is a\ntest",
+ expected: {
+ urlbar: "this is a test",
+ autocomplete: "this is a test",
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ },
+ },
+ {
+ input: "this is a\n\ttest",
+ expected: {
+ urlbar: "this is a test",
+ autocomplete: "this is a test",
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ },
+ },
+ {
+ input: "http:\n//\nexample.\ncom",
+ expected: {
+ urlbar: "http://example.com",
+ autocomplete: "http://example.com/",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "htp:example.\ncom",
+ expected: {
+ urlbar: "htp:example.com",
+ autocomplete: "http://example.com/",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "example.\ncom",
+ expected: {
+ urlbar: "example.com",
+ autocomplete: "http://example.com/",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "http://example.com/foo bar/",
+ expected: {
+ urlbar: "http://example.com/foo bar/",
+ autocomplete: "http://example.com/foo bar/",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "http://exam\nple.com/foo bar/",
+ expected: {
+ urlbar: "http://example.com/foo bar/",
+ autocomplete: "http://example.com/foo bar/",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "javasc\nript:\nalert(1)",
+ expected: {
+ urlbar: "alert(1)",
+ autocomplete: "alert(1)",
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ },
+ },
+ {
+ input: "a\nb\nc",
+ expected: {
+ urlbar: "a b c",
+ autocomplete: "a b c",
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ },
+ },
+ {
+ input: "lo\ncal\nhost",
+ expected: {
+ urlbar: "localhost",
+ autocomplete: "http://localhost/",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "data:text/html,<iframe\n src='example\n.com'>\n</iframe>",
+ expected: {
+ urlbar: "data:text/html,<iframe src='example .com'> </iframe>",
+ autocomplete: "data:text/html,<iframe src='example .com'> </iframe>",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "data:,123\n4 5\n6",
+ expected: {
+ urlbar: "data:,123 4 5 6",
+ autocomplete: "data:,123 4 5 6",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "data:text/html;base64,123\n4 5\n6",
+ expected: {
+ urlbar: "data:text/html;base64,1234 56",
+ autocomplete: "data:text/html;base64,123456",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "http://example.com\n",
+ expected: {
+ urlbar: "http://example.com",
+ autocomplete: "http://example.com/",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "http://example.com\r",
+ expected: {
+ urlbar: "http://example.com",
+ autocomplete: "http://example.com/",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "http://ex\ra\nmp\r\nle.com\r\n",
+ expected: {
+ urlbar: "http://example.com",
+ autocomplete: "http://example.com/",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "http://example.com/titled",
+ expected: {
+ urlbar: "http://example.com/titled",
+ autocomplete: "example title",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "127.0.0.1\r",
+ expected: {
+ urlbar: "127.0.0.1",
+ autocomplete: "http://127.0.0.1/",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ },
+ },
+ {
+ input: "\r\n\r\n\r\n\r\n\r\n",
+ expected: {
+ urlbar: "",
+ autocomplete: "",
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ },
+ },
+];
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // There are cases that URLBar loses focus before assertion of this test.
+ // In that case, this test will be failed since the result is closed
+ // before it. We use this pref so that keep the result even if lose focus.
+ ["ui.popup.disable_autohide", true],
+ ],
+ });
+
+ await PlacesUtils.history.clear();
+ await PlacesTestUtils.addVisits({
+ uri: "http://example.com/titled",
+ title: "example title",
+ });
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ SpecialPowers.clipboardCopyString("");
+ });
+});
+
+add_task(async function test_paste_onto_urlbar() {
+ for (const { input, expected } of TEST_DATA) {
+ gURLBar.value = "";
+ gURLBar.focus();
+
+ await paste(input);
+ await assertResult(expected);
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ }
+});
+
+add_task(async function test_paste_after_opening_autocomplete_panel() {
+ for (const { input, expected } of TEST_DATA) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+
+ await paste(input);
+ await assertResult(expected);
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ }
+});
+
+async function assertResult(expected) {
+ Assert.equal(gURLBar.value, expected.urlbar, "Pasted value is correct");
+
+ const result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ result.title,
+ expected.autocomplete,
+ "Title of autocomplete is correct"
+ );
+ Assert.equal(result.type, expected.type, "Type of autocomplete is correct");
+
+ if (gURLBar.value) {
+ Assert.equal(gURLBar.getAttribute("usertyping"), "true");
+ Assert.ok(BrowserTestUtils.is_visible(gURLBar.goButton));
+ } else {
+ Assert.ok(!gURLBar.hasAttribute("usertyping"));
+ Assert.ok(BrowserTestUtils.is_hidden(gURLBar.goButton));
+ }
+}
+
+async function paste(input) {
+ await SimpleTest.promiseClipboardChange(input.replace(/\r\n?/g, "\n"), () => {
+ clipboardHelper.copyString(input);
+ });
+
+ document.commandDispatcher
+ .getControllerForCommand("cmd_paste")
+ .doCommand("cmd_paste");
+}
diff --git a/browser/components/urlbar/tests/browser/browser_paste_then_focus.js b/browser/components/urlbar/tests/browser/browser_paste_then_focus.js
new file mode 100644
index 0000000000..23d603fd80
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_paste_then_focus.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the urlbar value when focusing after pasting value.
+
+const TEST_DATA = [
+ {
+ input: "this is a\ntest",
+ expected: "this is a test",
+ },
+ {
+ input: "http:\n//\nexample.\ncom",
+ expected: "http://example.com",
+ },
+ {
+ input: "javasc\nript:\nalert(1)",
+ expected: "alert(1)",
+ },
+ {
+ input: "javascript:alert(1)",
+ expected: "alert(1)",
+ },
+ {
+ input: "test",
+ expected: "test",
+ },
+];
+
+add_task(async function test_paste_then_focus() {
+ for (const testData of TEST_DATA) {
+ gURLBar.value = "";
+ gURLBar.focus();
+
+ EventUtils.synthesizeKey("x");
+ gURLBar.select();
+
+ await paste(testData.input);
+
+ gURLBar.blur();
+ gURLBar.focus();
+
+ Assert.equal(
+ gURLBar.value,
+ testData.expected,
+ "Value on urlbar is correct"
+ );
+ }
+});
+
+async function paste(input) {
+ await SimpleTest.promiseClipboardChange(input, () => {
+ clipboardHelper.copyString(input);
+ });
+
+ document.commandDispatcher
+ .getControllerForCommand("cmd_paste")
+ .doCommand("cmd_paste");
+}
diff --git a/browser/components/urlbar/tests/browser/browser_paste_then_switch_tab.js b/browser/components/urlbar/tests/browser/browser_paste_then_switch_tab.js
new file mode 100644
index 0000000000..883b128c60
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_paste_then_switch_tab.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the urlbar value when switching tab after pasting value.
+
+const TEST_DATA = [
+ {
+ input: "this is a\ntest",
+ expected: "this is a test",
+ },
+ {
+ input: "https:\n//\nexample.\ncom",
+ expected: "https://example.com",
+ },
+ {
+ input: "http:\n//\nexample.\ncom",
+ expected: "example.com",
+ },
+ {
+ input: "javasc\nript:\nalert(1)",
+ expected: "alert(1)",
+ },
+ {
+ input: "javascript:alert(1)",
+ expected: "alert(1)",
+ },
+ {
+ // Has U+3000 IDEOGRAPHIC SPACE.
+ input: "Mozilla Firefox",
+ expected: "Mozilla Firefox",
+ },
+ {
+ input: "test",
+ expected: "test",
+ },
+];
+
+add_task(async function test_paste_then_switch_tab() {
+ for (const testData of TEST_DATA) {
+ gURLBar.focus();
+ gURLBar.select();
+
+ await paste(testData.input);
+
+ // Switch to a new tab.
+ const originalTab = gBrowser.selectedTab;
+ const newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await BrowserTestUtils.waitForCondition(() => !gURLBar.value);
+
+ // Switch back to original tab.
+ gBrowser.selectedTab = originalTab;
+
+ Assert.equal(
+ gURLBar.value,
+ testData.expected,
+ "Value on urlbar is correct"
+ );
+
+ BrowserTestUtils.removeTab(newTab);
+ }
+});
+
+async function paste(input) {
+ await SimpleTest.promiseClipboardChange(input, () => {
+ clipboardHelper.copyString(input);
+ });
+
+ document.commandDispatcher
+ .getControllerForCommand("cmd_paste")
+ .doCommand("cmd_paste");
+}
diff --git a/browser/components/urlbar/tests/browser/browser_percent_encoded.js b/browser/components/urlbar/tests/browser/browser_percent_encoded.js
new file mode 100644
index 0000000000..c334c03a09
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_percent_encoded.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that searching history works for both encoded or decoded strings.
+
+add_task(async function test() {
+ const decoded = "日本";
+ const TEST_URL = TEST_BASE_URL + "?" + decoded;
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+
+ // Visit url in a new tab, going through normal urlbar workflow.
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+ let promise = PlacesTestUtils.waitForNotification("page-visited", visits => {
+ Assert.equal(
+ visits.length,
+ 1,
+ "Was notified for the right number of visits."
+ );
+ let { url, transitionType } = visits[0];
+ return (
+ url == encodeURI(TEST_URL) &&
+ transitionType == PlacesUtils.history.TRANSITIONS.TYPED
+ );
+ });
+ gURLBar.focus();
+ gURLBar.value = TEST_URL;
+ info("Visiting url");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await promise;
+ gBrowser.removeCurrentTab({ skipPermitUnload: true });
+
+ info("Search for the decoded string.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: decoded,
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "Check number of results"
+ );
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, encodeURI(TEST_URL), "Check result url");
+
+ info("Search for the encoded string.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: encodeURIComponent(decoded),
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "Check number of results"
+ );
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, encodeURI(TEST_URL), "Check result url");
+});
diff --git a/browser/components/urlbar/tests/browser/browser_placeholder.js b/browser/components/urlbar/tests/browser/browser_placeholder.js
new file mode 100644
index 0000000000..fbf0a5007c
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_placeholder.js
@@ -0,0 +1,412 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test ensures the placeholder is set correctly for different search
+ * engines.
+ */
+
+"use strict";
+
+var originalEngine, extraEngine, extraPrivateEngine, expectedString;
+var tabs = [];
+
+var noEngineString;
+
+add_setup(async function () {
+ originalEngine = await Services.search.getDefault();
+ [noEngineString, expectedString] = (
+ await document.l10n.formatMessages([
+ { id: "urlbar-placeholder" },
+ {
+ id: "urlbar-placeholder-with-name",
+ args: { name: originalEngine.name },
+ },
+ ])
+ ).map(msg => msg.attributes[0].value);
+
+ let rootUrl = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://mochi.test:8888/"
+ );
+ await SearchTestUtils.installSearchExtension({
+ name: "extraEngine",
+ search_url: "https://mochi.test:8888/",
+ suggest_url: `${rootUrl}/searchSuggestionEngine.sjs`,
+ });
+ extraEngine = Services.search.getEngineByName("extraEngine");
+ await SearchTestUtils.installSearchExtension({
+ name: "extraPrivateEngine",
+ search_url: "https://mochi.test:8888/",
+ suggest_url: `${rootUrl}/searchSuggestionEngine.sjs`,
+ });
+ extraPrivateEngine = Services.search.getEngineByName("extraPrivateEngine");
+
+ // Force display of a tab with a URL bar, to clear out any possible placeholder
+ // initialization listeners that happen on startup.
+ let urlTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+ BrowserTestUtils.removeTab(urlTab);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", true],
+ ["browser.search.separatePrivateDefault", false],
+ ["browser.urlbar.suggest.quickactions", false],
+ ],
+ });
+
+ registerCleanupFunction(async () => {
+ await Services.search.setDefault(
+ originalEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ for (let tab of tabs) {
+ BrowserTestUtils.removeTab(tab);
+ }
+ });
+});
+
+add_task(async function test_change_default_engine_updates_placeholder() {
+ tabs.push(await BrowserTestUtils.openNewForegroundTab(gBrowser));
+
+ await Services.search.setDefault(
+ extraEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ await TestUtils.waitForCondition(
+ () => gURLBar.placeholder == noEngineString,
+ "The placeholder should match the default placeholder for non-built-in engines."
+ );
+ Assert.equal(gURLBar.placeholder, noEngineString);
+
+ await Services.search.setDefault(
+ originalEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ await TestUtils.waitForCondition(
+ () => gURLBar.placeholder == expectedString,
+ "The placeholder should include the engine name for built-in engines."
+ );
+ Assert.equal(gURLBar.placeholder, expectedString);
+});
+
+add_task(async function test_delayed_update_placeholder() {
+ // We remove the change of engine listener here as that is set so that
+ // if the engine is changed by the user then the placeholder is always updated
+ // straight away. As we want to test the delay update here, we remove the
+ // listener and call the placeholder update manually with the delay flag.
+ Services.obs.removeObserver(BrowserSearch, "browser-search-engine-modified");
+
+ // Since we can't easily test for startup changes, we'll at least test the delay
+ // of update for the placeholder works.
+ let urlTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+ tabs.push(urlTab);
+
+ // Open a tab with a blank URL bar.
+ let blankTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ tabs.push(blankTab);
+
+ await Services.search.setDefault(
+ extraEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ // Pretend we've "initialized".
+ BrowserSearch._updateURLBarPlaceholder(extraEngine.name, false, true);
+
+ Assert.equal(
+ gURLBar.placeholder,
+ expectedString,
+ "Placeholder should be unchanged."
+ );
+
+ // Now switch to a tab with something in the URL Bar.
+ await BrowserTestUtils.switchTab(gBrowser, urlTab);
+
+ await TestUtils.waitForCondition(
+ () => gURLBar.placeholder == noEngineString,
+ "The placeholder should have updated in the background."
+ );
+
+ // Do it the other way to check both named engine and fallback code paths.
+ await BrowserTestUtils.switchTab(gBrowser, blankTab);
+
+ await Services.search.setDefault(
+ originalEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ BrowserSearch._updateURLBarPlaceholder(originalEngine.name, false, true);
+
+ Assert.equal(
+ gURLBar.placeholder,
+ noEngineString,
+ "Placeholder should be unchanged."
+ );
+ Assert.deepEqual(
+ document.l10n.getAttributes(gURLBar.inputField),
+ { id: "urlbar-placeholder", args: null },
+ "Placeholder data should be unchanged."
+ );
+
+ await BrowserTestUtils.switchTab(gBrowser, urlTab);
+
+ await TestUtils.waitForCondition(
+ () => gURLBar.placeholder == expectedString,
+ "The placeholder should include the engine name for built-in engines."
+ );
+
+ // Now check when we have a URL displayed, the placeholder is updated straight away.
+ BrowserSearch._updateURLBarPlaceholder(extraEngine.name, false);
+
+ await TestUtils.waitForCondition(
+ () => gURLBar.placeholder == noEngineString,
+ "The placeholder should go back to the default"
+ );
+ Assert.equal(
+ gURLBar.placeholder,
+ noEngineString,
+ "Placeholder should be the default."
+ );
+
+ Services.obs.addObserver(BrowserSearch, "browser-search-engine-modified");
+});
+
+add_task(async function test_private_window_no_separate_engine() {
+ const win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+ await Services.search.setDefault(
+ extraEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ await TestUtils.waitForCondition(
+ () => win.gURLBar.placeholder == noEngineString,
+ "The placeholder should match the default placeholder for non-built-in engines."
+ );
+ Assert.equal(win.gURLBar.placeholder, noEngineString);
+
+ await Services.search.setDefault(
+ originalEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ await TestUtils.waitForCondition(
+ () => win.gURLBar.placeholder == expectedString,
+ "The placeholder should include the engine name for built-in engines."
+ );
+ Assert.equal(win.gURLBar.placeholder, expectedString);
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_private_window_separate_engine() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.separatePrivateDefault", true]],
+ });
+ const originalPrivateEngine = await Services.search.getDefaultPrivate();
+ registerCleanupFunction(async () => {
+ await Services.search.setDefaultPrivate(
+ originalPrivateEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ });
+
+ const win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+ // Keep the normal default as a different string to the private, so that we
+ // can be sure we're testing the right thing.
+ await Services.search.setDefault(
+ originalEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await Services.search.setDefaultPrivate(
+ extraPrivateEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ await TestUtils.waitForCondition(
+ () => win.gURLBar.placeholder == noEngineString,
+ "The placeholder should match the default placeholder for non-built-in engines."
+ );
+ Assert.equal(win.gURLBar.placeholder, noEngineString);
+
+ await Services.search.setDefault(
+ extraEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await Services.search.setDefaultPrivate(
+ originalEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ await TestUtils.waitForCondition(
+ () => win.gURLBar.placeholder == expectedString,
+ "The placeholder should include the engine name for built-in engines."
+ );
+ Assert.equal(win.gURLBar.placeholder, expectedString);
+
+ await BrowserTestUtils.closeWindow(win);
+
+ // Verify that the placeholder for private windows is updated even when no
+ // private window is visible (https://bugzilla.mozilla.org/1792816).
+ await Services.search.setDefault(
+ originalEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await Services.search.setDefaultPrivate(
+ extraPrivateEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ const win2 = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+ Assert.equal(win2.gURLBar.placeholder, noEngineString);
+ await BrowserTestUtils.closeWindow(win2);
+
+ // And ensure this doesn't affect the placeholder for non private windows.
+ tabs.push(await BrowserTestUtils.openNewForegroundTab(gBrowser));
+ Assert.equal(win.gURLBar.placeholder, expectedString);
+});
+
+add_task(async function test_search_mode_engine_web() {
+ // Add our test engine to WEB_ENGINE_NAMES so that it's recognized as a web
+ // engine.
+ SearchUtils.GENERAL_SEARCH_ENGINE_IDS.add(
+ extraEngine.wrappedJSObject._extensionID
+ );
+
+ await doSearchModeTest(
+ {
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ engineName: extraEngine.name,
+ },
+ {
+ id: "urlbar-placeholder-search-mode-web-2",
+ args: { name: extraEngine.name },
+ }
+ );
+
+ SearchUtils.GENERAL_SEARCH_ENGINE_IDS.delete(
+ extraEngine.wrappedJSObject._extensionID
+ );
+});
+
+add_task(async function test_search_mode_engine_other() {
+ await doSearchModeTest(
+ { engineName: extraEngine.name },
+ {
+ id: "urlbar-placeholder-search-mode-other-engine",
+ args: { name: extraEngine.name },
+ }
+ );
+});
+
+add_task(async function test_search_mode_bookmarks() {
+ await doSearchModeTest(
+ { source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS },
+ { id: "urlbar-placeholder-search-mode-other-bookmarks", args: null }
+ );
+});
+
+add_task(async function test_search_mode_tabs() {
+ await doSearchModeTest(
+ { source: UrlbarUtils.RESULT_SOURCE.TABS },
+ { id: "urlbar-placeholder-search-mode-other-tabs", args: null }
+ );
+});
+
+add_task(async function test_search_mode_history() {
+ await doSearchModeTest(
+ { source: UrlbarUtils.RESULT_SOURCE.HISTORY },
+ { id: "urlbar-placeholder-search-mode-other-history", args: null }
+ );
+});
+
+add_task(async function test_change_default_engine_updates_placeholder() {
+ tabs.push(await BrowserTestUtils.openNewForegroundTab(gBrowser));
+
+ info(`Set engine to ${extraEngine.name}`);
+ await Services.search.setDefault(
+ extraEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await TestUtils.waitForCondition(
+ () => gURLBar.placeholder == noEngineString,
+ "The placeholder should match the default placeholder for non-built-in engines."
+ );
+ Assert.equal(gURLBar.placeholder, noEngineString);
+
+ info(`Set engine to ${originalEngine.name}`);
+ await Services.search.setDefault(
+ originalEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await TestUtils.waitForCondition(
+ () => gURLBar.placeholder == expectedString,
+ "The placeholder should include the engine name for built-in engines."
+ );
+
+ // Simulate the placeholder not having changed due to the delayed update
+ // on startup.
+ BrowserSearch._setURLBarPlaceholder("");
+ await TestUtils.waitForCondition(
+ () => gURLBar.placeholder == noEngineString,
+ "The placeholder should have been reset."
+ );
+
+ info("Show search engine removal info bar");
+ BrowserSearch.removalOfSearchEngineNotificationBox(
+ extraEngine.name,
+ originalEngine.name
+ );
+ const notificationBox = gNotificationBox.getNotificationWithValue(
+ "search-engine-removal"
+ );
+ Assert.ok(notificationBox, "Search engine removal should be shown.");
+
+ await TestUtils.waitForCondition(
+ () => gURLBar.placeholder == expectedString,
+ "The placeholder should include the engine name for built-in engines."
+ );
+
+ Assert.equal(gURLBar.placeholder, expectedString);
+
+ notificationBox.close();
+});
+
+/**
+ * Opens the view, clicks a one-off button to enter search mode, and asserts
+ * that the placeholder is corrrect.
+ *
+ * @param {object} expectedSearchMode
+ * The expected search mode object for the one-off.
+ * @param {object} expectedPlaceholderL10n
+ * The expected l10n object for the one-off.
+ */
+async function doSearchModeTest(expectedSearchMode, expectedPlaceholderL10n) {
+ // Click the urlbar to open the top-sites view.
+ if (gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+
+ // Enter search mode.
+ await UrlbarTestUtils.enterSearchMode(window, expectedSearchMode);
+
+ // Check the placeholder.
+ Assert.deepEqual(
+ document.l10n.getAttributes(gURLBar.inputField),
+ expectedPlaceholderL10n,
+ "Placeholder has expected l10n"
+ );
+
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+}
diff --git a/browser/components/urlbar/tests/browser/browser_populateAfterPushState.js b/browser/components/urlbar/tests/browser/browser_populateAfterPushState.js
new file mode 100644
index 0000000000..8d383092fe
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_populateAfterPushState.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* When a user clears the URL bar, and then the page pushes state, we should
+ * re-fill the URL bar so it doesn't remain empty indefinitely. See bug 1441039.
+ * For normal loads, this happens automatically because a non-same-document state
+ * change takes place.
+ */
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ TEST_BASE_URL + "dummy_page.html",
+ async function (browser) {
+ gURLBar.value = "";
+
+ let locationChangePromise = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ TEST_BASE_URL + "dummy_page2.html"
+ );
+ await SpecialPowers.spawn(browser, [], function () {
+ content.history.pushState({}, "Page 2", "dummy_page2.html");
+ });
+ await locationChangePromise;
+ is(
+ gURLBar.value,
+ TEST_BASE_URL + "dummy_page2.html",
+ "Should have updated the URL bar."
+ );
+ }
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_primary_selection_safe_on_new_tab.js b/browser/components/urlbar/tests/browser/browser_primary_selection_safe_on_new_tab.js
new file mode 100644
index 0000000000..941c44441d
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_primary_selection_safe_on_new_tab.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Verify that the primary selection is unaffected by opening a new tab.
+ *
+ * The steps here follow STR for regression
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1457355.
+ */
+
+"use strict";
+
+let tabs = [];
+let supportsPrimary = Services.clipboard.isClipboardTypeSupported(
+ Services.clipboard.kSelectionClipboard
+);
+const NON_EMPTY_URL = "data:text/html,Hello";
+const TEXT_FOR_PRIMARY = "Text for PRIMARY selection";
+
+add_task(async function () {
+ tabs.push(
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, NON_EMPTY_URL)
+ );
+
+ // Bug 1457355 reproduced only when the url had a non-empty selection.
+ gURLBar.select();
+ Assert.equal(gURLBar.inputField.selectionStart, 0);
+ Assert.equal(
+ gURLBar.inputField.selectionEnd,
+ gURLBar.inputField.value.length
+ );
+
+ if (supportsPrimary) {
+ clipboardHelper.copyStringToClipboard(
+ TEXT_FOR_PRIMARY,
+ Services.clipboard.kSelectionClipboard
+ );
+ }
+
+ tabs.push(
+ await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: () => {
+ // Simulate tab open from user input such as keyboard shortcut or new
+ // tab button.
+ let userInput = window.windowUtils.setHandlingUserInput(true);
+ try {
+ BrowserOpenTab();
+ } finally {
+ userInput.destruct();
+ }
+ },
+ waitForLoad: false,
+ })
+ );
+
+ if (!supportsPrimary) {
+ info("Primary selection not supported. Skipping assertion.");
+ return;
+ }
+
+ let primaryAsText = SpecialPowers.getClipboardData(
+ "text/plain",
+ SpecialPowers.Ci.nsIClipboard.kSelectionClipboard
+ );
+ Assert.equal(primaryAsText, TEXT_FOR_PRIMARY);
+});
+
+registerCleanupFunction(() => {
+ for (let tab of tabs) {
+ BrowserTestUtils.removeTab(tab);
+ }
+});
diff --git a/browser/components/urlbar/tests/browser/browser_privateBrowsingWindowChange.js b/browser/components/urlbar/tests/browser/browser_privateBrowsingWindowChange.js
new file mode 100644
index 0000000000..eeeda93687
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_privateBrowsingWindowChange.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test that when opening a private browsing window and typing in it before
+ * about:privatebrowsing loads, we don't clear the URL bar.
+ */
+add_task(async function () {
+ let urlbarTestValue = "Mary had a little lamb";
+ let win = OpenBrowserWindow({ private: true });
+ registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
+ await BrowserTestUtils.waitForEvent(win, "load");
+ let promise = new Promise(resolve => {
+ let wpl = {
+ onLocationChange(aWebProgress, aRequest, aLocation) {
+ if (aLocation && aLocation.spec == "about:privatebrowsing") {
+ win.gBrowser.removeProgressListener(wpl);
+ resolve();
+ }
+ },
+ };
+ win.gBrowser.addProgressListener(wpl);
+ });
+ Assert.notEqual(
+ win.gBrowser.selectedBrowser.currentURI.spec,
+ "about:privatebrowsing",
+ "Check privatebrowsing page has not been loaded yet"
+ );
+ info("Search in urlbar");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: urlbarTestValue,
+ fireInputEvent: true,
+ });
+ info("waiting for about:privatebrowsing load");
+ await promise;
+
+ let urlbar = win.gURLBar;
+ is(
+ urlbar.value,
+ urlbarTestValue,
+ "URL bar value should be the same once about:privatebrowsing has loaded"
+ );
+ is(
+ win.gBrowser.selectedBrowser.userTypedValue,
+ urlbarTestValue,
+ "User typed value should be the same once about:privatebrowsing has loaded"
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_queryContextCache.js b/browser/components/urlbar/tests/browser/browser_queryContextCache.js
new file mode 100644
index 0000000000..758043233d
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_queryContextCache.js
@@ -0,0 +1,482 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests the view's QueryContextCache. When the view opens and a context is
+// cached for the search, the view should *synchronously* open and update.
+
+"use strict";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ UrlbarProviderTopSites: "resource:///modules/UrlbarProviderTopSites.sys.mjs",
+});
+
+const TEST_URLS = [];
+const TEST_URLS_COUNT = 5;
+const TOP_SITES_VISIT_COUNT = 5;
+const SEARCH_STRING = "example";
+
+// Allow more time for Mac machines so they don't time out in verify mode.
+if (AppConstants.platform == "macosx") {
+ requestLongerTimeout(3);
+}
+
+add_setup(async function () {
+ // Clear history and bookmarks to make sure the URLs we add below are truly
+ // the top sites. If any existing history or bookmarks were the top sites,
+ // which is likely but not guaranteed, one or more "newtab-top-sites-changed"
+ // notifications will be sent, potentially interfering with the rest of the
+ // test. Waiting for Places updates to finish and then an extra tick should be
+ // enough to make sure no more notfications occur.
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesTestUtils.promiseAsyncUpdates();
+ await TestUtils.waitForTick();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.quickactions", false]],
+ });
+
+ // Add some URLs to populate both history and top sites. Each URL needs to
+ // match `SEARCH_STRING`.
+ for (let i = 0; i < TEST_URLS_COUNT; i++) {
+ let url = `https://${i}.example.com/${SEARCH_STRING}`;
+ TEST_URLS.unshift(url);
+ // Each URL needs to be added several times to boost its frecency enough to
+ // qualify as a top site.
+ for (let j = 0; j < TOP_SITES_VISIT_COUNT; j++) {
+ await PlacesTestUtils.addVisits(url);
+ }
+ }
+ await updateTopSitesAndAwaitChanged(TEST_URLS_COUNT);
+
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+add_task(async function search() {
+ await withNewBrowserWindow(async win => {
+ // Do a search and then close the view.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: SEARCH_STRING,
+ });
+ await UrlbarTestUtils.promisePopupClose(win);
+
+ // Open the view. It should open synchronously and the cached search context
+ // should be used.
+ await openViewAndAssertCached({
+ win,
+ searchString: SEARCH_STRING,
+ cached: true,
+ });
+ });
+});
+
+add_task(async function topSites_simple() {
+ await withNewBrowserWindow(async win => {
+ // Open the view to show top sites and then close it.
+ await openViewAndAssertCached({ win, cached: false });
+
+ // Open the view again. It should open synchronously and the cached
+ // top-sites context should be used.
+ await openViewAndAssertCached({ win, cached: true });
+ });
+});
+
+add_task(async function topSites_nonEmptySearch() {
+ await withNewBrowserWindow(async win => {
+ // Open the view to show top sites and then close it.
+ await openViewAndAssertCached({ win, cached: false });
+
+ // Do a search, close the view, and revert the input.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "test",
+ });
+ await UrlbarTestUtils.promisePopupClose(win);
+ win.gURLBar.handleRevert();
+
+ // Open the view. It should open synchronously and the cached top-sites
+ // context should be used.
+ await openViewAndAssertCached({ win, cached: true });
+ });
+});
+
+add_task(async function topSites_otherEmptySearch() {
+ await withNewBrowserWindow(async win => {
+ // Open the view to show top sites and then close it.
+ await openViewAndAssertCached({ win, cached: false });
+
+ // Enter search mode with an empty search string (by pressing accel+K),
+ // starting a new search. The view should *not* open synchronously and the
+ // cached top-sites context should not be used.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+ EventUtils.synthesizeKey("k", { accelKey: true }, win);
+ Assert.ok(!win.gURLBar.view.isOpen, "View is not open");
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(win, {
+ engineName: Services.search.defaultEngine.name,
+ isGeneralPurposeEngine: true,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ isPreview: false,
+ entry: "shortcut",
+ });
+
+ // Close the view and revert the input.
+ await UrlbarTestUtils.promisePopupClose(win);
+ win.gURLBar.handleRevert();
+ await UrlbarTestUtils.assertSearchMode(win, null);
+
+ // Open the view. It should open synchronously and the cached top-sites
+ // context should be used.
+ await openViewAndAssertCached({ win, cached: true });
+ });
+});
+
+add_task(async function topSites_changed() {
+ await withNewBrowserWindow(async win => {
+ // Open the view to show top sites and then close it.
+ await openViewAndAssertCached({ win, cached: false });
+
+ // Change the top sites by adding visits to a new URL.
+ let newURL = "https://changed.example.com/";
+ for (let j = 0; j < TOP_SITES_VISIT_COUNT; j++) {
+ await PlacesTestUtils.addVisits(newURL);
+ }
+ await updateTopSitesAndAwaitChanged(TEST_URLS_COUNT + 1);
+
+ // Open the view. It should *not* open synchronously and the cached
+ // top-sites context should not be used.
+ await openViewAndAssertCached({ win, cached: false });
+
+ // Open the view again. It should open synchronously and the new cached
+ // top-sites context with the new URL should be used.
+ await openViewAndAssertCached({
+ win,
+ cached: true,
+ urls: [newURL, ...TEST_URLS],
+ // The new URL is sometimes at the end of the list of top sites instead of
+ // the start, so ignore the order of the results.
+ ignoreOrder: true,
+ });
+
+ // Remove the new URL. The top sites will update themselves automatically,
+ // so we only need to wait for newtab-top-sites-changed.
+ info("Removing new URL and awaiting newtab-top-sites-changed");
+ let changedPromise = TestUtils.topicObserved("newtab-top-sites-changed");
+ await PlacesUtils.history.remove([newURL]);
+ await changedPromise;
+
+ // Open the view. It should *not* open synchronously and the cached
+ // top-sites context should not be used.
+ await openViewAndAssertCached({ win, cached: false });
+
+ // Open the view again. It should open synchronously and the new cached
+ // top-sites context with the new URL should be used.
+ await openViewAndAssertCached({ win, cached: true });
+ });
+});
+
+add_task(async function topSites_nonTopSitesResults() {
+ await withNewBrowserWindow(async win => {
+ // Open the view to show top sites and then close it.
+ await openViewAndAssertCached({ win, cached: false });
+
+ // Add a provider that returns a result with a suggested index of zero so
+ // that the first result in the view is not from the top-sites provider.
+ let suggestedIndexURL = "https://example.com/suggested-index-0";
+ let provider = new UrlbarTestUtils.TestProvider({
+ priority: lazy.UrlbarProviderTopSites.PRIORITY,
+ results: [
+ Object.assign(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ url: suggestedIndexURL,
+ }
+ ),
+ { suggestedIndex: 0 }
+ ),
+ ],
+ });
+ UrlbarProvidersManager.registerProvider(provider);
+
+ // Open the view. It should open synchronously and the cached top-sites
+ // context should be used. The suggested-index result should not be
+ // immediately present in the view since it's not in the cached context.
+ await openViewAndAssertCached({ win, cached: true, keepOpen: true });
+
+ // After the search has finished, the suggested-index result should be in
+ // the first row. The search's context should become the newly cached
+ // top-sites context and it should include the suggested-index result.
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(win),
+ TEST_URLS.length + 1,
+ "Should be one more result after search finishes"
+ );
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(win, 0);
+ Assert.equal(
+ details.url,
+ suggestedIndexURL,
+ "First result after search finishes should be the suggested index result"
+ );
+
+ // At this point, the search's context should have become the newly cached
+ // top-sites context and it should include the suggested-index result.
+
+ await UrlbarTestUtils.promisePopupClose(win);
+
+ // Open the view again. It should open synchronously and the new cached
+ // top-sites context with the suggested-index URL should be used.
+ await openViewAndAssertCached({
+ win,
+ cached: true,
+ urls: [suggestedIndexURL, ...TEST_URLS],
+ });
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+ });
+});
+
+add_task(async function topSites_disabled_1() {
+ await withNewBrowserWindow(async win => {
+ // Open the view to show top sites and then close it.
+ await openViewAndAssertCached({ win, cached: false });
+
+ // Disable `browser.urlbar.suggest.topsites`.
+ UrlbarPrefs.set("suggest.topsites", false);
+
+ // Open the view. It should *not* open synchronously and the cached
+ // top-sites context should not be used.
+ await openViewAndAssertCached({
+ win,
+ cached: false,
+ cachedAfterOpen: false,
+ });
+
+ // Clear the pref, open the view to show top sites, and close it.
+ UrlbarPrefs.clear("suggest.topsites");
+ await openViewAndAssertCached({ win, cached: false });
+
+ // Open the view. It should open synchronously and the cached top-sites
+ // context should be used.
+ await openViewAndAssertCached({ win, cached: true });
+ });
+});
+
+add_task(async function topSites_disabled_2() {
+ await withNewBrowserWindow(async win => {
+ // Open the view to show top sites and then close it.
+ await openViewAndAssertCached({ win, cached: false });
+
+ // Disable `browser.newtabpage.activity-stream.feeds.system.topsites`.
+ Services.prefs.setBoolPref(
+ "browser.newtabpage.activity-stream.feeds.system.topsites",
+ false
+ );
+
+ // Open the view. It should *not* open synchronously and the cached
+ // top-sites context should not be used.
+ await openViewAndAssertCached({
+ win,
+ cached: false,
+ cachedAfterOpen: false,
+ });
+
+ // Clear the pref, open the view to show top sites, and close it.
+ Services.prefs.clearUserPref(
+ "browser.newtabpage.activity-stream.feeds.system.topsites"
+ );
+ await openViewAndAssertCached({ win, cached: false });
+
+ // Open the view. It should open synchronously and the cached top-sites
+ // context should be used.
+ await openViewAndAssertCached({ win, cached: true });
+ });
+});
+
+add_task(async function evict() {
+ await withNewBrowserWindow(async win => {
+ let cache = win.gURLBar.view.queryContextCache;
+ Assert.equal(
+ typeof cache.size,
+ "number",
+ "Sanity check: queryContextCache.size is a number"
+ );
+
+ // Open the view to show top sites and then close it.
+ await openViewAndAssertCached({ win, cached: false });
+
+ // Do `cache.size` + 1 searches.
+ for (let i = 0; i < cache.size + 1; i++) {
+ let searchString = "test" + i;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: searchString,
+ });
+ await UrlbarTestUtils.promisePopupClose(win);
+ Assert.ok(
+ cache.get(searchString),
+ "Cache includes search string: " + searchString
+ );
+ }
+
+ // The first search string should have been evicted from the cache, but the
+ // one after that should still be cached.
+ Assert.ok(!cache.get("test0"), "test0 has been evicted from the cache");
+ Assert.ok(cache.get("test1"), "Cache includes test1");
+
+ // Revert the input and open the view to show the top sites. It should open
+ // synchronously and the cached top-sites context should be used.
+ win.gURLBar.handleRevert();
+ Assert.equal(win.gURLBar.value, "", "Input is empty after reverting");
+ await openViewAndAssertCached({ win, cached: true });
+ });
+});
+
+/**
+ * Opens the view and checks that it is or is not synchronously opened and
+ * populated as specified.
+ *
+ * @param {object} options
+ * Options object.
+ * @param {window} options.win
+ * The window to open the view in.
+ * @param {boolean} options.cached
+ * Whether a query context is expected to already be cached for the search
+ * that's performed when the view opens. If true, then the view should
+ * synchronously open and populate using the cached context. If false, then
+ * the view should asynchronously open once the first results are fetched.
+ * @param {boolean} [options.cachedAfterOpen]
+ * Whether the context is expected to be cached after the view opens and the
+ * query finishes.
+ * @param {string} [options.searchString]
+ * The search string for which the context should or should not be cached. If
+ * falsey, then the relevant context is assumed to be the top-sites context.
+ * @param {Array} [options.urls]
+ * Array of URLs that are expected to be shown in the view.
+ * @param {boolean} [options.ignoreOrder]
+ * Whether to treat `urls` as an unordered set instead of an array. When true,
+ * the order of results is ignored.
+ * @param {boolean} [options.keepOpen]
+ * Whether to keep the view open when the function returns.
+ */
+async function openViewAndAssertCached({
+ win,
+ cached,
+ cachedAfterOpen = true,
+ searchString = "",
+ urls = TEST_URLS,
+ ignoreOrder = false,
+ keepOpen = false,
+}) {
+ let cache = win.gURLBar.view.queryContextCache;
+ let getContext = () =>
+ searchString ? cache.get(searchString) : cache.topSitesContext;
+
+ Assert.equal(
+ !!getContext(),
+ cached,
+ "Context is present or not in cache as expected for search string: " +
+ JSON.stringify(searchString)
+ );
+
+ // Open the view by performing the accel+L command.
+ await SimpleTest.promiseFocus(win);
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+
+ Assert.equal(
+ win.gURLBar.view.isOpen,
+ cached,
+ "View is open or not as expected"
+ );
+
+ if (!cached && cachedAfterOpen) {
+ // Wait for the search to finish and the context to be cached since callers
+ // generally expect it.
+ await TestUtils.waitForCondition(
+ getContext,
+ "Waiting for context to be cached for search string: " +
+ JSON.stringify(searchString)
+ );
+ } else if (cached) {
+ // The view is expected to open synchronously. Check the results. We don't
+ // do this in the `!cached` case, when the view is expected to open
+ // asynchronously, because there are plenty of other tests for that. Here we
+ // want to make sure results are correct before the new search finishes in
+ // order to avoid any flicker.
+ let startIndex = 0;
+ let resultCount = urls.length;
+ if (searchString) {
+ // Plus heuristic
+ startIndex++;
+ resultCount++;
+ }
+
+ // In all the checks below, check the rows container directly instead of
+ // relying on `UrlbarTestUtils` functions that wait for the search to
+ // finish. Here we're specifically checking cached results that should be
+ // used before the search finishes.
+ let rows = UrlbarTestUtils.getResultsContainer(win).children;
+ Assert.equal(rows.length, resultCount, "View has expected row count");
+
+ // Check the search heuristic row.
+ if (searchString) {
+ let result = rows[0].result;
+ Assert.ok(result.heuristic, "First row should be a heuristic");
+ Assert.equal(
+ result.payload.query,
+ searchString,
+ "First row's query should be the search string"
+ );
+ }
+
+ // Check the URL rows.
+ let actualURLs = [];
+ let urlRows = Array.from(rows).slice(startIndex);
+ for (let row of urlRows) {
+ actualURLs.push(row.result.payload.url);
+ }
+ if (ignoreOrder) {
+ urls.sort();
+ actualURLs.sort();
+ }
+ Assert.deepEqual(actualURLs, urls, "View should contain the expected URLs");
+ }
+
+ // Now wait for the search to finish before returning. We await
+ // `lastQueryContextPromise` instead of the promise returned from
+ // `UrlbarTestUtils.promiseSearchComplete()` because the latter assumes the
+ // view will open, which isn't the case for every task here.
+ await win.gURLBar.lastQueryContextPromise;
+ if (!keepOpen) {
+ await UrlbarTestUtils.promisePopupClose(win);
+ }
+}
+
+/**
+ * Updates the top sites and waits for the "newtab-top-sites-changed"
+ * notification. Note that this notification is not sent if the sites don't
+ * actually change. In that case, use only `updateTopSites()` instead.
+ *
+ * @param {number} expectedCount
+ * The new expected number of top sites.
+ */
+async function updateTopSitesAndAwaitChanged(expectedCount) {
+ info("Updating top sites and awaiting newtab-top-sites-changed");
+ let changedPromise = TestUtils.topicObserved("newtab-top-sites-changed").then(
+ () => info("Observed newtab-top-sites-changed")
+ );
+ await updateTopSites(sites => sites?.length == expectedCount);
+ await changedPromise;
+}
+
+async function withNewBrowserWindow(callback) {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await callback(win);
+ await BrowserTestUtils.closeWindow(win);
+}
diff --git a/browser/components/urlbar/tests/browser/browser_quickactions.js b/browser/components/urlbar/tests/browser/browser_quickactions.js
new file mode 100644
index 0000000000..5945910067
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_quickactions.js
@@ -0,0 +1,783 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests QuickActions.
+ */
+
+"use strict";
+
+requestLongerTimeout(3);
+
+ChromeUtils.defineESModuleGetters(this, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ UpdateService: "resource://gre/modules/UpdateService.sys.mjs",
+
+ UrlbarProviderQuickActions:
+ "resource:///modules/UrlbarProviderQuickActions.sys.mjs",
+});
+XPCOMUtils.defineLazyModuleGetters(this, {
+ BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+});
+
+const DUMMY_PAGE =
+ "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
+
+let testActionCalled = 0;
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.quickactions.enabled", true],
+ ["browser.urlbar.suggest.quickactions", true],
+ ["browser.urlbar.shortcuts.quickactions", true],
+ ],
+ });
+
+ UrlbarProviderQuickActions.addAction("testaction", {
+ commands: ["testaction"],
+ label: "quickactions-downloads2",
+ onPick: () => testActionCalled++,
+ });
+
+ registerCleanupFunction(() => {
+ UrlbarProviderQuickActions.removeAction("testaction");
+ });
+});
+
+add_task(async function basic() {
+ info("The action isnt shown when not matched");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "nomatch",
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 1,
+ "We did no match anything"
+ );
+
+ info("A prefix of the command matches");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "testact",
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "We matched the action"
+ );
+
+ info("The callback of the action is fired when selected");
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ EventUtils.synthesizeKey("KEY_Enter", {}, window);
+ Assert.equal(testActionCalled, 1, "Test actionwas called");
+});
+
+add_task(async function test_label_command() {
+ info("A prefix of the label matches");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "View Dow",
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "We matched the action"
+ );
+
+ let { result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.DYNAMIC);
+ Assert.equal(result.providerName, "quickactions");
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_Escape");
+ });
+});
+
+add_task(async function enter_search_mode_button() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ let oneOffButton = await TestUtils.waitForCondition(() =>
+ window.document.getElementById("urlbar-engine-one-off-item-actions")
+ );
+ Assert.ok(oneOffButton, "One off button is available when preffed on");
+
+ EventUtils.synthesizeMouseAtCenter(oneOffButton, {}, window);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.ACTIONS,
+ entry: "oneoff",
+ });
+
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+ Assert.ok(true, "Actions are shown when we enter actions search mode.");
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeKey("KEY_Escape");
+});
+
+add_task(async function enter_search_mode_oneoff_by_key() {
+ // Select actions oneoff button by keyboard.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ const oneOffButtons = UrlbarTestUtils.getOneOffSearchButtons(window);
+ for (;;) {
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ if (
+ oneOffButtons.selectedButton.source === UrlbarUtils.RESULT_SOURCE.ACTIONS
+ ) {
+ break;
+ }
+ }
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: " ",
+ });
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.ACTIONS,
+ entry: "oneoff",
+ });
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeKey("KEY_Escape");
+});
+
+add_task(async function enter_search_mode_key() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "> ",
+ });
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.ACTIONS,
+ entry: "typed",
+ });
+ Assert.equal(
+ await hasQuickActions(window),
+ true,
+ "Actions are shown in search mode"
+ );
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeKey("KEY_Escape");
+});
+
+add_task(async function test_disabled() {
+ UrlbarProviderQuickActions.addAction("disabledaction", {
+ commands: ["disabledaction"],
+ isActive: () => false,
+ label: "quickactions-restart",
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "disabled",
+ });
+
+ Assert.equal(
+ await hasQuickActions(window),
+ false,
+ "Result for quick actions is hidden"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ UrlbarProviderQuickActions.removeAction("disabledaction");
+});
+
+/**
+ * The first part of this test confirms that when the screenshots component is enabled
+ * the screenshot quick action button will be enabled on about: pages.
+ * The second part confirms that when the screenshots extension is enabled the
+ * screenshot quick action button will be disbaled on about: pages.
+ */
+add_task(async function test_screenshot_enabled_or_disabled() {
+ let onLoaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ "about:blank"
+ );
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, "about:blank");
+ await onLoaded;
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "screenshot",
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "The action is displayed"
+ );
+ let screenshotButton = window.document.querySelector(
+ ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-row"
+ );
+ Assert.ok(
+ !screenshotButton.hasAttribute("disabled"),
+ "Screenshot button is enabled on about pages"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeKey("KEY_Escape");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["screenshots.browser.component.enabled", false]],
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "screenshot",
+ });
+ Assert.equal(
+ await hasQuickActions(window),
+ false,
+ "Result for quick actions is hidden"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+add_task(async function match_in_phrase() {
+ UrlbarProviderQuickActions.addAction("newtestaction", {
+ commands: ["matchingstring"],
+ label: "quickactions-downloads2",
+ });
+
+ info("The action is matched when at end of input");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "Test we match at end of matchingstring",
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "We matched the action"
+ );
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeKey("KEY_Escape");
+ UrlbarProviderQuickActions.removeAction("newtestaction");
+});
+
+async function isScreenshotInitialized() {
+ return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ let screenshotsChild = content.windowGlobalChild.getActor(
+ "ScreenshotsComponent"
+ );
+ return screenshotsChild?._overlay?._initialized;
+ });
+}
+
+add_task(async function test_screenshot() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["screenshots.browser.component.enabled", true]],
+ });
+
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, DUMMY_PAGE);
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ DUMMY_PAGE
+ );
+
+ info("The action is matched when at end of input");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "screenshot",
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "We matched the action"
+ );
+ let { result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.DYNAMIC);
+ Assert.equal(result.providerName, "quickactions");
+
+ info("Trigger the screenshot mode");
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ EventUtils.synthesizeKey("KEY_Enter", {}, window);
+ await TestUtils.waitForCondition(
+ isScreenshotInitialized,
+ "Screenshot component is active",
+ 200,
+ 100
+ );
+
+ info("Press Escape to exit screenshot mode");
+ EventUtils.synthesizeKey("KEY_Escape", {}, window);
+ await TestUtils.waitForCondition(
+ async () => !(await isScreenshotInitialized()),
+ "Screenshot component has been dismissed"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_Escape");
+ });
+});
+
+add_task(async function test_other_search_mode() {
+ let defaultEngine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ });
+ defaultEngine.alias = "testalias";
+ let oldDefaultEngine = await Services.search.getDefault();
+ Services.search.setDefault(
+ defaultEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: defaultEngine.alias + " ",
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 0,
+ "The results should be empty as no actions are displayed in other search modes"
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: defaultEngine.name,
+ entry: "typed",
+ });
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_Escape");
+ });
+ Services.search.setDefault(
+ oldDefaultEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+});
+
+add_task(async function test_no_quickactions_suggestions() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.quickactions", false]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "screenshot",
+ });
+ Assert.ok(
+ !window.document.querySelector(
+ ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-row"
+ ),
+ "Screenshot button is not suggested"
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "> screenshot",
+ });
+ Assert.ok(
+ window.document.querySelector(
+ ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-row"
+ ),
+ "Screenshot button is suggested"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeKey("KEY_Escape");
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_quickactions_disabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.quickactions.enabled", false],
+ ["browser.urlbar.suggest.quickactions", true],
+ ],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "screenshot",
+ });
+
+ Assert.ok(
+ !window.document.querySelector(
+ ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-row"
+ ),
+ "Screenshot button is not suggested"
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "> screenshot",
+ });
+ Assert.ok(
+ !window.document.querySelector(
+ ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-row"
+ ),
+ "Screenshot button is not suggested"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeKey("KEY_Escape");
+
+ await SpecialPowers.popPrefEnv();
+});
+
+let COMMANDS_TESTS = [
+ {
+ cmd: "add-ons",
+ uri: "about:addons",
+ testFun: async () => isSelected("button[name=discover]"),
+ },
+ {
+ cmd: "plugins",
+ uri: "about:addons",
+ testFun: async () => isSelected("button[name=plugin]"),
+ },
+ {
+ cmd: "extensions",
+ uri: "about:addons",
+ testFun: async () => isSelected("button[name=extension]"),
+ },
+ {
+ cmd: "themes",
+ uri: "about:addons",
+ testFun: async () => isSelected("button[name=theme]"),
+ },
+ {
+ cmd: "add-ons",
+ setup: async () => {
+ const onLoad = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ "http://example.com/"
+ );
+ BrowserTestUtils.loadURIString(
+ gBrowser.selectedBrowser,
+ "http://example.com/"
+ );
+ await onLoad;
+ },
+ uri: "about:addons",
+ isNewTab: true,
+ testFun: async () => isSelected("button[name=discover]"),
+ },
+ {
+ cmd: "plugins",
+ setup: async () => {
+ const onLoad = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ "http://example.com/"
+ );
+ BrowserTestUtils.loadURIString(
+ gBrowser.selectedBrowser,
+ "http://example.com/"
+ );
+ await onLoad;
+ },
+ uri: "about:addons",
+ isNewTab: true,
+ testFun: async () => isSelected("button[name=plugin]"),
+ },
+ {
+ cmd: "extensions",
+ setup: async () => {
+ const onLoad = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ "http://example.com/"
+ );
+ BrowserTestUtils.loadURIString(
+ gBrowser.selectedBrowser,
+ "http://example.com/"
+ );
+ await onLoad;
+ },
+ uri: "about:addons",
+ isNewTab: true,
+ testFun: async () => isSelected("button[name=extension]"),
+ },
+ {
+ cmd: "themes",
+ setup: async () => {
+ const onLoad = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ "http://example.com/"
+ );
+ BrowserTestUtils.loadURIString(
+ gBrowser.selectedBrowser,
+ "http://example.com/"
+ );
+ await onLoad;
+ },
+ uri: "about:addons",
+ isNewTab: true,
+ testFun: async () => isSelected("button[name=theme]"),
+ },
+];
+
+let isSelected = async selector =>
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [selector], arg => {
+ return ContentTaskUtils.waitForCondition(() =>
+ content.document.querySelector(arg)?.hasAttribute("selected")
+ );
+ });
+
+add_task(async function test_pages() {
+ for (const { cmd, uri, setup, isNewTab, testFun } of COMMANDS_TESTS) {
+ info(`Testing ${cmd} command is triggered`);
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ if (setup) {
+ info("Setup");
+ await setup();
+ }
+
+ let onLoad = isNewTab
+ ? BrowserTestUtils.waitForNewTab(gBrowser, uri, true)
+ : BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: cmd,
+ });
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ EventUtils.synthesizeKey("KEY_Enter", {}, window);
+
+ const newTab = await onLoad;
+
+ Assert.ok(
+ await testFun(),
+ `The command "${cmd}" passed completed its test`
+ );
+
+ if (isNewTab) {
+ await BrowserTestUtils.removeTab(newTab);
+ }
+ await BrowserTestUtils.removeTab(tab);
+ }
+});
+
+const assertActionButtonStatus = async (name, expectedEnabled, description) => {
+ await BrowserTestUtils.waitForCondition(() =>
+ window.document.querySelector(`[data-key=${name}]`)
+ );
+ const target = window.document.querySelector(`[data-key=${name}]`);
+ Assert.equal(!target.hasAttribute("disabled"), expectedEnabled, description);
+};
+
+add_task(async function test_viewsource() {
+ info("Check the button status of when the page is not web content");
+ const tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "about:home",
+ waitForLoad: true,
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "viewsource",
+ });
+ await assertActionButtonStatus(
+ "viewsource",
+ true,
+ "Should be enabled even if the page is not web content"
+ );
+
+ info("Check the button status of when the page is web content");
+ BrowserTestUtils.loadURIString(
+ gBrowser.selectedBrowser,
+ "http://example.com"
+ );
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "viewsource",
+ });
+ await assertActionButtonStatus(
+ "viewsource",
+ true,
+ "Should be enabled on web content as well"
+ );
+
+ info("Do view source action");
+ const onLoad = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "view-source:http://example.com/"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ EventUtils.synthesizeKey("KEY_Enter", {}, window);
+ const viewSourceTab = await onLoad;
+
+ info("Do view source action on the view-source page");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "viewsource",
+ });
+
+ Assert.equal(
+ await hasQuickActions(window),
+ false,
+ "Result for quick actions is hidden"
+ );
+
+ // Clean up.
+ BrowserTestUtils.removeTab(viewSourceTab);
+ BrowserTestUtils.removeTab(tab);
+});
+
+async function doAlertDialogTest({ input, dialogContentURI }) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: input,
+ });
+
+ const onDialog = BrowserTestUtils.promiseAlertDialog(null, null, {
+ isSubDialog: true,
+ callback: win => {
+ Assert.equal(win.location.href, dialogContentURI, "The dialog is opened");
+ EventUtils.synthesizeKey("KEY_Escape", {}, win);
+ },
+ });
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ EventUtils.synthesizeKey("KEY_Enter", {}, window);
+
+ await onDialog;
+}
+
+add_task(async function test_refresh() {
+ await doAlertDialogTest({
+ input: "refresh",
+ dialogContentURI: "chrome://global/content/resetProfile.xhtml",
+ });
+});
+
+add_task(async function test_clear() {
+ await doAlertDialogTest({
+ input: "clear",
+ dialogContentURI: "chrome://browser/content/sanitize.xhtml",
+ });
+});
+
+async function doUpdateActionTest(isActiveExpected, description) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "update",
+ });
+
+ if (isActiveExpected) {
+ await assertActionButtonStatus("update", isActiveExpected, description);
+ } else {
+ Assert.equal(await hasQuickActions(window), false, description);
+ }
+}
+
+add_task(async function test_update() {
+ if (!AppConstants.MOZ_UPDATER) {
+ await doUpdateActionTest(
+ false,
+ "Should be disabled since not AppConstants.MOZ_UPDATER"
+ );
+ return;
+ }
+
+ const sandbox = sinon.createSandbox();
+ try {
+ sandbox
+ .stub(UpdateService.prototype, "currentState")
+ .get(() => Ci.nsIApplicationUpdateService.STATE_IDLE);
+ await doUpdateActionTest(
+ false,
+ "Should be disabled since current update state is not pending"
+ );
+ sandbox
+ .stub(UpdateService.prototype, "currentState")
+ .get(() => Ci.nsIApplicationUpdateService.STATE_PENDING);
+ await doUpdateActionTest(
+ true,
+ "Should be enabled since current update state is pending"
+ );
+ } finally {
+ sandbox.restore();
+ }
+});
+
+async function hasQuickActions(win) {
+ for (let i = 0, count = UrlbarTestUtils.getResultCount(win); i < count; i++) {
+ const { result } = await UrlbarTestUtils.getDetailsOfResultAt(win, i);
+ if (result.providerName === "quickactions") {
+ return true;
+ }
+ }
+ return false;
+}
+
+add_task(async function test_show_in_zero_prefix() {
+ for (const minimumSearchString of [0, 3]) {
+ info(
+ `Test when quickactions.minimumSearchString pref is ${minimumSearchString}`
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "browser.urlbar.quickactions.minimumSearchString",
+ minimumSearchString,
+ ],
+ ],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+
+ Assert.equal(
+ await hasQuickActions(window),
+ !minimumSearchString,
+ "Result for quick actions is as expected"
+ );
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+add_task(async function test_whitespace() {
+ info("Test with quickactions.showInZeroPrefix pref is false");
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.quickactions.showInZeroPrefix", false]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: " ",
+ });
+ Assert.equal(
+ await hasQuickActions(window),
+ false,
+ "Result for quick actions is not shown"
+ );
+ await SpecialPowers.popPrefEnv();
+
+ info("Test with quickactions.showInZeroPrefix pref is true");
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.quickactions.showInZeroPrefix", true]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ const countForEmpty = window.document.querySelectorAll(
+ ".urlbarView-quickaction-row"
+ ).length;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: " ",
+ });
+ const countForWhitespace = window.document.querySelectorAll(
+ ".urlbarView-quickaction-row"
+ ).length;
+ Assert.equal(
+ countForEmpty,
+ countForWhitespace,
+ "Count of quick actions of empty and whitespace are same"
+ );
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_quickactions_devtools.js b/browser/components/urlbar/tests/browser/browser_quickactions_devtools.js
new file mode 100644
index 0000000000..d113a4c3a8
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_quickactions_devtools.js
@@ -0,0 +1,176 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests QuickActions related to DevTools.
+ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+ChromeUtils.defineESModuleGetters(this, {
+ DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.sys.mjs",
+});
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.quickactions.enabled", true],
+ ["browser.urlbar.suggest.quickactions", true],
+ ["browser.urlbar.shortcuts.quickactions", true],
+ ],
+ });
+});
+
+const assertActionButtonStatus = async (name, expectedEnabled, description) => {
+ await BrowserTestUtils.waitForCondition(() =>
+ window.document.querySelector(`[data-key=${name}]`)
+ );
+ const target = window.document.querySelector(`[data-key=${name}]`);
+ Assert.equal(!target.hasAttribute("disabled"), expectedEnabled, description);
+};
+
+async function hasQuickActions(win) {
+ for (let i = 0, count = UrlbarTestUtils.getResultCount(win); i < count; i++) {
+ const { result } = await UrlbarTestUtils.getDetailsOfResultAt(win, i);
+ if (result.providerName === "quickactions") {
+ return true;
+ }
+ }
+ return false;
+}
+
+add_task(async function test_inspector() {
+ const testData = [
+ {
+ description: "Test for 'about:' page",
+ page: "about:home",
+ isDevToolsUser: true,
+ actionVisible: true,
+ actionEnabled: true,
+ },
+ {
+ description: "Test for another 'about:' page",
+ page: "about:about",
+ isDevToolsUser: true,
+ actionVisible: true,
+ actionEnabled: true,
+ },
+ {
+ description: "Test for another devtools-toolbox page",
+ page: "about:devtools-toolbox",
+ isDevToolsUser: true,
+ actionVisible: true,
+ actionEnabled: false,
+ },
+ {
+ description: "Test for web content",
+ page: "https://example.com",
+ isDevToolsUser: true,
+ actionVisible: true,
+ actionEnabled: true,
+ },
+ {
+ description: "Test for disabled DevTools",
+ page: "https://example.com",
+ prefs: [["devtools.policy.disabled", true]],
+ isDevToolsUser: true,
+ actionVisible: true,
+ actionEnabled: false,
+ },
+ {
+ description: "Test for not DevTools user",
+ page: "https://example.com",
+ isDevToolsUser: false,
+ actionVisible: true,
+ actionEnabled: false,
+ },
+ {
+ description: "Test for fully disabled",
+ page: "https://example.com",
+ prefs: [["devtools.policy.disabled", true]],
+ isDevToolsUser: false,
+ actionVisible: false,
+ },
+ ];
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ for (const {
+ description,
+ page,
+ prefs = [],
+ isDevToolsUser,
+ actionEnabled,
+ actionVisible,
+ } of testData) {
+ info(description);
+
+ info("Set preferences");
+ await SpecialPowers.pushPrefEnv({
+ set: [...prefs, ["devtools.selfxss.count", isDevToolsUser ? 5 : 0]],
+ });
+
+ info("Check the button status");
+ const onLoad = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, page);
+ await onLoad;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "inspector",
+ });
+
+ if (actionVisible && actionEnabled) {
+ await assertActionButtonStatus(
+ "inspect",
+ true,
+ "The status of action button is correct"
+ );
+ } else {
+ Assert.equal(
+ await hasQuickActions(window),
+ false,
+ "Result for quick actions is not shown since the inspector tool is disabled"
+ );
+ }
+
+ await SpecialPowers.popPrefEnv();
+
+ if (!actionVisible || !actionEnabled) {
+ continue;
+ }
+
+ info("Do inspect action");
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ EventUtils.synthesizeKey("KEY_Enter", {}, window);
+ await BrowserTestUtils.waitForCondition(
+ () => DevToolsShim.hasToolboxForTab(gBrowser.selectedTab),
+ "Wait for opening inspector for current selected tab"
+ );
+ const toolbox = await DevToolsShim.getToolboxForTab(gBrowser.selectedTab);
+ await BrowserTestUtils.waitForCondition(
+ () => toolbox.getPanel("inspector"),
+ "Wait until the inspector is ready"
+ );
+
+ info("Do inspect action again in the same page during opening inspector");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "inspector",
+ });
+ Assert.equal(
+ await hasQuickActions(window),
+ false,
+ "Result for quick actions is not shown since the inspector is already opening"
+ );
+
+ info(
+ "Select another tool to check whether the inspector will be selected in next test even if the previous tool is not inspector"
+ );
+ await toolbox.selectTool("options");
+ await toolbox.destroy();
+ }
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_quickactions_tab_refocus.js b/browser/components/urlbar/tests/browser/browser_quickactions_tab_refocus.js
new file mode 100644
index 0000000000..f969528806
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_quickactions_tab_refocus.js
@@ -0,0 +1,194 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests for QuickActions that re-focus tab..
+ */
+
+"use strict";
+
+requestLongerTimeout(3);
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.quickactions.enabled", true],
+ ["browser.urlbar.suggest.quickactions", true],
+ ["browser.urlbar.shortcuts.quickactions", true],
+ ],
+ });
+});
+
+let isSelected = async selector =>
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [selector], arg => {
+ return ContentTaskUtils.waitForCondition(() =>
+ content.document.querySelector(arg)?.hasAttribute("selected")
+ );
+ });
+
+add_task(async function test_about_pages() {
+ const testData = [
+ {
+ firstInput: "downloads",
+ uri: "about:downloads",
+ },
+ {
+ firstInput: "logins",
+ uri: "about:logins",
+ },
+ {
+ firstInput: "settings",
+ uri: "about:preferences",
+ },
+ {
+ firstInput: "add-ons",
+ uri: "about:addons",
+ component: "button[name=discover]",
+ },
+ {
+ firstInput: "extensions",
+ uri: "about:addons",
+ component: "button[name=extension]",
+ },
+ {
+ firstInput: "plugins",
+ uri: "about:addons",
+ component: "button[name=plugin]",
+ },
+ {
+ firstInput: "themes",
+ uri: "about:addons",
+ component: "button[name=theme]",
+ },
+ {
+ firstLoad: "about:preferences#home",
+ secondInput: "settings",
+ uri: "about:preferences#home",
+ },
+ ];
+
+ for (const {
+ firstInput,
+ firstLoad,
+ secondInput,
+ uri,
+ component,
+ } of testData) {
+ info("Setup initial state");
+ let firstTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let onLoad = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ uri
+ );
+ if (firstLoad) {
+ info("Load initial URI");
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, uri);
+ } else {
+ info("Open about page by quick action");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: firstInput,
+ });
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ EventUtils.synthesizeKey("KEY_Enter", {}, window);
+ }
+ await onLoad;
+
+ if (component) {
+ info("Check whether the component is in the page");
+ Assert.ok(await isSelected(component), "There is expected component");
+ }
+
+ info("Do the second quick action in second tab");
+ let secondTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: secondInput || firstInput,
+ });
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ EventUtils.synthesizeKey("KEY_Enter", {}, window);
+ Assert.equal(
+ gBrowser.selectedTab,
+ firstTab,
+ "Switched to the tab that is opening the about page"
+ );
+ Assert.equal(
+ gBrowser.selectedBrowser.currentURI.spec,
+ uri,
+ "URI is not changed"
+ );
+ Assert.equal(gBrowser.tabs.length, 3, "Not opened a new tab");
+
+ if (component) {
+ info("Check whether the component is still in the page");
+ Assert.ok(await isSelected(component), "There is expected component");
+ }
+
+ BrowserTestUtils.removeTab(secondTab);
+ BrowserTestUtils.removeTab(firstTab);
+ }
+});
+
+add_task(async function test_about_addons_pages() {
+ let testData = [
+ {
+ cmd: "add-ons",
+ testFun: async () => isSelected("button[name=discover]"),
+ },
+ {
+ cmd: "plugins",
+ testFun: async () => isSelected("button[name=plugin]"),
+ },
+ {
+ cmd: "extensions",
+ testFun: async () => isSelected("button[name=extension]"),
+ },
+ {
+ cmd: "themes",
+ testFun: async () => isSelected("button[name=theme]"),
+ },
+ ];
+
+ info("Pick all actions related about:addons");
+ let originalTab = gBrowser.selectedTab;
+ for (const { cmd, testFun } of testData) {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: cmd,
+ });
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ EventUtils.synthesizeKey("KEY_Enter", {}, window);
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ Assert.ok(await testFun(), "The page content is correct");
+ }
+ Assert.equal(
+ gBrowser.tabs.length,
+ testData.length + 1,
+ "Tab length is correct"
+ );
+
+ info("Pick all again");
+ for (const { cmd, testFun } of testData) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: cmd,
+ });
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ EventUtils.synthesizeKey("KEY_Enter", {}, window);
+ await BrowserTestUtils.waitForCondition(() => testFun());
+ Assert.ok(true, "The tab correspondent action is selected");
+ }
+ Assert.equal(
+ gBrowser.tabs.length,
+ testData.length + 1,
+ "Tab length is not changed"
+ );
+
+ for (const tab of gBrowser.tabs) {
+ if (tab !== originalTab) {
+ BrowserTestUtils.removeTab(tab);
+ }
+ }
+});
diff --git a/browser/components/urlbar/tests/browser/browser_raceWithTabs.js b/browser/components/urlbar/tests/browser/browser_raceWithTabs.js
new file mode 100644
index 0000000000..17560ea101
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_raceWithTabs.js
@@ -0,0 +1,86 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URL = `${TEST_BASE_URL}dummy_page.html`;
+
+async function addBookmark(bookmark) {
+ info("Creating bookmark and keyword");
+ let bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: bookmark.url,
+ title: bookmark.title,
+ });
+ if (bookmark.keyword) {
+ await PlacesUtils.keywords.insert({
+ keyword: bookmark.keyword,
+ url: bookmark.url,
+ });
+ }
+
+ registerCleanupFunction(async function () {
+ if (bookmark.keyword) {
+ await PlacesUtils.keywords.remove(bookmark.keyword);
+ }
+ await PlacesUtils.bookmarks.remove(bm);
+ });
+}
+
+/**
+ * Check that if the user hits enter and ctrl-t at the same time, we open the
+ * URL in the right tab.
+ */
+add_task(async function hitEnterLoadInRightTab() {
+ await addBookmark({
+ title: "Test for keyword bookmark and URL",
+ url: TEST_URL,
+ keyword: "urlbarkeyword",
+ });
+
+ info("Opening a tab");
+ let oldTabOpenPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabOpen"
+ );
+ BrowserOpenTab();
+ let oldTab = (await oldTabOpenPromise).target;
+ let oldTabLoadedPromise = BrowserTestUtils.browserLoaded(
+ oldTab.linkedBrowser,
+ false,
+ TEST_URL
+ ).then(() => info("Old tab loaded"));
+
+ info("Filling URL bar, sending <return> and opening a tab");
+ let tabOpenPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabOpen"
+ );
+ gURLBar.value = "urlbarkeyword";
+ gURLBar.focus();
+ gURLBar.select();
+ EventUtils.sendKey("return");
+
+ info("Immediately open a second tab");
+ BrowserOpenTab();
+ let newTab = (await tabOpenPromise).target;
+
+ info("Created new tab; waiting for tabs to load");
+ let newTabLoadedPromise = BrowserTestUtils.browserLoaded(
+ newTab.linkedBrowser,
+ false,
+ "about:newtab"
+ ).then(() => info("New tab loaded"));
+ // If one of the tabs loads the wrong page, this will timeout, and that
+ // indicates we regressed this bug fix.
+ await Promise.all([newTabLoadedPromise, oldTabLoadedPromise]);
+ // These are not particularly useful, but the test must contain some checks.
+ is(
+ newTab.linkedBrowser.currentURI.spec,
+ "about:newtab",
+ "New tab loaded about:newtab"
+ );
+ is(oldTab.linkedBrowser.currentURI.spec, TEST_URL, "Old tab loaded URL");
+
+ info("Closing tabs");
+ BrowserTestUtils.removeTab(newTab);
+ BrowserTestUtils.removeTab(oldTab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_redirect_error.js b/browser/components/urlbar/tests/browser/browser_redirect_error.js
new file mode 100644
index 0000000000..2fc6155cd5
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_redirect_error.js
@@ -0,0 +1,137 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const REDIRECT_FROM = `${TEST_BASE_URL}redirect_error.sjs`;
+
+const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host.
+
+function isRedirectedURISpec(aURISpec) {
+ return isRedirectedURI(Services.io.newURI(aURISpec));
+}
+
+function isRedirectedURI(aURI) {
+ // Compare only their before-hash portion.
+ return Services.io.newURI(REDIRECT_TO).equalsExceptRef(aURI);
+}
+
+/*
+ Test.
+
+1. Load redirect_bug623155.sjs#BG in a background tab.
+
+2. The redirected URI is <https://www.bank1.com/#BG>, which displayes a cert
+ error page.
+
+3. Switch the tab to foreground.
+
+4. Check the URLbar's value, expecting <https://www.bank1.com/#BG>
+
+5. Load redirect_bug623155.sjs#FG in the foreground tab.
+
+6. The redirected URI is <https://www.bank1.com/#FG>. And this is also
+ a cert-error page.
+
+7. Check the URLbar's value, expecting <https://www.bank1.com/#FG>
+
+8. End.
+
+ */
+
+var gNewTab;
+
+function test() {
+ waitForExplicitFinish();
+
+ // Load a URI in the background.
+ gNewTab = BrowserTestUtils.addTab(gBrowser, REDIRECT_FROM + "#BG");
+ gBrowser
+ .getBrowserForTab(gNewTab)
+ .webProgress.addProgressListener(
+ gWebProgressListener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION
+ );
+}
+
+var gWebProgressListener = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+
+ // ---------------------------------------------------------------------------
+ // NOTIFY_LOCATION mode should work fine without these methods.
+ //
+ // onStateChange: function() {},
+ // onStatusChange: function() {},
+ // onProgressChange: function() {},
+ // onSecurityChange: function() {},
+ // ----------------------------------------------------------------------------
+
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ if (!aRequest) {
+ // This is bug 673752, or maybe initial "about:blank".
+ return;
+ }
+
+ ok(gNewTab, "There is a new tab.");
+ ok(
+ isRedirectedURI(aLocation),
+ "onLocationChange catches only redirected URI."
+ );
+
+ if (aLocation.ref == "BG") {
+ // This is background tab's request.
+ isnot(gNewTab, gBrowser.selectedTab, "This is a background tab.");
+ } else if (aLocation.ref == "FG") {
+ // This is foreground tab's request.
+ is(gNewTab, gBrowser.selectedTab, "This is a foreground tab.");
+ } else {
+ // We shonuld not reach here.
+ ok(false, "This URI hash is not expected:" + aLocation.ref);
+ }
+
+ let isSelectedTab = gNewTab.selected;
+ setTimeout(delayed, 0, isSelectedTab);
+ },
+};
+
+function delayed(aIsSelectedTab) {
+ // Switch tab and confirm URL bar.
+ if (!aIsSelectedTab) {
+ gBrowser.selectedTab = gNewTab;
+ }
+
+ let currentURI = gBrowser.selectedBrowser.currentURI.spec;
+ ok(
+ isRedirectedURISpec(currentURI),
+ "The content area is redirected. aIsSelectedTab:" + aIsSelectedTab
+ );
+ is(
+ gURLBar.value,
+ currentURI,
+ "The URL bar shows the content URI. aIsSelectedTab:" + aIsSelectedTab
+ );
+
+ if (!aIsSelectedTab) {
+ // If this was a background request, go on a foreground request.
+ BrowserTestUtils.loadURIString(
+ gBrowser.selectedBrowser,
+ REDIRECT_FROM + "#FG"
+ );
+ } else {
+ // Othrewise, nothing to do remains.
+ finish();
+ }
+}
+
+/* Cleanup */
+registerCleanupFunction(function () {
+ if (gNewTab) {
+ gBrowser
+ .getBrowserForTab(gNewTab)
+ .webProgress.removeProgressListener(gWebProgressListener);
+
+ gBrowser.removeTab(gNewTab);
+ }
+ gNewTab = null;
+});
diff --git a/browser/components/urlbar/tests/browser/browser_remoteness_switch.js b/browser/components/urlbar/tests/browser/browser_remoteness_switch.js
new file mode 100644
index 0000000000..364eeff1b2
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_remoteness_switch.js
@@ -0,0 +1,56 @@
+"use strict";
+
+/**
+ * Verify that when loading and going back/forward through history between URLs
+ * loaded in the content process, and URLs loaded in the parent process, we
+ * don't set the URL for the tab to about:blank inbetween the loads.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.navigation.requireUserInteraction", false]],
+ });
+ let url = "http://www.example.com/foo.html";
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url },
+ async function (browser) {
+ let wpl = {
+ onLocationChange(unused, unused2, location) {
+ if (location.schemeIs("about")) {
+ is(
+ location.spec,
+ "about:config",
+ "Only about: location change should be for about:preferences"
+ );
+ } else {
+ is(
+ location.spec,
+ url,
+ "Only non-about: location change should be for the http URL we're dealing with."
+ );
+ }
+ },
+ };
+ gBrowser.addProgressListener(wpl);
+
+ let didLoad = BrowserTestUtils.browserLoaded(
+ browser,
+ null,
+ function (loadedURL) {
+ return loadedURL == "about:config";
+ }
+ );
+ BrowserTestUtils.loadURIString(browser, "about:config");
+ await didLoad;
+
+ gBrowser.goBack();
+ await BrowserTestUtils.browserLoaded(browser, null, function (loadedURL) {
+ return url == loadedURL;
+ });
+ gBrowser.goForward();
+ await BrowserTestUtils.browserLoaded(browser, null, function (loadedURL) {
+ return loadedURL == "about:config";
+ });
+ gBrowser.removeProgressListener(wpl);
+ }
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_remotetab.js b/browser/components/urlbar/tests/browser/browser_remotetab.js
new file mode 100644
index 0000000000..1fde855dbd
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_remotetab.js
@@ -0,0 +1,111 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests checks that the remote tab result is displayed and can be
+ * selected.
+ */
+
+"use strict";
+
+const { SyncedTabs } = ChromeUtils.importESModule(
+ "resource://services-sync/SyncedTabs.sys.mjs"
+);
+
+const TEST_URL = `${TEST_BASE_URL}dummy_page.html`;
+
+const REMOTE_TAB = {
+ id: "7cqCr77ptzX3",
+ type: "client",
+ lastModified: 1492201200,
+ name: "zcarter's Nightly on MacBook-Pro-25",
+ clientType: "desktop",
+ tabs: [
+ {
+ type: "tab",
+ title: "Test Remote",
+ url: TEST_URL,
+ icon: UrlbarUtils.ICON.DEFAULT,
+ client: "7cqCr77ptzX3",
+ lastUsed: Math.floor(Date.now() / 1000),
+ },
+ ],
+};
+
+add_setup(async function () {
+ sandbox = sinon.createSandbox();
+
+ let originalSyncedTabsInternal = SyncedTabs._internal;
+ SyncedTabs._internal = {
+ isConfiguredToSyncTabs: true,
+ hasSyncedThisSession: true,
+ getTabClients() {
+ return Promise.resolve([]);
+ },
+ syncTabs() {
+ return Promise.resolve();
+ },
+ };
+
+ // Tell the Sync XPCOM service it is initialized.
+ let weaveXPCService = Cc["@mozilla.org/weave/service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ let oldWeaveServiceReady = weaveXPCService.ready;
+ weaveXPCService.ready = true;
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.autoFill", false],
+ ["services.sync.username", "fake"],
+ ["services.sync.syncedTabs.showRemoteTabs", true],
+ ],
+ });
+
+ sandbox
+ .stub(SyncedTabs._internal, "getTabClients")
+ .callsFake(() => Promise.resolve(Cu.cloneInto([REMOTE_TAB], {})));
+
+ registerCleanupFunction(async () => {
+ sandbox.restore();
+ weaveXPCService.ready = oldWeaveServiceReady;
+ SyncedTabs._internal = originalSyncedTabsInternal;
+ });
+});
+
+add_task(async function test_remotetab_opens() {
+ await BrowserTestUtils.withNewTab(
+ { url: "about:robots", gBrowser },
+ async function () {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "Test Remote",
+ });
+
+ // There should be two items in the pop-up, the first is the default search
+ // suggestion, the second is the remote tab.
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.REMOTE_TAB,
+ "Should be the remote tab entry"
+ );
+
+ // The URL is going to open in the current tab as it is currently about:blank
+ let promiseTabLoaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await promiseTabLoaded;
+
+ Assert.equal(
+ gBrowser.selectedTab.linkedBrowser.currentURI.spec,
+ TEST_URL,
+ "correct URL loaded"
+ );
+ }
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_removeUnsafeProtocolsFromURLBarPaste.js b/browser/components/urlbar/tests/browser/browser_removeUnsafeProtocolsFromURLBarPaste.js
new file mode 100644
index 0000000000..4dfbc5c01b
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_removeUnsafeProtocolsFromURLBarPaste.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Ensures that pasting unsafe protocols in the urlbar have the protocol
+ * correctly stripped.
+ */
+
+var pairs = [
+ ["javascript:", ""],
+ ["javascript:1+1", "1+1"],
+ ["javascript:document.domain", "document.domain"],
+ [
+ " \u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009javascript:document.domain",
+ "document.domain",
+ ],
+ ["java\nscript:foo", "foo"],
+ ["java\tscript:foo", "foo"],
+ ["http://\nexample.com", "http://example.com"],
+ ["http://\nexample.com\n", "http://example.com"],
+ ["data:text/html,<body>hi</body>", "data:text/html,<body>hi</body>"],
+ ["javaScript:foopy", "foopy"],
+ ["javaScript:javaScript:alert('hi')", "alert('hi')"],
+ // Nested things get confusing because some things don't parse as URIs:
+ ["javascript:javascript:alert('hi!')", "alert('hi!')"],
+ [
+ "data:data:text/html,<body>hi</body>",
+ "data:data:text/html,<body>hi</body>",
+ ],
+ ["javascript:data:javascript:alert('hi!')", "data:javascript:alert('hi!')"],
+ [
+ "javascript:data:text/html,javascript:alert('hi!')",
+ "data:text/html,javascript:alert('hi!')",
+ ],
+ [
+ "data:data:text/html,javascript:alert('hi!')",
+ "data:data:text/html,javascript:alert('hi!')",
+ ],
+];
+
+let supportsNullBytes = AppConstants.platform == "macosx";
+// Note that \u000d (\r) is missing here; we test it separately because it
+// makes the test sad on Windows.
+let nonsense =
+ "\u000a\u000b\u000c\u000e\u000f\u0010\u0011\u0012\u0013\u0014javascript:foo";
+if (supportsNullBytes) {
+ nonsense = "\u0000" + nonsense;
+}
+pairs.push([nonsense, "foo"]);
+
+let supportsReturnWithoutNewline =
+ AppConstants.platform != "win" && AppConstants.platform != "linux";
+if (supportsReturnWithoutNewline) {
+ pairs.push(["java\rscript:foo", "foo"]);
+}
+
+async function paste(input) {
+ try {
+ await SimpleTest.promiseClipboardChange(
+ aData => {
+ // This test checks how "\r" is treated. Therefore, we cannot specify
+ // string here and instead, we need to compare strictly with this
+ // function.
+ return aData === input;
+ },
+ () => {
+ clipboardHelper.copyString(input);
+ }
+ );
+ } catch (ex) {
+ Assert.ok(false, "Failed to copy string '" + input + "' to clipboard");
+ }
+
+ document.commandDispatcher
+ .getControllerForCommand("cmd_paste")
+ .doCommand("cmd_paste");
+}
+
+add_task(async function test_stripUnsafeProtocolPaste() {
+ for (let [inputValue, expectedURL] of pairs) {
+ gURLBar.value = "";
+ gURLBar.focus();
+ await paste(inputValue);
+
+ Assert.equal(
+ gURLBar.value,
+ expectedURL,
+ `entering ${inputValue} strips relevant bits.`
+ );
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+});
diff --git a/browser/components/urlbar/tests/browser/browser_remove_match.js b/browser/components/urlbar/tests/browser/browser_remove_match.js
new file mode 100644
index 0000000000..94a1c874bf
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_remove_match.js
@@ -0,0 +1,297 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+ChromeUtils.defineESModuleGetters(this, {
+ FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
+});
+
+add_setup(async function () {
+ await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
+
+ let engine = Services.search.getEngineByName("Example");
+ await Services.search.moveEngine(engine, 0);
+});
+
+add_task(async function test_remove_history() {
+ const TEST_URL = "http://remove.me/from_urlbar/";
+ await PlacesTestUtils.addVisits(TEST_URL);
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+
+ let promiseVisitRemoved = PlacesTestUtils.waitForNotification(
+ "page-removed",
+ events => events[0].url === TEST_URL
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "from_urlbar",
+ });
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, TEST_URL, "Found the expected result");
+
+ let expectedResultCount = UrlbarTestUtils.getResultCount(window) - 1;
+
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), 1);
+ EventUtils.synthesizeKey("KEY_Delete", { shiftKey: true });
+
+ const removeEvents = await promiseVisitRemoved;
+ Assert.ok(
+ removeEvents[0].isRemovedFromStore,
+ "isRemovedFromStore should be true"
+ );
+
+ await TestUtils.waitForCondition(
+ () => UrlbarTestUtils.getResultCount(window) == expectedResultCount,
+ "Waiting for the result to disappear"
+ );
+
+ for (let i = 0; i < expectedResultCount; i++) {
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.notEqual(
+ details.url,
+ TEST_URL,
+ "Should not find the test URL in the remaining results"
+ );
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+add_task(async function test_remove_form_history() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.searches", true],
+ ["browser.urlbar.maxHistoricalSearchSuggestions", 1],
+ ],
+ });
+
+ let formHistoryValue = "foobar";
+ await UrlbarTestUtils.formHistory.add([formHistoryValue]);
+
+ let formHistory = (
+ await UrlbarTestUtils.formHistory.search({
+ value: formHistoryValue,
+ })
+ ).map(entry => entry.value);
+ Assert.deepEqual(
+ formHistory,
+ [formHistoryValue],
+ "Should find form history after adding it"
+ );
+
+ let promiseRemoved = UrlbarTestUtils.formHistory.promiseChanged("remove");
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+
+ let index = 1;
+ let count = UrlbarTestUtils.getResultCount(window);
+ for (; index < count; index++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ if (
+ result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
+ result.source == UrlbarUtils.RESULT_SOURCE.HISTORY
+ ) {
+ break;
+ }
+ }
+ Assert.ok(index < count, "Result found");
+
+ EventUtils.synthesizeKey("KEY_Tab", { repeat: index });
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), index);
+ EventUtils.synthesizeKey("KEY_Delete", { shiftKey: true });
+ await promiseRemoved;
+
+ await TestUtils.waitForCondition(
+ () => UrlbarTestUtils.getResultCount(window) == count - 1,
+ "Waiting for the result to disappear"
+ );
+
+ for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.ok(
+ result.type != UrlbarUtils.RESULT_TYPE.SEARCH ||
+ result.source != UrlbarUtils.RESULT_SOURCE.HISTORY,
+ "Should not find the form history result in the remaining results"
+ );
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ formHistory = (
+ await UrlbarTestUtils.formHistory.search({
+ value: formHistoryValue,
+ })
+ ).map(entry => entry.value);
+ Assert.deepEqual(
+ formHistory,
+ [],
+ "Should not find form history after removing it"
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// We shouldn't be able to remove a bookmark item.
+add_task(async function test_remove_bookmark_doesnt() {
+ const TEST_URL = "http://dont.remove.me/from_urlbar/";
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "test",
+ url: TEST_URL,
+ });
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.bookmarks.eraseEverything();
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "from_urlbar",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.url, TEST_URL, "Found the expected result");
+
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), 1);
+ EventUtils.synthesizeKey("KEY_Delete", { shiftKey: true });
+
+ // We don't have an easy way of determining if the event was process or not,
+ // so let any event queues clear before testing.
+ await new Promise(resolve => setTimeout(resolve, 0));
+ await PlacesTestUtils.promiseAsyncUpdates();
+
+ Assert.ok(
+ await PlacesUtils.bookmarks.fetch({ url: TEST_URL }),
+ "Should still have the URL bookmarked."
+ );
+});
+
+add_task(async function test_searchMode_removeRestyledHistory() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.searches", true],
+ ["browser.urlbar.maxHistoricalSearchSuggestions", 1],
+ ],
+ });
+
+ let query = "ciao";
+ let url = `https://example.com/?q=${query}bar`;
+ await PlacesTestUtils.addVisits(url);
+
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: query,
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.SEARCH);
+ Assert.equal(result.source, UrlbarUtils.RESULT_SOURCE.HISTORY);
+
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), 1);
+ EventUtils.synthesizeKey("KEY_Delete", { shiftKey: true });
+ await TestUtils.waitForCondition(
+ async () => !(await PlacesTestUtils.isPageInDB(url)),
+ "Wait for url to be removed from history"
+ );
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 1,
+ "Urlbar result should be removed"
+ );
+
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+ await PlacesUtils.history.clear();
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function blockButton() {
+ if (UrlbarPrefs.get("resultMenu")) {
+ // This case is covered by browser_result_menu.js.
+ return;
+ }
+
+ let url = "https://example.com/has-block-button";
+ let provider = new UrlbarTestUtils.TestProvider({
+ priority: Infinity,
+ results: [
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ url,
+ isBlockable: true,
+ blockL10n: { id: "firefox-suggest-urlbar-block" },
+ }
+ ),
+ ],
+ });
+
+ // Implement the provider's `onEngagement()` so it removes the result.
+ let onEngagementCallCount = 0;
+ provider.onEngagement = (isPrivate, state, queryContext, details) => {
+ onEngagementCallCount++;
+ queryContext.view.controller.removeResult(details.result);
+ };
+
+ UrlbarProvidersManager.registerProvider(provider);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 1,
+ "There should be one result"
+ );
+
+ let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+ Assert.equal(
+ row.result.payload.url,
+ url,
+ "The result should be in the first row"
+ );
+
+ let button = row.querySelector(".urlbarView-button-block");
+ Assert.ok(button, "The row should have a block button");
+
+ info("Tabbing down to block button");
+ EventUtils.synthesizeKey("KEY_Tab", { repeat: 2 });
+
+ Assert.equal(
+ UrlbarTestUtils.getSelectedElement(window),
+ button,
+ "The block button should be selected after tabbing down"
+ );
+
+ info("Pressing Enter on block button");
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ Assert.equal(
+ onEngagementCallCount,
+ 1,
+ "onEngagement() should have been called once"
+ );
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 0,
+ "There should be no results after blocking"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_restoreEmptyInput.js b/browser/components/urlbar/tests/browser/browser_restoreEmptyInput.js
new file mode 100644
index 0000000000..096d8e2134
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_restoreEmptyInput.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// When the input is empty and the view is opened, keying down through the
+// results and then out of the results should restore the empty input.
+
+"use strict";
+
+add_task(async function test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.quickactions", false]],
+ });
+
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits("http://example.com/");
+ }
+ // Update Top Sites to make sure the last Top Site is a URL. Otherwise, it
+ // would be a search shortcut and thus would not fill the Urlbar when
+ // selected.
+ await updateTopSites(sites => {
+ return (
+ sites &&
+ sites[sites.length - 1] &&
+ sites[sites.length - 1].url == "http://example.com/"
+ );
+ });
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ fireInputEvent: true,
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ -1,
+ "Nothing selected"
+ );
+
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+ Assert.greater(resultCount, 0, "At least one result");
+
+ for (let i = 0; i < resultCount; i++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ resultCount - 1,
+ "Last result selected"
+ );
+ Assert.notEqual(gURLBar.value, "", "Input should not be empty");
+
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ -1,
+ "Nothing selected"
+ );
+ Assert.equal(gURLBar.value, "", "Input should be empty");
+});
diff --git a/browser/components/urlbar/tests/browser/browser_resultSpan.js b/browser/components/urlbar/tests/browser/browser_resultSpan.js
new file mode 100644
index 0000000000..9b17fb71f5
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_resultSpan.js
@@ -0,0 +1,254 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that displaying results with resultSpan > 1 limits other results in
+// the view.
+
+const TEST_RESULTS = [
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: "http://mozilla.org/1" }
+ ),
+ makeTipResult(),
+];
+
+const MAX_RESULTS = UrlbarPrefs.get("maxRichResults");
+const TIP_SPAN = UrlbarUtils.getSpanForResult({
+ type: UrlbarUtils.RESULT_TYPE.TIP,
+});
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.quickactions", false]],
+ });
+});
+
+// A restricting provider with one tip result and many history results.
+add_task(async function oneTip() {
+ let results = Array.from(TEST_RESULTS);
+ for (let i = TEST_RESULTS.length; i < MAX_RESULTS; i++) {
+ results.push(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: `http://mozilla.org/${i}` }
+ )
+ );
+ }
+
+ let expectedResults = Array.from(results).slice(
+ 0,
+ MAX_RESULTS - TIP_SPAN + 1
+ );
+
+ let provider = new UrlbarTestUtils.TestProvider({ results, priority: 1 });
+ UrlbarProvidersManager.registerProvider(provider);
+
+ let context = await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "test",
+ window,
+ });
+
+ checkResults(context.results, expectedResults);
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+ gURLBar.view.close();
+});
+
+// A restricting provider with three tip results and many history results.
+add_task(async function threeTips() {
+ let results = Array.from(TEST_RESULTS);
+ for (let i = 1; i < 3; i++) {
+ results.push(makeTipResult());
+ }
+ for (let i = 2; i < 15; i++) {
+ results.push(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: `http://mozilla.org/${i}` }
+ )
+ );
+ }
+
+ let expectedResults = Array.from(results).slice(
+ 0,
+ MAX_RESULTS - 3 * (TIP_SPAN - 1)
+ );
+
+ let provider = new UrlbarTestUtils.TestProvider({ results, priority: 1 });
+ UrlbarProvidersManager.registerProvider(provider);
+
+ let context = await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "test",
+ window,
+ });
+
+ checkResults(context.results, expectedResults);
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+ gURLBar.view.close();
+});
+
+// A non-restricting provider with one tip result and many history results.
+add_task(async function oneTip_nonRestricting() {
+ let results = Array.from(TEST_RESULTS);
+ for (let i = 2; i < 15; i++) {
+ results.push(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: `http://mozilla.org/${i}` }
+ )
+ );
+ }
+
+ let expectedResults = Array.from(results);
+
+ // UrlbarProviderHeuristicFallback's heuristic search result
+ expectedResults.unshift({
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ payload: {
+ engine: Services.search.defaultEngine.name,
+ query: "test",
+ },
+ });
+
+ expectedResults = expectedResults.slice(0, MAX_RESULTS - TIP_SPAN + 1);
+
+ let provider = new UrlbarTestUtils.TestProvider({ results });
+ UrlbarProvidersManager.registerProvider(provider);
+
+ let context = await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "test",
+ window,
+ });
+
+ checkResults(context.results, expectedResults);
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+ gURLBar.view.close();
+});
+
+// A non-restricting provider with three tip results and many history results.
+add_task(async function threeTips_nonRestricting() {
+ let results = Array.from(TEST_RESULTS);
+ for (let i = 1; i < 3; i++) {
+ results.push(makeTipResult());
+ }
+ for (let i = 2; i < 15; i++) {
+ results.push(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: `http://mozilla.org/${i}` }
+ )
+ );
+ }
+
+ let expectedResults = Array.from(results);
+
+ // UrlbarProviderHeuristicFallback's heuristic search result
+ expectedResults.unshift({
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ payload: {
+ engine: Services.search.defaultEngine.name,
+ query: "test",
+ },
+ });
+
+ expectedResults = expectedResults.slice(0, MAX_RESULTS - 3 * (TIP_SPAN - 1));
+
+ let provider = new UrlbarTestUtils.TestProvider({ results });
+ UrlbarProvidersManager.registerProvider(provider);
+
+ let context = await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "test",
+ window,
+ });
+
+ checkResults(context.results, expectedResults);
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+ gURLBar.view.close();
+});
+
+add_task(async function customValue() {
+ let results = [];
+ for (let i = 0; i < 15; i++) {
+ results.push(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: `http://mozilla.org/${i}` }
+ )
+ );
+ }
+
+ results[1].resultSpan = 5;
+
+ let expectedResults = Array.from(results);
+ expectedResults = expectedResults.slice(0, 6);
+
+ let provider = new UrlbarTestUtils.TestProvider({ results });
+ UrlbarProvidersManager.registerProvider(provider);
+
+ let context = await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "test",
+ window,
+ });
+
+ checkResults(context.results, expectedResults);
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+ gURLBar.view.close();
+});
+
+function checkResults(actual, expected) {
+ Assert.equal(actual.length, expected.length, "Number of results");
+ for (let i = 0; i < expected.length; i++) {
+ info(`Checking results at index ${i}`);
+ let actualResult = collectExpectedProperties(actual[i], expected[i]);
+ Assert.deepEqual(actualResult, expected[i], "Actual vs. expected result");
+ }
+}
+
+function collectExpectedProperties(actualObj, expectedObj) {
+ let newActualObj = {};
+ for (let name in expectedObj) {
+ if (typeof expectedObj[name] == "object") {
+ newActualObj[name] = collectExpectedProperties(
+ actualObj[name],
+ expectedObj[name]
+ );
+ } else {
+ newActualObj[name] = expectedObj[name];
+ }
+ }
+ return newActualObj;
+}
+
+function makeTipResult() {
+ return new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.TIP,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ helpUrl: "http://example.com/",
+ type: "test",
+ titleL10n: { id: "urlbar-search-tips-confirm" },
+ buttons: [
+ {
+ url: "http://example.com/",
+ l10n: { id: "urlbar-search-tips-confirm" },
+ },
+ ],
+ }
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/browser_result_menu.js b/browser/components/urlbar/tests/browser/browser_result_menu.js
new file mode 100644
index 0000000000..b5df97f863
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_result_menu.js
@@ -0,0 +1,266 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.resultMenu", true]],
+ });
+});
+
+add_task(async function test_history() {
+ const TEST_URL = "https://remove.me/from_urlbar/";
+ await PlacesTestUtils.addVisits(TEST_URL);
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+
+ const resultIndex = 1;
+ let result;
+ let startQuery = async () => {
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "from_urlbar",
+ });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, resultIndex);
+ Assert.equal(result.url, TEST_URL, "Found the expected result");
+ gURLBar.view.selectedRowIndex = resultIndex;
+ };
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.resultMenu.keyboardAccessible", false]],
+ });
+ await startQuery();
+ EventUtils.synthesizeKey("KEY_Tab");
+ isnot(
+ UrlbarTestUtils.getSelectedElement(window),
+ UrlbarTestUtils.getButtonForResultIndex(window, "menu", resultIndex),
+ "Tab key skips over menu button with resultMenu.keyboardAccessible pref set to false"
+ );
+ info(
+ "Checking that the mouse can still activate the menu button with resultMenu.keyboardAccessible = false"
+ );
+ await UrlbarTestUtils.openResultMenu(window, {
+ byMouse: true,
+ resultIndex,
+ });
+ gURLBar.view.resultMenu.hidePopup();
+ await SpecialPowers.popPrefEnv();
+ await startQuery();
+ EventUtils.synthesizeKey("KEY_Tab");
+ is(
+ UrlbarTestUtils.getSelectedElement(window),
+ UrlbarTestUtils.getButtonForResultIndex(window, "menu", resultIndex),
+ "Tab key doesn't skip over menu button with resultMenu.keyboardAccessible pref reset to true"
+ );
+
+ info("Checking that Space activates the menu button");
+ await startQuery();
+ await UrlbarTestUtils.openResultMenu(window, {
+ activationKey: " ",
+ });
+ gURLBar.view.resultMenu.hidePopup();
+
+ info("Selecting Learn more item from the result menu");
+ let tabOpenPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ Services.urlFormatter.formatURLPref("app.support.baseURL") +
+ "awesome-bar-result-menu"
+ );
+ await UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "L");
+ info("Waiting for Learn more link to open in a new tab");
+ await tabOpenPromise;
+ gBrowser.removeCurrentTab();
+
+ info("Restarting query in order to remove history entry via the menu");
+ await startQuery();
+ let promiseVisitRemoved = PlacesTestUtils.waitForNotification(
+ "page-removed",
+ events => events[0].url === TEST_URL
+ );
+ let expectedResultCount = UrlbarTestUtils.getResultCount(window) - 1;
+
+ await UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "R");
+ const removeEvents = await promiseVisitRemoved;
+ Assert.ok(
+ removeEvents[0].isRemovedFromStore,
+ "isRemovedFromStore should be true"
+ );
+ await TestUtils.waitForCondition(
+ () => UrlbarTestUtils.getResultCount(window) == expectedResultCount,
+ "Waiting for the result to disappear"
+ );
+ for (let i = 0; i < expectedResultCount; i++) {
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.notEqual(
+ details.url,
+ TEST_URL,
+ "Should not find the test URL in the remaining results"
+ );
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+add_task(async function test_remove_search_history() {
+ await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
+ let engine = Services.search.getEngineByName("Example");
+ await Services.search.moveEngine(engine, 0);
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.searches", true],
+ ["browser.urlbar.maxHistoricalSearchSuggestions", 1],
+ ],
+ });
+
+ let formHistoryValue = "foobar";
+ await UrlbarTestUtils.formHistory.add([formHistoryValue]);
+
+ let formHistory = (
+ await UrlbarTestUtils.formHistory.search({
+ value: formHistoryValue,
+ })
+ ).map(entry => entry.value);
+ Assert.deepEqual(
+ formHistory,
+ [formHistoryValue],
+ "Should find form history after adding it"
+ );
+
+ let promiseRemoved = UrlbarTestUtils.formHistory.promiseChanged("remove");
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+
+ let resultIndex = 1;
+ let count = UrlbarTestUtils.getResultCount(window);
+ for (; resultIndex < count; resultIndex++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ resultIndex
+ );
+ if (
+ result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
+ result.source == UrlbarUtils.RESULT_SOURCE.HISTORY
+ ) {
+ break;
+ }
+ }
+ Assert.ok(resultIndex < count, "Result found");
+
+ await UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "R", {
+ resultIndex,
+ });
+ await promiseRemoved;
+
+ await TestUtils.waitForCondition(
+ () => UrlbarTestUtils.getResultCount(window) == count - 1,
+ "Waiting for the result to disappear"
+ );
+
+ for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.ok(
+ result.type != UrlbarUtils.RESULT_TYPE.SEARCH ||
+ result.source != UrlbarUtils.RESULT_SOURCE.HISTORY,
+ "Should not find the form history result in the remaining results"
+ );
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ formHistory = (
+ await UrlbarTestUtils.formHistory.search({
+ value: formHistoryValue,
+ })
+ ).map(entry => entry.value);
+ Assert.deepEqual(
+ formHistory,
+ [],
+ "Should not find form history after removing it"
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function firefoxSuggest() {
+ const url = "https://example.com/hey-there";
+ const helpUrl = "https://example.com/help";
+ let provider = new UrlbarTestUtils.TestProvider({
+ priority: Infinity,
+ results: [
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ url,
+ isBlockable: true,
+ blockL10n: { id: "urlbar-result-menu-dismiss-firefox-suggest" },
+ helpUrl,
+ helpL10n: {
+ id: "urlbar-result-menu-learn-more-about-firefox-suggest",
+ },
+ }
+ ),
+ ],
+ });
+
+ // Implement the provider's `onEngagement()` so it removes the result.
+ let onEngagementCallCount = 0;
+ provider.onEngagement = (isPrivate, state, queryContext, details) => {
+ onEngagementCallCount++;
+ queryContext.view.controller.removeResult(details.result);
+ };
+
+ UrlbarProvidersManager.registerProvider(provider);
+
+ async function openResults() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 1,
+ "There should be one result"
+ );
+
+ let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+ Assert.equal(
+ row.result.payload.url,
+ url,
+ "The result should be in the first row"
+ );
+ }
+
+ await openResults();
+ let tabOpenPromise = BrowserTestUtils.waitForNewTab(gBrowser, helpUrl);
+ await UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "L", {
+ resultIndex: 0,
+ });
+ info("Waiting for help URL to load in a new tab");
+ await tabOpenPromise;
+ gBrowser.removeCurrentTab();
+
+ await openResults();
+ await UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "D", {
+ resultIndex: 0,
+ });
+
+ Assert.greater(
+ onEngagementCallCount,
+ 0,
+ "onEngagement() should have been called"
+ );
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 0,
+ "There should be no results after blocking"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_result_onSelection.js b/browser/components/urlbar/tests/browser/browser_result_onSelection.js
new file mode 100644
index 0000000000..18c16a3072
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_result_onSelection.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test() {
+ let results = [
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: "http://mozilla.org/1" }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: "http://mozilla.org/2" }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.TIP,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ helpUrl: "http://example.com/",
+ type: "test",
+ titleL10n: { id: "urlbar-search-tips-confirm" },
+ buttons: [
+ {
+ url: "http://example.com/",
+ l10n: { id: "urlbar-search-tips-confirm" },
+ },
+ ],
+ }
+ ),
+ ];
+
+ results[0].heuristic = true;
+
+ let selectionCount = 0;
+ let provider = new UrlbarTestUtils.TestProvider({
+ results,
+ priority: 1,
+ onSelection: (result, element) => {
+ selectionCount++;
+ },
+ });
+ UrlbarProvidersManager.registerProvider(provider);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+
+ EventUtils.synthesizeKey("KEY_Tab", {
+ repeat: UrlbarPrefs.get("resultMenu") ? 5 : 3,
+ });
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ ok(
+ UrlbarTestUtils.getOneOffSearchButtons(window).selectedButton,
+ "a one off button is selected"
+ );
+
+ Assert.equal(
+ selectionCount,
+ UrlbarPrefs.get("resultMenu") ? 6 : 4,
+ "Number of elements selected in the view."
+ );
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js b/browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js
new file mode 100644
index 0000000000..22ec47403e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js
@@ -0,0 +1,435 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests retained results.
+// When there is a pending search (user typed a search string and blurred
+// without picking a result), on focus we should the search results again.
+
+async function checkPanelStatePersists(win, isOpen) {
+ // Check for popup events, we should not see any of them because the urlbar
+ // popup state should not change. This also ensures we don't cause flickering
+ // open/close actions.
+ function handler(event) {
+ Assert.ok(false, `Received unexpected event ${event.type}`);
+ }
+ win.gURLBar.addEventListener("popupshowing", handler);
+ win.gURLBar.addEventListener("popuphiding", handler);
+ // Because the panel opening may not be immediate, we must wait a bit.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 300));
+ win.gURLBar.removeEventListener("popupshowing", handler);
+ win.gURLBar.removeEventListener("popuphiding", handler);
+ Assert.equal(
+ isOpen,
+ win.gURLBar.view.isOpen,
+ `check urlbar remains ${isOpen ? "open" : "closed"}`
+ );
+}
+
+async function checkOpensOnFocus(win, state) {
+ Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open");
+ win.gURLBar.blur();
+
+ info("Check the keyboard shortcut.");
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ });
+
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ Assert.equal(state.selectionStart, win.gURLBar.selectionStart);
+ Assert.equal(state.selectionEnd, win.gURLBar.selectionEnd);
+
+ await UrlbarTestUtils.promisePopupClose(win, () => {
+ win.gURLBar.blur();
+ });
+ info("Focus with the mouse.");
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {}, win);
+ });
+
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ Assert.equal(state.selectionStart, win.gURLBar.selectionStart);
+ Assert.equal(state.selectionEnd, win.gURLBar.selectionEnd);
+}
+
+async function checkDoesNotOpenOnFocus(win) {
+ Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open");
+ win.gURLBar.blur();
+
+ info("Check the keyboard shortcut.");
+ let promiseState = checkPanelStatePersists(win, false);
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ await promiseState;
+ win.gURLBar.blur();
+ info("Focus with the mouse.");
+ promiseState = checkPanelStatePersists(win, false);
+ EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {}, win);
+ await promiseState;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.autoFill", true]],
+ });
+ // Add some history for the empty panel and autofill.
+ await PlacesTestUtils.addVisits([
+ {
+ uri: "https://example.com/",
+ transition: PlacesUtils.history.TRANSITIONS.TYPED,
+ },
+ {
+ uri: "https://example.com/foo/",
+ transition: PlacesUtils.history.TRANSITIONS.TYPED,
+ },
+ ]);
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+async function test_window(win) {
+ for (let url of ["about:newtab", "about:home", "https://example.com/"]) {
+ // withNewTab may hang on preloaded pages, thus instead of waiting for load
+ // we just wait for the expected currentURI value.
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: win.gBrowser, url, waitForLoad: false },
+ async browser => {
+ await TestUtils.waitForCondition(
+ () => win.gBrowser.currentURI.spec == url,
+ "Ensure we're on the expected page"
+ );
+
+ // In one case use a value that triggers autofill.
+ let autofill = url == "https://example.com/";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: autofill ? "ex" : "foo",
+ fireInputEvent: true,
+ });
+ let { value, selectionStart, selectionEnd } = win.gURLBar;
+ if (!autofill) {
+ selectionStart = 0;
+ }
+ info("expected " + value + " " + selectionStart + " " + selectionEnd);
+ await UrlbarTestUtils.promisePopupClose(win, () => {
+ win.gURLBar.blur();
+ });
+
+ info("The panel should open when there's a search string");
+ await checkOpensOnFocus(win, { value, selectionStart, selectionEnd });
+ await UrlbarTestUtils.promisePopupClose(win, () => {
+ win.gURLBar.blur();
+ });
+ }
+ );
+ }
+}
+
+add_task(async function test_normalWindow() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await test_window(win);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_privateWindow() {
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ await test_window(privateWin);
+ await BrowserTestUtils.closeWindow(privateWin);
+});
+
+add_task(async function test_tabSwitch() {
+ info("Check that switching tabs reopens the view.");
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "ex",
+ fireInputEvent: true,
+ });
+ let { value, selectionStart, selectionEnd } = win.gURLBar;
+ Assert.equal(value, "example.com/", "Check autofill value");
+ Assert.ok(
+ selectionStart > 0 && selectionEnd > selectionStart,
+ "Check autofill selection"
+ );
+
+ Assert.ok(win.gURLBar.focused, "The urlbar should be focused");
+ let tab1 = win.gBrowser.selectedTab;
+
+ async function check_autofill() {
+ // The urlbar code waits for both TabSelect and the focus change, thus
+ // we can't just wait for search completion here, we have to poll for a
+ // value.
+ await TestUtils.waitForCondition(
+ () => win.gURLBar.value == "example.com/",
+ "wait for autofill value"
+ );
+ // Ensure stable results.
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ Assert.equal(selectionStart, win.gURLBar.selectionStart);
+ Assert.equal(selectionEnd, win.gURLBar.selectionEnd);
+ }
+
+ info("Open a new tab with the same search");
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(win.gBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "ex",
+ fireInputEvent: true,
+ });
+
+ info("Switch across tabs");
+ for (let tab of win.gBrowser.tabs) {
+ await UrlbarTestUtils.promisePopupOpen(win, async () => {
+ await BrowserTestUtils.switchTab(win.gBrowser, tab);
+ });
+ await check_autofill();
+ }
+
+ info("Close tab and check the view is open.");
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ BrowserTestUtils.removeTab(tab2);
+ });
+ await check_autofill();
+
+ info("Open a new tab with a different search");
+ tab2 = await BrowserTestUtils.openNewForegroundTab(win.gBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "xam",
+ fireInputEvent: true,
+ });
+
+ info("Switch to the first tab and check the panel remains open");
+ let promiseState = checkPanelStatePersists(win, true);
+ await BrowserTestUtils.switchTab(win.gBrowser, tab1);
+ await promiseState;
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ await check_autofill();
+
+ info("Switch to the second tab and check the panel remains open");
+ promiseState = checkPanelStatePersists(win, true);
+ await BrowserTestUtils.switchTab(win.gBrowser, tab2);
+ await promiseState;
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ Assert.equal(win.gURLBar.value, "xam", "check value");
+ Assert.equal(win.gURLBar.selectionStart, 3);
+ Assert.equal(win.gURLBar.selectionEnd, 3);
+
+ info("autofill in tab2, switch to tab1, then back to tab2 with the mouse");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "e",
+ fireInputEvent: true,
+ });
+ // Adjust selection start, we are using a different search string.
+ await BrowserTestUtils.switchTab(win.gBrowser, tab1);
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ await check_autofill();
+ tab2.click();
+ selectionStart = 1;
+ await check_autofill();
+
+ info("Check we don't rerun a search if the shortcut is used on an open view");
+ EventUtils.synthesizeKey("KEY_Backspace", {}, win);
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ Assert.ok(win.gURLBar.view.isOpen, "The view should be open");
+ Assert.equal(win.gURLBar.value, "e", "The value should be the typed one");
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ // A search should not run here, so there's nothing to wait for.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 300));
+ Assert.ok(win.gURLBar.view.isOpen, "The view should be open");
+ Assert.equal(win.gURLBar.value, "e", "The value should not change");
+
+ info(
+ "Tab switch from an empty search tab with unfocused urlbar to a tab with a search string and a focused urlbar"
+ );
+ win.gURLBar.value = "";
+ win.gURLBar.blur();
+ await UrlbarTestUtils.promisePopupOpen(win, async () => {
+ await BrowserTestUtils.switchTab(win.gBrowser, tab1);
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_tabSwitch_pageproxystate() {
+ info("Switching tabs on valid pageproxystate doesn't reopen.");
+
+ info("Adding some visits for the empty panel");
+ await PlacesTestUtils.addVisits([
+ "https://example.com/",
+ "https://example.org/",
+ ]);
+ registerCleanupFunction(PlacesUtils.history.clear);
+
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ BrowserTestUtils.loadURIString(win.gBrowser.selectedBrowser, "about:robots");
+ let tab1 = win.gBrowser.selectedTab;
+
+ info("Open a new tab and the empty search");
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(win.gBrowser);
+ await UrlbarTestUtils.promisePopupOpen(win, async () => {
+ win.gURLBar.focus();
+ // On Linux and Mac down moves caret to the end of the text unless it's
+ // there already.
+ win.gURLBar.selectionStart = win.gURLBar.selectionEnd =
+ win.gURLBar.value.length;
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ });
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(win, 0);
+ Assert.notEqual(result.url, "about:robots");
+
+ info("Switch to the first tab and start searching with DOWN");
+ await UrlbarTestUtils.promisePopupClose(win, async () => {
+ await BrowserTestUtils.switchTab(win.gBrowser, tab1);
+ });
+ await checkPanelStatePersists(win, false);
+ await UrlbarTestUtils.promisePopupOpen(win, async () => {
+ win.gURLBar.focus();
+ // On Linux and Mac down moves caret to the end of the text unless it's
+ // there already.
+ win.gURLBar.selectionStart = win.gURLBar.selectionEnd =
+ win.gURLBar.value.length;
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ });
+ await UrlbarTestUtils.promiseSearchComplete(win);
+
+ info("Switcihng to the second tab should not reopen the search");
+ await UrlbarTestUtils.promisePopupClose(win, async () => {
+ await BrowserTestUtils.switchTab(win.gBrowser, tab2);
+ });
+ await checkPanelStatePersists(win, false);
+
+ info("Switching to the first tab should not reopen the search");
+ let promiseState = await checkPanelStatePersists(win, false);
+ await BrowserTestUtils.switchTab(win.gBrowser, tab1);
+ await promiseState;
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_tabSwitch_emptySearch() {
+ info("Switching between empty-search tabs should not reopen the view.");
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ info("Open the empty search");
+ let tab1 = win.gBrowser.selectedTab;
+ await UrlbarTestUtils.promisePopupOpen(win, async () => {
+ win.gURLBar.focus();
+ // On Linux and Mac down moves caret to the end of the text unless it's
+ // there already.
+ win.gURLBar.selectionStart = win.gURLBar.selectionEnd =
+ win.gURLBar.value.length;
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ });
+ await UrlbarTestUtils.promiseSearchComplete(win);
+
+ info("Open a new tab and the empty search");
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(win.gBrowser);
+ await UrlbarTestUtils.promisePopupOpen(win, async () => {
+ win.gURLBar.focus();
+ // On Linux and Mac down moves caret to the end of the text unless it's
+ // there already.
+ win.gURLBar.selectionStart = win.gURLBar.selectionEnd =
+ win.gURLBar.value.length;
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ });
+ await UrlbarTestUtils.promiseSearchComplete(win);
+
+ info("Switching to the first tab should not reopen the view");
+ await UrlbarTestUtils.promisePopupClose(win, async () => {
+ await BrowserTestUtils.switchTab(win.gBrowser, tab1);
+ });
+ await checkPanelStatePersists(win, false);
+
+ info("Switching to the second tab should not reopen the view");
+ let promiseState = await checkPanelStatePersists(win, false);
+ await BrowserTestUtils.switchTab(win.gBrowser, tab2);
+ await promiseState;
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_pageproxystate_valid() {
+ info("Focusing on valid pageproxystate should not reopen the view.");
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ info("Search for a full url and confirm it with Enter");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "about:robots",
+ fireInputEvent: true,
+ });
+ let loadedPromise = BrowserTestUtils.browserLoaded(
+ win.gBrowser.selectedBrowser
+ );
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+ await loadedPromise;
+
+ Assert.ok(!win.gURLBar.focused, "The urlbar should not be focused");
+ info("Focus the urlbar");
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_allowAutofill() {
+ info("Check we respect allowAutofill from the last search");
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ await UrlbarTestUtils.promisePopupOpen(win, async () => {
+ await selectAndPaste("e", win);
+ });
+ Assert.equal(win.gURLBar.value, "e", "Should not autofill");
+ let context = await win.gURLBar.lastQueryContextPromise;
+ Assert.equal(context.allowAutofill, false, "Check initial allowAutofill");
+ await UrlbarTestUtils.promisePopupClose(win);
+
+ await UrlbarTestUtils.promisePopupOpen(win, async () => {
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ Assert.equal(win.gURLBar.value, "e", "Should not autofill");
+ context = await win.gURLBar.lastQueryContextPromise;
+ Assert.equal(context.allowAutofill, false, "Check reopened allowAutofill");
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_clicks_after_autofill() {
+ info(
+ "Check that clickin on an autofilled input field doesn't requery, causing loss of the caret position"
+ );
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ info("autofill in tab2, switch to tab1, then back to tab2 with the mouse");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "e",
+ fireInputEvent: true,
+ });
+ Assert.equal(win.gURLBar.value, "example.com/", "Should have autofilled");
+
+ // Check single click.
+ let input = win.gURLBar.inputField;
+ EventUtils.synthesizeMouse(input, 30, 10, {}, win);
+ // Wait a bit, in case of a mistake this would run a query, otherwise not.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 300));
+ Assert.ok(win.gURLBar.selectionStart < win.gURLBar.value.length);
+ Assert.equal(win.gURLBar.selectionStart, win.gURLBar.selectionEnd);
+
+ // Check double click.
+ EventUtils.synthesizeMouse(input, 30, 10, { clickCount: 2 }, win);
+ // Wait a bit, in case of a mistake this would run a query, otherwise not.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 300));
+ Assert.ok(win.gURLBar.selectionStart < win.gURLBar.value.length);
+ Assert.ok(win.gURLBar.selectionEnd > win.gURLBar.selectionStart);
+
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_revert.js b/browser/components/urlbar/tests/browser/browser_revert.js
new file mode 100644
index 0000000000..b68ad0ff91
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_revert.js
@@ -0,0 +1,33 @@
+// Test reverting the urlbar value with ESC after a tab switch.
+
+add_task(async function () {
+ registerCleanupFunction(PlacesUtils.history.clear);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "http://example.com",
+ },
+ async function (browser) {
+ let originalValue = gURLBar.value;
+ let tab = gBrowser.selectedTab;
+ info("Put a typed value.");
+ gBrowser.userTypedValue = "foobar";
+ info("Switch tabs.");
+ gBrowser.selectedTab = gBrowser.tabs[0];
+ gBrowser.selectedTab = tab;
+ Assert.equal(
+ gURLBar.value,
+ "foobar",
+ "location bar displays typed value"
+ );
+
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_Escape");
+ Assert.equal(
+ gURLBar.value,
+ originalValue,
+ "ESC reverted the location bar value"
+ );
+ }
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchFunction.js b/browser/components/urlbar/tests/browser/browser_searchFunction.js
new file mode 100644
index 0000000000..0a272f9f01
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchFunction.js
@@ -0,0 +1,278 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test checks the urlbar.search() function.
+
+"use strict";
+
+const ALIAS = "@enginealias";
+let aliasEngine;
+
+add_setup(async function () {
+ // Run this in a new tab, to ensure all the locationchange notifications have
+ // fired.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ await SearchTestUtils.installSearchExtension({
+ keyword: ALIAS,
+ });
+ aliasEngine = Services.search.getEngineByName("Example");
+
+ registerCleanupFunction(async function () {
+ BrowserTestUtils.removeTab(tab);
+ gURLBar.handleRevert();
+ });
+});
+
+// Calls search() with a normal, non-"@engine" search-string argument.
+add_task(async function basic() {
+ gURLBar.blur();
+ gURLBar.search("basic");
+ ok(gURLBar.hasAttribute("focused"), "url bar is focused");
+ await assertUrlbarValue("basic");
+
+ assertOneOffButtonsVisible(true);
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// Calls search() with an invalid "@engine" search engine alias so that the
+// one-off search buttons are disabled.
+add_task(async function searchEngineAlias() {
+ gURLBar.blur();
+ await UrlbarTestUtils.promisePopupOpen(window, () =>
+ gURLBar.search("@example")
+ );
+ ok(gURLBar.hasAttribute("focused"), "url bar is focused");
+ UrlbarTestUtils.assertSearchMode(window, null);
+ await assertUrlbarValue("@example");
+
+ assertOneOffButtonsVisible(false);
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+
+ // Open the popup again (by doing another search) to make sure the one-off
+ // buttons are shown -- i.e., that we didn't accidentally break them.
+ await UrlbarTestUtils.promisePopupOpen(window, () =>
+ gURLBar.search("not an engine alias")
+ );
+ await assertUrlbarValue("not an engine alias");
+ assertOneOffButtonsVisible(true);
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+add_task(async function searchRestriction() {
+ gURLBar.blur();
+ await UrlbarTestUtils.promisePopupOpen(window, () =>
+ gURLBar.search(UrlbarTokenizer.RESTRICT.SEARCH)
+ );
+ ok(gURLBar.hasAttribute("focused"), "url bar is focused");
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: UrlbarSearchUtils.getDefaultEngine().name,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ // Entry is "other" because we didn't pass searchModeEntry to search().
+ entry: "other",
+ });
+ assertOneOffButtonsVisible(true);
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+add_task(async function historyRestriction() {
+ gURLBar.blur();
+ await UrlbarTestUtils.promisePopupOpen(window, () =>
+ gURLBar.search(UrlbarTokenizer.RESTRICT.HISTORY)
+ );
+ ok(gURLBar.hasAttribute("focused"), "url bar is focused");
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ entry: "other",
+ });
+ assertOneOffButtonsVisible(true);
+ Assert.ok(!gURLBar.value, "The Urlbar has no value.");
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+add_task(async function historyRestrictionWithString() {
+ gURLBar.blur();
+ // The leading and trailing spaces are intentional to verify that search()
+ // preserves them.
+ let searchString = " foo bar ";
+ await UrlbarTestUtils.promisePopupOpen(window, () =>
+ gURLBar.search(`${UrlbarTokenizer.RESTRICT.HISTORY} ${searchString}`)
+ );
+ ok(gURLBar.hasAttribute("focused"), "url bar is focused");
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ entry: "other",
+ });
+ // We don't use assertUrlbarValue here since we expect to open a local search
+ // mode. In those modes, we don't show a heuristic search result, which
+ // assertUrlbarValue checks for.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.equal(
+ gURLBar.value,
+ searchString,
+ "The Urlbar value should be the search string."
+ );
+ assertOneOffButtonsVisible(true);
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+add_task(async function tagRestriction() {
+ gURLBar.blur();
+ await UrlbarTestUtils.promisePopupOpen(window, () =>
+ gURLBar.search(UrlbarTokenizer.RESTRICT.TAG)
+ );
+ ok(gURLBar.hasAttribute("focused"), "url bar is focused");
+ // Since tags are not a supported search mode, we should just insert the tag
+ // restriction token and not enter search mode.
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ await assertUrlbarValue(`${UrlbarTokenizer.RESTRICT.TAG} `);
+ assertOneOffButtonsVisible(true);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Calls search() twice with the same value. The popup should reopen.
+add_task(async function searchTwice() {
+ gURLBar.blur();
+ await UrlbarTestUtils.promisePopupOpen(window, () => gURLBar.search("test"));
+ ok(gURLBar.hasAttribute("focused"), "url bar is focused");
+ await assertUrlbarValue("test");
+ assertOneOffButtonsVisible(true);
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ await UrlbarTestUtils.promisePopupOpen(window, () => gURLBar.search("test"));
+ ok(gURLBar.hasAttribute("focused"), "url bar is focused");
+ await assertUrlbarValue("test");
+ assertOneOffButtonsVisible(true);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Calls search() during an IME composition.
+add_task(async function searchIME() {
+ // First run a search.
+ gURLBar.blur();
+ await UrlbarTestUtils.promisePopupOpen(window, () => gURLBar.search("test"));
+ ok(gURLBar.hasAttribute("focused"), "url bar is focused");
+ await assertUrlbarValue("test");
+ // Start composition.
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeComposition({ type: "compositionstart" })
+ );
+
+ gURLBar.search("test");
+ // Unfortunately there's no other way to check we don't open the view than to
+ // wait for it.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ ok(!UrlbarTestUtils.isPopupOpen(window), "The panel should still be closed");
+
+ await UrlbarTestUtils.promisePopupOpen(window, () =>
+ EventUtils.synthesizeComposition({ type: "compositioncommitasis" })
+ );
+
+ assertOneOffButtonsVisible(true);
+
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Calls search() with an engine alias.
+add_task(async function searchWithAlias() {
+ await UrlbarTestUtils.promisePopupOpen(window, async () =>
+ gURLBar.search(`${ALIAS} test`, {
+ searchEngine: aliasEngine,
+ searchModeEntry: "topsites_urlbar",
+ })
+ );
+ Assert.ok(gURLBar.hasAttribute("focused"), "Urlbar is focused");
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: aliasEngine.name,
+ entry: "topsites_urlbar",
+ });
+ await assertUrlbarValue("test");
+ assertOneOffButtonsVisible(true);
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Calls search() and passes in a search engine without including a restriction
+// token or engine alias in the search string. Simulates pasting into the newtab
+// handoff field with search suggestions disabled.
+add_task(async function searchEngineWithNoToken() {
+ await UrlbarTestUtils.promisePopupOpen(window, async () =>
+ gURLBar.search("no-alias", {
+ searchEngine: aliasEngine,
+ searchModeEntry: "handoff",
+ })
+ );
+
+ Assert.ok(gURLBar.hasAttribute("focused"), "Urlbar is focused");
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: aliasEngine.name,
+ entry: "handoff",
+ });
+ await assertUrlbarValue("no-alias");
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+/**
+ * Asserts that the one-off search buttons are or aren't visible.
+ *
+ * @param {boolean} visible
+ * True if they should be visible, false if not.
+ */
+function assertOneOffButtonsVisible(visible) {
+ Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ visible,
+ "Should show or not the one-off search buttons"
+ );
+}
+
+/**
+ * Asserts that the urlbar's input value is the given value. Also asserts that
+ * the first (heuristic) result in the popup is a search suggestion whose search
+ * query is the given value.
+ *
+ * @param {string} value
+ * The urlbar's expected value.
+ */
+async function assertUrlbarValue(value) {
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+
+ Assert.equal(gURLBar.value, value);
+ Assert.greater(
+ UrlbarTestUtils.getResultCount(window),
+ 0,
+ "Should have at least one result"
+ );
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Should have type search for the first result"
+ );
+ // Strip search restriction token from value.
+ if (value[0] == UrlbarTokenizer.RESTRICT.SEARCH) {
+ value = value.substring(1).trim();
+ }
+ Assert.equal(
+ result.searchParams.query,
+ value,
+ "Should have the correct query for the first result"
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/browser_searchHistoryLimit.js b/browser/components/urlbar/tests/browser/browser_searchHistoryLimit.js
new file mode 100644
index 0000000000..6fcde0882b
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchHistoryLimit.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test checks that search values longer than
+ * SearchSuggestionController.SEARCH_HISTORY_MAX_VALUE_LENGTH are not added to
+ * search history.
+ */
+
+"use strict";
+
+const { SearchSuggestionController } = ChromeUtils.importESModule(
+ "resource://gre/modules/SearchSuggestionController.sys.mjs"
+);
+
+let gEngine;
+
+add_setup(async function () {
+ await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
+ gEngine = Services.search.getEngineByName("Example");
+ await UrlbarTestUtils.formHistory.clear();
+
+ registerCleanupFunction(async function () {
+ await UrlbarTestUtils.formHistory.clear();
+ });
+});
+
+add_task(async function sanityCheckShortString() {
+ const shortString = new Array(
+ SearchSuggestionController.SEARCH_HISTORY_MAX_VALUE_LENGTH
+ )
+ .fill("a")
+ .join("");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: shortString,
+ });
+ let url = gEngine.getSubmission(shortString).uri.spec;
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ url
+ );
+ let addPromise = UrlbarTestUtils.formHistory.promiseChanged("add");
+ EventUtils.synthesizeKey("VK_RETURN");
+ await Promise.all([loadPromise, addPromise]);
+
+ let formHistory = (
+ await UrlbarTestUtils.formHistory.search({ source: gEngine.name })
+ ).map(entry => entry.value);
+ Assert.deepEqual(
+ formHistory,
+ [shortString],
+ "Should find form history after adding it"
+ );
+
+ await UrlbarTestUtils.formHistory.clear();
+});
+
+add_task(async function urlbar_checkLongString() {
+ const longString = new Array(
+ SearchSuggestionController.SEARCH_HISTORY_MAX_VALUE_LENGTH + 1
+ )
+ .fill("a")
+ .join("");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: longString,
+ });
+ let url = gEngine.getSubmission(longString).uri.spec;
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ url
+ );
+ EventUtils.synthesizeKey("VK_RETURN");
+ await loadPromise;
+ // There's nothing we can wait for, since addition should not be happening.
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ await new Promise(resolve => setTimeout(resolve, 500));
+ let formHistory = (
+ await UrlbarTestUtils.formHistory.search({ source: gEngine.name })
+ ).map(entry => entry.value);
+ Assert.deepEqual(formHistory, [], "Should not find form history");
+
+ await UrlbarTestUtils.formHistory.clear();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_alias_replacement.js b/browser/components/urlbar/tests/browser/browser_searchMode_alias_replacement.js
new file mode 100644
index 0000000000..9f4558e6c9
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_alias_replacement.js
@@ -0,0 +1,274 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that user-defined aliases are replaced by the search mode indicator.
+ */
+
+const ALIAS = "testalias";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+// We make sure that aliases and search terms are correctly recognized when they
+// are separated by each of these different types of spaces and combinations of
+// spaces. U+3000 is the ideographic space in CJK and is commonly used by CJK
+// speakers.
+const TEST_SPACES = [" ", "\u3000", " \u3000", "\u3000 "];
+
+let defaultEngine, aliasEngine;
+
+add_setup(async function () {
+ defaultEngine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ setAsDefault: true,
+ });
+ defaultEngine.alias = "@default";
+ await SearchTestUtils.installSearchExtension({
+ keyword: ALIAS,
+ });
+ aliasEngine = Services.search.getEngineByName("Example");
+});
+
+// An incomplete alias should not be replaced.
+add_task(async function incompleteAlias() {
+ // Check that a non-fully typed alias is not replaced.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS.slice(0, -1),
+ });
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ // Type a space just to make sure it's not replaced.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey(" ");
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ Assert.equal(
+ gURLBar.value,
+ ALIAS.slice(0, -1) + " ",
+ "The typed value should be unchanged except for the space."
+ );
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// A complete alias without a trailing space should not be replaced.
+add_task(async function noTrailingSpace() {
+ let value = ALIAS;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ });
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// A complete typed alias without a trailing space should not be replaced.
+add_task(async function noTrailingSpace_typed() {
+ // Start by searching for the alias minus its last char.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS.slice(0, -1),
+ });
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ // Now type the last char.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey(ALIAS.slice(-1));
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ Assert.equal(
+ gURLBar.value,
+ ALIAS,
+ "The typed value should be the full alias."
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// A complete alias with a trailing space should be replaced.
+add_task(async function trailingSpace() {
+ for (let spaces of TEST_SPACES) {
+ info("Testing: " + JSON.stringify({ spaces: codePoints(spaces) }));
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS + spaces,
+ });
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: aliasEngine.name,
+ entry: "typed",
+ });
+ Assert.ok(!gURLBar.value, "The urlbar value should be cleared.");
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ }
+});
+
+// A complete alias should be replaced after typing a space.
+add_task(async function trailingSpace_typed() {
+ for (let spaces of TEST_SPACES) {
+ if (spaces.length != 1) {
+ continue;
+ }
+ info("Testing: " + JSON.stringify({ spaces: codePoints(spaces) }));
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS,
+ });
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ // We need to wait for two searches: The first enters search mode, the second
+ // does the search in search mode.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey(spaces);
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: aliasEngine.name,
+ entry: "typed",
+ });
+ Assert.ok(!gURLBar.value, "The urlbar value should be cleared.");
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ }
+});
+
+// A complete alias with a trailing space should be replaced, and the query
+// after the trailing space should be the new value of the input.
+add_task(async function trailingSpace_query() {
+ for (let spaces of TEST_SPACES) {
+ info("Testing: " + JSON.stringify({ spaces: codePoints(spaces) }));
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS + spaces + "query",
+ });
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: aliasEngine.name,
+ entry: "typed",
+ });
+ Assert.equal(
+ gURLBar.value,
+ "query",
+ "The urlbar value should be the query."
+ );
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ }
+});
+
+add_task(async function () {
+ info("Test search mode when typing an alias after selecting one-off button");
+
+ info("Open the result popup");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+
+ info("Select one of one-off button");
+ const oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window);
+ await TestUtils.waitForCondition(
+ () => !oneOffs._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ ok(oneOffs.selectedButton, "There is a selected one-off button");
+ const selectedEngine = oneOffs.selectedButton.engine;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: selectedEngine.name,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ entry: "oneoff",
+ isPreview: true,
+ });
+
+ info("Type a search engine alias and query");
+ const inputString = "@default query";
+ inputString.split("").forEach(c => EventUtils.synthesizeKey(c));
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.equal(
+ gURLBar.value,
+ inputString,
+ "Alias and query is inputed correctly to the urlbar"
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: selectedEngine.name,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ entry: "oneoff",
+ });
+
+ // When starting typing, as the search mode is confirmed, the one-off
+ // selection is removed.
+ ok(!oneOffs.selectedButton, "There is no any selected one-off button");
+
+ // Clean up
+ gURLBar.value = "";
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+add_task(async function () {
+ info(
+ "Test search mode after removing current search mode when multiple aliases are written"
+ );
+
+ info("Open the result popup with multiple aliases");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@default testalias @default",
+ });
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: defaultEngine.name,
+ entry: "typed",
+ });
+ Assert.equal(
+ gURLBar.value,
+ "testalias @default",
+ "The value on the urlbar is correct"
+ );
+
+ info("Exit search mode by clicking");
+ const indicator = gURLBar.querySelector("#urlbar-search-mode-indicator");
+ EventUtils.synthesizeMouseAtCenter(indicator, { type: "mouseover" }, window);
+ const closeButton = gURLBar.querySelector(
+ "#urlbar-search-mode-indicator-close"
+ );
+ const searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(closeButton, {}, window);
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: aliasEngine.name,
+ entry: "typed",
+ });
+ Assert.equal(gURLBar.value, "@default", "The value on the urlbar is correct");
+
+ // Clean up
+ gURLBar.value = "";
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+/**
+ * Returns an array of code points in the given string. Each code point is
+ * returned as a hexidecimal string.
+ *
+ * @param {string} str
+ * The code points of this string will be returned.
+ * @returns {Array}
+ * Array of code points in the string, where each is a hexidecimal string.
+ */
+function codePoints(str) {
+ return str.split("").map(s => s.charCodeAt(0).toString(16));
+}
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_autofill.js b/browser/components/urlbar/tests/browser/browser_searchMode_autofill.js
new file mode 100644
index 0000000000..3186d96b92
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_autofill.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that autofill is cleared if a remote search mode is entered but still
+ * works for local search modes.
+ */
+
+"use strict";
+
+add_setup(async function () {
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits([{ uri: "http://example.com/" }]);
+ }
+
+ await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
+ let defaultEngine = Services.search.getEngineByName("Example");
+ await Services.search.moveEngine(defaultEngine, 0);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.quickactions", false]],
+ });
+
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+// Tests that autofill is cleared when entering a remote search mode and that
+// autofill doesn't happen when in that mode.
+add_task(async function remote() {
+ info("Sanity check: we autofill in a normal search.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ex",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill, "We're autofilling.");
+ Assert.equal(
+ gURLBar.value,
+ "example.com/",
+ "Urlbar contains the autofilled URL."
+ );
+ info("Enter remote search mode and check autofill is cleared.");
+ await UrlbarTestUtils.enterSearchMode(window);
+ Assert.equal(gURLBar.value, "ex", "Urlbar contains the typed string.");
+
+ info("Continue typing and check that we're not autofilling.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exa",
+ fireInputEvent: true,
+ });
+
+ details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(!details.autofill, "We're not autofilling.");
+ Assert.equal(gURLBar.value, "exa", "Urlbar contains the typed string.");
+
+ info("Exit remote search mode and check that we now autofill.");
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill, "We're autofilling.");
+ Assert.equal(
+ gURLBar.value,
+ "example.com/",
+ "Urlbar contains the typed string."
+ );
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+});
+
+// Tests that autofill works as normal when entering and when in a local search
+// mode.
+add_task(async function local() {
+ info("Sanity check: we autofill in a normal search.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ex",
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill, "We're autofilling.");
+ Assert.equal(
+ gURLBar.value,
+ "example.com/",
+ "Urlbar contains the autofilled URL."
+ );
+ info("Enter local search mode and check autofill is preserved.");
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ });
+ Assert.equal(
+ gURLBar.value,
+ "example.com/",
+ "Urlbar contains the autofilled URL."
+ );
+
+ info("Continue typing and check that we're autofilling.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exa",
+ fireInputEvent: true,
+ });
+
+ details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill, "We're autofilling.");
+ Assert.equal(
+ gURLBar.value,
+ "example.com/",
+ "Urlbar contains the autofilled URL."
+ );
+
+ info("Exit local search mode and check that nothing has changed.");
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill, "We're autofilling.");
+ Assert.equal(
+ gURLBar.value,
+ "example.com/",
+ "Urlbar contains the typed string."
+ );
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_clickLink.js b/browser/components/urlbar/tests/browser/browser_searchMode_clickLink.js
new file mode 100644
index 0000000000..d037c77bbb
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_clickLink.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that search mode is exited after clicking a link and loading a page in
+ * the current tab.
+ */
+
+"use strict";
+
+const LINK_PAGE_URL =
+ "http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/dummy_page.html";
+
+// Opens a new tab containing a link, enters search mode, and clicks the link.
+// Uses a variety of search strings and link hrefs in order to hit different
+// branches in setURI. Search mode should be exited in all cases, and the href
+// in the link should be opened.
+add_task(async function clickLink() {
+ for (let test of [
+ // searchString, href to use in the link
+ [LINK_PAGE_URL, LINK_PAGE_URL],
+ [LINK_PAGE_URL, "http://www.example.com/"],
+ ["test", LINK_PAGE_URL],
+ ["test", "http://www.example.com/"],
+ [null, LINK_PAGE_URL],
+ [null, "http://www.example.com/"],
+ ]) {
+ await doClickLinkTest(...test);
+ }
+});
+
+async function doClickLinkTest(searchString, href) {
+ info(
+ "doClickLinkTest with args: " +
+ JSON.stringify({
+ searchString,
+ href,
+ })
+ );
+
+ await BrowserTestUtils.withNewTab(LINK_PAGE_URL, async () => {
+ if (searchString) {
+ // Do a search with the search string.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ Assert.ok(
+ gBrowser.selectedBrowser.userTypedValue,
+ "userTypedValue should be defined"
+ );
+ } else {
+ // Open top sites.
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ Assert.strictEqual(
+ gBrowser.selectedBrowser.userTypedValue,
+ null,
+ "userTypedValue should be null"
+ );
+ }
+
+ // Enter search mode and then close the popup so we can click the link in
+ // the page.
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ });
+
+ // Add a link to the page and click it.
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ await ContentTask.spawn(gBrowser.selectedBrowser, href, async cHref => {
+ let link = this.content.document.createElement("a");
+ link.textContent = "Click me";
+ link.href = cHref;
+ this.content.document.body.append(link);
+ link.click();
+ });
+ await loadPromise;
+ Assert.equal(
+ gBrowser.currentURI.spec,
+ href,
+ "Should have loaded the href URL"
+ );
+
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_engineRemoval.js b/browser/components/urlbar/tests/browser/browser_searchMode_engineRemoval.js
new file mode 100644
index 0000000000..f5eab77789
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_engineRemoval.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that we exit search mode when the search mode engine is removed.
+ */
+
+"use strict";
+
+// Tests that we exit search mode in the active tab when the search mode engine
+// is removed.
+add_task(async function activeTab() {
+ let extension = await SearchTestUtils.installSearchExtension(
+ {},
+ { skipUnload: true }
+ );
+ let engine = Services.search.getEngineByName("Example");
+ await Services.search.moveEngine(engine, 0);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ex",
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ // Sanity check: we are in the correct search mode.
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: engine.name,
+ entry: "oneoff",
+ });
+ await extension.unload();
+ // Check that we are no longer in search mode.
+ await UrlbarTestUtils.assertSearchMode(window, null);
+});
+
+// Tests that we exit search mode in a background tab when the search mode
+// engine is removed.
+add_task(async function backgroundTab() {
+ let extension = await SearchTestUtils.installSearchExtension(
+ {},
+ { skipUnload: true }
+ );
+ let engine = Services.search.getEngineByName("Example");
+ await Services.search.moveEngine(engine, 0);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ex",
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ let tab1 = gBrowser.selectedTab;
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // Sanity check: tab1 is still in search mode.
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: engine.name,
+ entry: "oneoff",
+ });
+
+ // Switch back to tab2 so tab1 is in the background when the engine is
+ // removed.
+ await BrowserTestUtils.switchTab(gBrowser, tab2);
+ // tab2 shouldn't be in search mode.
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ await extension.unload();
+
+ // tab1 should have exited search mode.
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+// Tests that we exit search mode in a background window when the search mode
+// engine is removed.
+add_task(async function backgroundWindow() {
+ let extension = await SearchTestUtils.installSearchExtension(
+ {},
+ { skipUnload: true }
+ );
+ let engine = Services.search.getEngineByName("Example");
+ await Services.search.moveEngine(engine, 0);
+
+ let win1 = window;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win1,
+ value: "ex",
+ });
+ await UrlbarTestUtils.enterSearchMode(win1);
+ let win2 = await BrowserTestUtils.openNewBrowserWindow();
+
+ // Sanity check: win1 is still in search mode.
+ win1.focus();
+ await UrlbarTestUtils.assertSearchMode(win1, {
+ engineName: engine.name,
+ entry: "oneoff",
+ });
+
+ // Switch back to win2 so win1 is in the background when the engine is
+ // removed.
+ win2.focus();
+ // win2 shouldn't be in search mode.
+ await UrlbarTestUtils.assertSearchMode(win2, null);
+ await extension.unload();
+
+ // win1 should not be in search mode.
+ win1.focus();
+ await UrlbarTestUtils.assertSearchMode(win1, null);
+ await BrowserTestUtils.closeWindow(win2);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_excludeResults.js b/browser/components/urlbar/tests/browser/browser_searchMode_excludeResults.js
new file mode 100644
index 0000000000..0e9471280e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_excludeResults.js
@@ -0,0 +1,217 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that results with hostnames other than the search mode engine are not
+ * shown.
+ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs",
+});
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.searches", false],
+ ["browser.urlbar.autoFill", false],
+ // Special prefs for remote tabs.
+ ["services.sync.username", "fake"],
+ ["services.sync.syncedTabs.showRemoteTabs", true],
+ ],
+ });
+
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ // Note that the result domain is subdomain.example.ca. We still expect to
+ // match with example.com results because we ignore subdomains and the public
+ // suffix in this check.
+ await SearchTestUtils.installSearchExtension(
+ {
+ search_url: "https://subdomain.example.ca/",
+ },
+ { setAsDefault: true }
+ );
+ let engine = Services.search.getEngineByName("Example");
+ await Services.search.moveEngine(engine, 0);
+
+ const REMOTE_TAB = {
+ id: "7cqCr77ptzX3",
+ type: "client",
+ lastModified: 1492201200,
+ name: "Nightly on MacBook-Pro",
+ clientType: "desktop",
+ tabs: [
+ {
+ type: "tab",
+ title: "Test Remote",
+ url: "https://example.com",
+ icon: UrlbarUtils.ICON.DEFAULT,
+ client: "7cqCr77ptzX3",
+ lastUsed: Math.floor(Date.now() / 1000),
+ },
+ {
+ type: "tab",
+ title: "Test Remote 2",
+ url: "https://example-2.com",
+ icon: UrlbarUtils.ICON.DEFAULT,
+ client: "7cqCr77ptzX3",
+ lastUsed: Math.floor(Date.now() / 1000),
+ },
+ ],
+ };
+
+ const sandbox = sinon.createSandbox();
+
+ let originalSyncedTabsInternal = SyncedTabs._internal;
+ SyncedTabs._internal = {
+ isConfiguredToSyncTabs: true,
+ hasSyncedThisSession: true,
+ getTabClients() {
+ return Promise.resolve([]);
+ },
+ syncTabs() {
+ return Promise.resolve();
+ },
+ };
+
+ // Tell the Sync XPCOM service it is initialized.
+ let weaveXPCService = Cc["@mozilla.org/weave/service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ let oldWeaveServiceReady = weaveXPCService.ready;
+ weaveXPCService.ready = true;
+
+ sandbox
+ .stub(SyncedTabs._internal, "getTabClients")
+ .callsFake(() => Promise.resolve(Cu.cloneInto([REMOTE_TAB], {})));
+
+ // Reset internal cache in UrlbarProviderRemoteTabs.
+ Services.obs.notifyObservers(null, "weave:engine:sync:finish", "tabs");
+
+ registerCleanupFunction(async function () {
+ sandbox.restore();
+ weaveXPCService.ready = oldWeaveServiceReady;
+ SyncedTabs._internal = originalSyncedTabsInternal;
+ await PlacesUtils.history.clear();
+ });
+});
+
+add_task(async function basic() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "example",
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 3,
+ "We have three results"
+ );
+ let firstResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ firstResult.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "The first result is the heuristic search result."
+ );
+ let secondResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ secondResult.type,
+ UrlbarUtils.RESULT_TYPE.REMOTE_TAB,
+ "The second result is a remote tab."
+ );
+ let thirdResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ thirdResult.type,
+ UrlbarUtils.RESULT_TYPE.REMOTE_TAB,
+ "The third result is a remote tab."
+ );
+
+ await UrlbarTestUtils.enterSearchMode(window);
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "We have two results. The second remote tab result is excluded despite matching the search string."
+ );
+ firstResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ firstResult.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "The first result is the heuristic search result."
+ );
+ secondResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ secondResult.type,
+ UrlbarUtils.RESULT_TYPE.REMOTE_TAB,
+ "The second result is a remote tab."
+ );
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// For engines with an invalid TLD, we filter on the entire domain.
+add_task(async function malformedEngine() {
+ await SearchTestUtils.installSearchExtension({
+ name: "TestMalformed",
+ search_url: "https://example.foobar/",
+ });
+ let badEngine = Services.search.getEngineByName("TestMalformed");
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "example",
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 4,
+ "We have four results"
+ );
+ let firstResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ firstResult.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "The first result is the heuristic search result."
+ );
+ let secondResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ secondResult.type,
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ "The second result is the tab-to-search onboarding result for our malformed engine."
+ );
+ let thirdResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(
+ thirdResult.type,
+ UrlbarUtils.RESULT_TYPE.REMOTE_TAB,
+ "The third result is a remote tab."
+ );
+ let fourthResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 3);
+ Assert.equal(
+ fourthResult.type,
+ UrlbarUtils.RESULT_TYPE.REMOTE_TAB,
+ "The fourth result is a remote tab."
+ );
+
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: badEngine.name,
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 1,
+ "We only have one result."
+ );
+ firstResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(firstResult.heuristic, "The first result is heuristic.");
+ Assert.equal(
+ firstResult.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "The first result is the heuristic search result."
+ );
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_heuristic.js b/browser/components/urlbar/tests/browser/browser_searchMode_heuristic.js
new file mode 100644
index 0000000000..bd8f00a512
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_heuristic.js
@@ -0,0 +1,221 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests heuristic results in search mode.
+ */
+
+"use strict";
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ // Add a new mock default engine so we don't hit the network.
+ await SearchTestUtils.installSearchExtension(
+ { name: "Test" },
+ { setAsDefault: true }
+ );
+
+ // Add one bookmark we'll use below.
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/bookmark",
+ });
+ registerCleanupFunction(async () => {
+ await PlacesUtils.bookmarks.eraseEverything();
+ });
+});
+
+// Enters search mode with no results.
+add_task(async function noResults() {
+ // Do a search that doesn't match our bookmark and enter bookmark search mode.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "doesn't match anything",
+ });
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 0,
+ "Zero results since no bookmark matches"
+ );
+
+ // Press enter. Nothing should happen.
+ let loadPromise = waitForLoadOrTimeout();
+ EventUtils.synthesizeKey("KEY_Enter");
+ let loadEvent = await loadPromise;
+ Assert.ok(!loadEvent, "Nothing should have loaded");
+
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Enters a local search mode (bookmarks) with a matching result. No heuristic
+// should be present.
+add_task(async function localNoHeuristic() {
+ // Do a search that matches our bookmark and enter bookmarks search mode.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "bookmark",
+ });
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 1,
+ "There should be one result"
+ );
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ result.source,
+ UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ "Result source should be BOOKMARKS"
+ );
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.URL,
+ "Result type should be URL"
+ );
+ Assert.equal(
+ result.url,
+ "http://example.com/bookmark",
+ "Result URL is our bookmark URL"
+ );
+ Assert.ok(!result.heuristic, "Result should not be heuristic");
+
+ // Press enter. Nothing should happen.
+ let loadPromise = waitForLoadOrTimeout();
+ EventUtils.synthesizeKey("KEY_Enter");
+ let loadEvent = await loadPromise;
+ Assert.ok(!loadEvent, "Nothing should have loaded");
+
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Enters a local search mode (bookmarks) with a matching autofill result. The
+// result should be the heuristic.
+add_task(async function localAutofill() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ // Do a search that autofills our bookmark's origin and enter bookmarks
+ // search mode.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "example",
+ });
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "There should be two results"
+ );
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ result.source,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ "Result source should be HISTORY"
+ );
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.URL,
+ "Result type should be URL"
+ );
+ Assert.equal(
+ result.url,
+ "http://example.com/",
+ "Result URL is our bookmark's origin"
+ );
+ Assert.ok(result.heuristic, "Result should be heuristic");
+ Assert.ok(result.autofill, "Result should be autofill");
+
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ result.source,
+ UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ "Result source should be BOOKMARKS"
+ );
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.URL,
+ "Result type should be URL"
+ );
+ Assert.equal(
+ result.url,
+ "http://example.com/bookmark",
+ "Result URL is our bookmark URL"
+ );
+
+ // Press enter. Our bookmark's origin should be loaded.
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ Assert.equal(
+ gBrowser.currentURI.spec,
+ "http://example.com/",
+ "Bookmark's origin should have loaded"
+ );
+ });
+});
+
+// Enters a remote engine search mode. There should be a heuristic.
+add_task(async function remote() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ // Do a search and enter search mode with our test engine.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "remote",
+ });
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: "Test",
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 1,
+ "There should be one result"
+ );
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ result.source,
+ UrlbarUtils.RESULT_SOURCE.SEARCH,
+ "Result source should be SEARCH"
+ );
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Result type should be SEARCH"
+ );
+ Assert.ok(result.searchParams, "searchParams should be present");
+ Assert.equal(
+ result.searchParams.engine,
+ "Test",
+ "searchParams.engine should be our test engine"
+ );
+ Assert.equal(
+ result.searchParams.query,
+ "remote",
+ "searchParams.query should be our query"
+ );
+ Assert.ok(result.heuristic, "Result should be heuristic");
+
+ // Press enter. The engine's SERP should load.
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ Assert.equal(
+ gBrowser.currentURI.spec,
+ "https://example.com/?q=remote",
+ "Engine's SERP should have loaded"
+ );
+ });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_indicator.js b/browser/components/urlbar/tests/browser/browser_searchMode_indicator.js
new file mode 100644
index 0000000000..357a5d17f9
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_indicator.js
@@ -0,0 +1,377 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests interactions with the search mode indicator. See browser_oneOffs.js for
+ * more coverage.
+ */
+
+const TEST_QUERY = "test string";
+const SUGGESTIONS_ENGINE_NAME = "searchSuggestionEngine.xml";
+
+// These need to have different domains because otherwise new tab and/or
+// activity stream collapses them.
+const TOP_SITES_URLS = [
+ "http://top-site-0.com/",
+ "http://top-site-1.com/",
+ "http://top-site-2.com/",
+];
+
+let suggestionsEngine;
+let defaultEngine;
+
+add_setup(async function () {
+ suggestionsEngine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + SUGGESTIONS_ENGINE_NAME,
+ });
+
+ await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
+ defaultEngine = Services.search.getEngineByName("Example");
+ await Services.search.moveEngine(suggestionsEngine, 0);
+
+ // Set our top sites.
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "browser.newtabpage.activity-stream.default.sites",
+ TOP_SITES_URLS.join(","),
+ ],
+ ],
+ });
+ await updateTopSites(sites =>
+ ObjectUtils.deepEqual(
+ sites.map(s => s.url),
+ TOP_SITES_URLS
+ )
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", false],
+ ["browser.urlbar.suggest.quickactions", false],
+ ],
+ });
+});
+
+async function verifySearchModeResultsAdded(window) {
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 3,
+ "There should be three results."
+ );
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ result.searchParams.engine,
+ suggestionsEngine.name,
+ "The first result should be a search result for our suggestion engine."
+ );
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ result.searchParams.suggestion,
+ `${TEST_QUERY}foo`,
+ "The second result should be a suggestion result."
+ );
+ Assert.equal(
+ result.searchParams.engine,
+ suggestionsEngine.name,
+ "The second result should be a search result for our suggestion engine."
+ );
+}
+
+async function verifySearchModeResultsRemoved(window) {
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 1,
+ "There should only be one result."
+ );
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ result.searchParams.engine,
+ defaultEngine.name,
+ "The first result should be a search result for our default engine."
+ );
+}
+
+async function verifyTopSitesResultsAdded(window) {
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ TOP_SITES_URLS.length,
+ "Expected number of top sites results"
+ );
+ for (let i = 0; i < TOP_SITES_URLS; i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.equal(
+ result.url,
+ TOP_SITES_URLS[i],
+ `Expected top sites result URL at index ${i}`
+ );
+ }
+}
+
+// Tests that the indicator is removed when backspacing at the beginning of
+// the search string.
+add_task(async function backspace() {
+ // View open, with string.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_QUERY,
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ await verifySearchModeResultsAdded(window);
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+ await verifySearchModeResultsRemoved(window);
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Urlbar view is open.");
+
+ // View open, no string (i.e., top sites).
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ if (gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Urlbar view is open.");
+ await verifyTopSitesResultsAdded(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ // View closed, with string.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_QUERY,
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ await verifySearchModeResultsAdded(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+ await verifySearchModeResultsRemoved(window);
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Urlbar view is now open.");
+
+ // View closed, no string (i.e., top sites).
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ if (gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Urlbar view is open.");
+ await verifyTopSitesResultsAdded(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+add_task(async function escapeOnInitialPage() {
+ info("Tests the indicator's interaction with the ESC key");
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_QUERY,
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ await verifySearchModeResultsAdded(window);
+
+ EventUtils.synthesizeKey("KEY_Escape");
+ Assert.ok(!UrlbarTestUtils.isPopupOpen(window, "UrlbarView is closed."));
+ Assert.equal(gURLBar.value, TEST_QUERY, "Urlbar value hasn't changed.");
+
+ let oneOffs =
+ UrlbarTestUtils.getOneOffSearchButtons(window).getSelectableButtons(true);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: oneOffs[0].engine.name,
+ entry: "oneoff",
+ });
+
+ EventUtils.synthesizeKey("KEY_Escape");
+ Assert.ok(!UrlbarTestUtils.isPopupOpen(window, "UrlbarView is closed."));
+ Assert.ok(!gURLBar.value, "Urlbar value is empty.");
+ await UrlbarTestUtils.assertSearchMode(window, null);
+});
+
+add_task(async function escapeOnBrowsingPage() {
+ info("Tests the indicator's interaction with the ESC key on browsing page");
+
+ await BrowserTestUtils.withNewTab("http://example.com", async browser => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_QUERY,
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ await verifySearchModeResultsAdded(window);
+
+ EventUtils.synthesizeKey("KEY_Escape");
+ Assert.ok(!UrlbarTestUtils.isPopupOpen(window, "UrlbarView is closed."));
+ Assert.equal(gURLBar.value, TEST_QUERY, "Urlbar value hasn't changed.");
+
+ const oneOffs =
+ UrlbarTestUtils.getOneOffSearchButtons(window).getSelectableButtons(true);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: oneOffs[0].engine.name,
+ entry: "oneoff",
+ });
+
+ EventUtils.synthesizeKey("KEY_Escape");
+ Assert.ok(!UrlbarTestUtils.isPopupOpen(window, "UrlbarView is closed."));
+ Assert.equal(
+ gURLBar.value,
+ "example.com",
+ "Urlbar value indicates the browsing page."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ });
+});
+
+// Tests that the indicator is removed when its close button is clicked.
+add_task(async function click_close() {
+ // Clicking close with the view open, with string.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_QUERY,
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ await verifySearchModeResultsAdded(window);
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await verifySearchModeResultsRemoved(window);
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Urlbar view is open.");
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ // Clicking close with the view open, no string (i.e., top sites).
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ if (gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Urlbar view is open.");
+ await verifyTopSitesResultsAdded(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ // Clicking close with the view closed, with string.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_QUERY,
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ await verifySearchModeResultsAdded(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.exitSearchMode(window, {
+ clickClose: true,
+ waitForSearch: false,
+ });
+ Assert.ok(!UrlbarTestUtils.isPopupOpen(window), "Urlbar view is closed.");
+
+ // Clicking close with the view closed, no string (i.e., top sites).
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ if (gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.exitSearchMode(window, {
+ clickClose: true,
+ waitForSearch: false,
+ });
+ Assert.ok(!UrlbarTestUtils.isPopupOpen(window), "Urlbar view is closed.");
+});
+
+// Tests that Accel+K enters search mode with the default engine. Also tests
+// that Accel+K highlights the typed search string.
+add_task(async function keyboard_shortcut() {
+ const query = "test query";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: query,
+ });
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ Assert.equal(
+ gURLBar.selectionStart,
+ gURLBar.selectionEnd,
+ "The search string is not highlighted."
+ );
+ EventUtils.synthesizeKey("k", { accelKey: true });
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ engineName: defaultEngine.name,
+ entry: "shortcut",
+ });
+ Assert.equal(gURLBar.value, query, "The search string was not cleared.");
+ Assert.equal(gURLBar.selectionStart, 0);
+ Assert.equal(
+ gURLBar.selectionEnd,
+ query.length,
+ "The search string is highlighted."
+ );
+ await UrlbarTestUtils.exitSearchMode(window, {
+ clickClose: true,
+ waitForSearch: false,
+ });
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+});
+
+// Tests that the Tools:Search menu item enters search mode with the default
+// engine. Also tests that Tools:Search highlights the typed search string.
+add_task(async function menubar_item() {
+ const query = "test query 2";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: query,
+ });
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ Assert.equal(
+ gURLBar.selectionStart,
+ gURLBar.selectionEnd,
+ "The search string is not highlighted."
+ );
+ let command = window.document.getElementById("Tools:Search");
+ command.doCommand();
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ engineName: defaultEngine.name,
+ entry: "shortcut",
+ });
+ Assert.equal(gURLBar.value, query, "The search string was not cleared.");
+ Assert.equal(gURLBar.selectionStart, 0);
+ Assert.equal(
+ gURLBar.selectionEnd,
+ query.length,
+ "The search string is highlighted."
+ );
+ await UrlbarTestUtils.exitSearchMode(window, {
+ clickClose: true,
+ waitForSearch: false,
+ });
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+});
+
+// Tests that entering search mode invalidates pageproxystate and that
+// pageproxystate remains invalid after exiting search mode.
+add_task(async function invalidate_pageproxystate() {
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ Assert.equal(gURLBar.getAttribute("pageproxystate"), "valid");
+ await UrlbarTestUtils.enterSearchMode(window);
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "invalid",
+ "Entering search mode should clear pageproxystate."
+ );
+ Assert.equal(gURLBar.value, "", "Urlbar value should be cleared.");
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "invalid",
+ "Pageproxystate should still be invalid after exiting search mode."
+ );
+ });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_indicator_clickthrough.js b/browser/components/urlbar/tests/browser/browser_searchMode_indicator_clickthrough.js
new file mode 100644
index 0000000000..acfb60922d
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_indicator_clickthrough.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check clicking on the search mode indicator when the urlbar is not focused puts
+ * focus in the urlbar.
+ */
+
+add_task(async function test() {
+ // Avoid remote connections.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.suggest.enabled", false]],
+ });
+
+ await BrowserTestUtils.withNewTab("about:robots", async browser => {
+ // View open, with string.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ const indicator = document.getElementById("urlbar-search-mode-indicator");
+ Assert.ok(!BrowserTestUtils.is_visible(indicator));
+ const indicatorCloseButton = document.getElementById(
+ "urlbar-search-mode-indicator-close"
+ );
+ Assert.ok(!BrowserTestUtils.is_visible(indicatorCloseButton));
+ const labelBox = document.getElementById("urlbar-label-box");
+ Assert.ok(!BrowserTestUtils.is_visible(labelBox));
+
+ await UrlbarTestUtils.enterSearchMode(window);
+ Assert.ok(BrowserTestUtils.is_visible(indicator));
+ Assert.ok(BrowserTestUtils.is_visible(indicatorCloseButton));
+ Assert.ok(!BrowserTestUtils.is_visible(labelBox));
+
+ info("Blur the urlbar");
+ gURLBar.blur();
+ Assert.ok(BrowserTestUtils.is_visible(indicator));
+ Assert.ok(BrowserTestUtils.is_visible(indicatorCloseButton));
+ Assert.ok(!BrowserTestUtils.is_visible(labelBox));
+ Assert.notEqual(
+ document.activeElement,
+ gURLBar.inputField,
+ "URL Bar should not be focused"
+ );
+
+ info("Focus the urlbar clicking on the indicator");
+ EventUtils.synthesizeMouseAtCenter(indicator, {});
+ Assert.ok(BrowserTestUtils.is_visible(indicator));
+ Assert.ok(BrowserTestUtils.is_visible(indicatorCloseButton));
+ Assert.ok(!BrowserTestUtils.is_visible(labelBox));
+ Assert.equal(
+ document.activeElement,
+ gURLBar.inputField,
+ "URL Bar should be focused"
+ );
+
+ info("Leave search mode clicking on the close button");
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ Assert.ok(!BrowserTestUtils.is_visible(indicator));
+ Assert.ok(!BrowserTestUtils.is_visible(indicatorCloseButton));
+ Assert.ok(!BrowserTestUtils.is_visible(labelBox));
+ });
+
+ await BrowserTestUtils.withNewTab("about:robots", async browser => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ const indicator = document.getElementById("urlbar-search-mode-indicator");
+ Assert.ok(!BrowserTestUtils.is_visible(indicator));
+ const indicatorCloseButton = document.getElementById(
+ "urlbar-search-mode-indicator-close"
+ );
+ Assert.ok(!BrowserTestUtils.is_visible(indicatorCloseButton));
+
+ await UrlbarTestUtils.enterSearchMode(window);
+ Assert.ok(BrowserTestUtils.is_visible(indicator));
+ Assert.ok(BrowserTestUtils.is_visible(indicatorCloseButton));
+
+ info("Blur the urlbar");
+ gURLBar.blur();
+ Assert.ok(BrowserTestUtils.is_visible(indicator));
+ Assert.ok(BrowserTestUtils.is_visible(indicatorCloseButton));
+ Assert.notEqual(
+ document.activeElement,
+ gURLBar.inputField,
+ "URL Bar should not be focused"
+ );
+
+ info("Leave search mode clicking on the close button while unfocussing");
+ await UrlbarTestUtils.exitSearchMode(window, {
+ clickClose: true,
+ waitForSearch: false,
+ });
+ Assert.ok(!BrowserTestUtils.is_visible(indicator));
+ Assert.ok(!BrowserTestUtils.is_visible(indicatorCloseButton));
+ });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_localOneOffs_actionText.js b/browser/components/urlbar/tests/browser/browser_searchMode_localOneOffs_actionText.js
new file mode 100644
index 0000000000..74a2a3caba
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_localOneOffs_actionText.js
@@ -0,0 +1,459 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests action text shown on heuristic and search suggestions when keyboard
+ * navigating local one-off buttons.
+ */
+
+"use strict";
+
+const DEFAULT_ENGINE_NAME = "Test";
+const SUGGESTIONS_ENGINE_NAME = "searchSuggestionEngine.xml";
+
+let engine;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.searches", true],
+ ["browser.urlbar.suggest.quickactions", false],
+ ["browser.urlbar.shortcuts.quickactions", false],
+ ],
+ });
+ engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + SUGGESTIONS_ENGINE_NAME,
+ setAsDefault: true,
+ });
+ await Services.search.moveEngine(engine, 0);
+
+ await PlacesUtils.history.clear();
+
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+add_task(async function localOneOff() {
+ info("Type some text, select a local one-off, check heuristic action");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "query",
+ });
+ Assert.ok(UrlbarTestUtils.getResultCount(window) > 1, "Sanity check results");
+
+ info("Alt UP to select the last local one-off.");
+ EventUtils.synthesizeKey("KEY_ArrowUp", { altKey: true });
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "the heuristic result should be selected"
+ );
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ let oneOffButtons = UrlbarTestUtils.getOneOffSearchButtons(window);
+ Assert.equal(
+ oneOffButtons.selectedButton.source,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ "A local one-off button should be selected"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_visible(result.element.action),
+ "The heuristic action should be visible"
+ );
+ let [actionHistory, actionBookmarks] = await document.l10n.formatValues([
+ { id: "urlbar-result-action-search-history" },
+ { id: "urlbar-result-action-search-bookmarks" },
+ ]);
+ Assert.equal(
+ result.displayed.action,
+ actionHistory,
+ "Check the heuristic action"
+ );
+ Assert.equal(
+ result.image,
+ "chrome://browser/skin/history.svg",
+ "Check the heuristic icon"
+ );
+
+ info("Move to an engine one-off and check heuristic action");
+ EventUtils.synthesizeKey("KEY_ArrowUp", { altKey: true });
+ EventUtils.synthesizeKey("KEY_ArrowUp", { altKey: true });
+ EventUtils.synthesizeKey("KEY_ArrowUp", { altKey: true });
+ Assert.ok(
+ oneOffButtons.selectedButton.engine,
+ "A one-off search button should be selected"
+ );
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(
+ BrowserTestUtils.is_visible(result.element.action),
+ "The heuristic action should be visible"
+ );
+ Assert.ok(
+ result.displayed.action.includes(oneOffButtons.selectedButton.engine.name),
+ "Check the heuristic action"
+ );
+ Assert.equal(
+ result.image,
+ oneOffButtons.selectedButton.engine.iconURI.spec,
+ "Check the heuristic icon"
+ );
+
+ info("Move again to a local one-off, deselect and reselect the heuristic");
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ oneOffButtons.selectedButton.source,
+ UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ "A local one-off button should be selected"
+ );
+ Assert.equal(
+ result.displayed.action,
+ actionBookmarks,
+ "Check the heuristic action"
+ );
+ Assert.equal(
+ result.image,
+ "chrome://browser/skin/bookmark.svg",
+ "Check the heuristic icon"
+ );
+
+ info(
+ "Select the next result, then reselect the heuristic, check it's a search with the default engine"
+ );
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "the heuristic result should be selected"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", {});
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 1,
+ "the heuristic result should not be selected"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowUp", {});
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "the heuristic result should be selected"
+ );
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.SEARCH);
+ Assert.equal(result.searchParams.engine, engine.name);
+ Assert.ok(
+ result.displayed.action.includes(engine.name),
+ "Check the heuristic action"
+ );
+ Assert.equal(
+ result.image,
+ "chrome://global/skin/icons/search-glass.svg",
+ "Check the heuristic icon"
+ );
+});
+
+add_task(async function localOneOff_withVisit() {
+ info("Type a url, select a local one-off, check heuristic action");
+ await PlacesUtils.history.clear();
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits("https://mozilla.org/");
+ await PlacesTestUtils.addVisits("https://other.mozilla.org/");
+ }
+ const searchString = "mozilla.org";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ });
+ Assert.ok(UrlbarTestUtils.getResultCount(window) > 1, "Sanity check results");
+ let oneOffButtons = UrlbarTestUtils.getOneOffSearchButtons(window);
+
+ let [actionHistory, actionTabs, actionBookmarks] =
+ await document.l10n.formatValues([
+ { id: "urlbar-result-action-search-history" },
+ { id: "urlbar-result-action-search-tabs" },
+ { id: "urlbar-result-action-search-bookmarks" },
+ ]);
+
+ info("Alt UP to select the history one-off.");
+ EventUtils.synthesizeKey("KEY_ArrowUp", { altKey: true });
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "the heuristic result should be selected"
+ );
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ oneOffButtons.selectedButton.source,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ "The history one-off button should be selected"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_visible(result.element.action),
+ "The heuristic action should be visible"
+ );
+ Assert.equal(
+ result.displayed.action,
+ actionHistory,
+ "Check the heuristic action"
+ );
+ Assert.equal(
+ result.image,
+ "chrome://browser/skin/history.svg",
+ "Check the heuristic icon"
+ );
+ let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+ Assert.equal(
+ row.querySelector(".urlbarView-title").textContent,
+ searchString,
+ "Check that the result title has been replaced with the search string."
+ );
+
+ info("Alt UP to select the tabs one-off.");
+ EventUtils.synthesizeKey("KEY_ArrowUp", { altKey: true });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ oneOffButtons.selectedButton.source,
+ UrlbarUtils.RESULT_SOURCE.TABS,
+ "The tabs one-off button should be selected"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_visible(result.element.action),
+ "The heuristic action should be visible"
+ );
+ Assert.equal(
+ result.displayed.action,
+ actionTabs,
+ "Check the heuristic action"
+ );
+ Assert.equal(
+ result.image,
+ "chrome://browser/skin/tab.svg",
+ "Check the heuristic icon"
+ );
+
+ info("Alt UP to select the bookmarks one-off.");
+ EventUtils.synthesizeKey("KEY_ArrowUp", { altKey: true });
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ oneOffButtons.selectedButton.source,
+ UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ "The bookmarks one-off button should be selected"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_visible(result.element.action),
+ "The heuristic action should be visible"
+ );
+ Assert.equal(
+ result.displayed.action,
+ actionBookmarks,
+ "Check the heuristic action"
+ );
+ Assert.equal(
+ result.image,
+ "chrome://browser/skin/bookmark.svg",
+ "Check the heuristic icon"
+ );
+
+ info(
+ "Select the next result, then reselect the heuristic, check it's a visit"
+ );
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "the heuristic result should be selected"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", {});
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 1,
+ "the heuristic result should not be selected"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowUp", {});
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "the heuristic result should be selected"
+ );
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ oneOffButtons.selectedButton,
+ null,
+ "No one-off button should be selected"
+ );
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.URL);
+ Assert.equal(
+ result.displayed.url,
+ result.result.payload.displayUrl,
+ "Check the heuristic action"
+ );
+ Assert.notEqual(
+ result.image,
+ "chrome://browser/skin/history.svg",
+ "Check the heuristic icon"
+ );
+ row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+ Assert.equal(
+ row.querySelector(".urlbarView-title").textContent,
+ result.result.payload.title || `https://${searchString}`,
+ "Check that the result title has been restored to the fixed-up URI."
+ );
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function localOneOff_suggestion() {
+ info("Type some text, select the first suggestion, then a local one-off");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "query",
+ });
+ let count = UrlbarTestUtils.getResultCount(window);
+ Assert.ok(count > 1, "Sanity check results");
+ let result = null;
+ let suggestionIndex = -1;
+ for (let i = 1; i < count; ++i) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ let index = await UrlbarTestUtils.getSelectedRowIndex(window);
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ if (
+ result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
+ result.searchParams.suggestion
+ ) {
+ suggestionIndex = i;
+ break;
+ }
+ }
+ Assert.ok(
+ result.searchParams.suggestion,
+ "Should have selected a search suggestion"
+ );
+ Assert.ok(
+ result.displayed.action.includes(result.searchParams.engine),
+ "Check the search suggestion action"
+ );
+
+ info("Alt UP to select the last local one-off.");
+ EventUtils.synthesizeKey("KEY_ArrowUp", { altKey: true });
+ Assert.equal(
+ await UrlbarTestUtils.getSelectedRowIndex(window),
+ suggestionIndex,
+ "the suggestion should still be selected"
+ );
+
+ let [actionHistory] = await document.l10n.formatValues([
+ { id: "urlbar-result-action-search-history" },
+ ]);
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, suggestionIndex);
+ Assert.equal(
+ result.displayed.action,
+ actionHistory,
+ "Check the search suggestion action changed to local one-off"
+ );
+ // Like in the normal engine one-offs case, we don't replace the favicon.
+ Assert.equal(
+ result.image,
+ "chrome://global/skin/icons/search-glass.svg",
+ "Check the suggestion icon"
+ );
+
+ info("DOWN to select the next suggestion");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ suggestionIndex + 1
+ );
+ Assert.ok(
+ result.searchParams.suggestion,
+ "Should have selected a search suggestion"
+ );
+ Assert.ok(
+ result.displayed.action.includes(result.searchParams.engine),
+ "Check the search suggestion action"
+ );
+ Assert.equal(
+ result.image,
+ "chrome://global/skin/icons/search-glass.svg",
+ "Check the suggestion icon"
+ );
+
+ info("UP back to the previous suggestion");
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, suggestionIndex);
+ Assert.ok(
+ result.displayed.action.includes(result.searchParams.engine),
+ "Check the search suggestion action"
+ );
+ Assert.equal(
+ result.image,
+ "chrome://global/skin/icons/search-glass.svg",
+ "Check the suggestion icon"
+ );
+});
+
+add_task(async function localOneOff_shortcut() {
+ info("Select a search shortcut, then a local one-off");
+
+ await PlacesUtils.history.clear();
+ // Enough vists to get this site into Top Sites.
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits("http://example.com/");
+ }
+
+ await updateTopSites(
+ sites => sites && sites[0] && sites[0].searchTopSite,
+ true
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ let count = UrlbarTestUtils.getResultCount(window);
+ Assert.ok(count > 1, "Sanity check results");
+ let result = null;
+ let shortcutIndex = -1;
+ for (let i = 0; i < count; ++i) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ let index = await UrlbarTestUtils.getSelectedRowIndex(window);
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ if (
+ result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
+ result.searchParams.keyword
+ ) {
+ shortcutIndex = i;
+ break;
+ }
+ }
+ Assert.ok(result.searchParams.keyword, "Should have selected a shortcut");
+ let shortcutEngine = result.searchParams.engine;
+
+ info("Alt UP to select the last local one-off.");
+ EventUtils.synthesizeKey("KEY_ArrowUp", { altKey: true });
+ Assert.equal(
+ await UrlbarTestUtils.getSelectedRowIndex(window),
+ shortcutIndex,
+ "the shortcut should still be selected"
+ );
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, shortcutIndex);
+ Assert.equal(
+ result.displayed.action,
+ "",
+ "Check the shortcut action is empty"
+ );
+ Assert.equal(
+ result.searchParams.engine,
+ shortcutEngine,
+ "Check the shortcut engine"
+ );
+ Assert.ok(
+ result.displayed.title.includes(shortcutEngine),
+ "Check the shortcut title"
+ );
+ Assert.notEqual(
+ result.image,
+ "chrome://global/skin/icons/search-glass.svg",
+ "Check the icon was not replaced"
+ );
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await PlacesUtils.history.clear();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_newWindow.js b/browser/components/urlbar/tests/browser/browser_searchMode_newWindow.js
new file mode 100644
index 0000000000..e5a3eb848a
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_newWindow.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests immediately entering search mode in a new window and then exiting it.
+// No errors should be thrown and search mode should be exited successfully.
+
+"use strict";
+
+add_task(async function escape() {
+ await doTest(win =>
+ EventUtils.synthesizeKey("KEY_Escape", { repeat: 2 }, win)
+ );
+});
+
+add_task(async function backspace() {
+ await doTest(win => EventUtils.synthesizeKey("KEY_Backspace", {}, win));
+});
+
+async function doTest(exitSearchMode) {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ // Press accel+K to enter search mode.
+ await UrlbarTestUtils.promisePopupOpen(win, () =>
+ EventUtils.synthesizeKey("k", { accelKey: true }, win)
+ );
+ await UrlbarTestUtils.assertSearchMode(win, {
+ engineName: Services.search.defaultEngine.name,
+ isGeneralPurposeEngine: true,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ isPreview: false,
+ entry: "shortcut",
+ });
+
+ // Exit search mode.
+ await exitSearchMode(win);
+ await UrlbarTestUtils.assertSearchMode(win, null);
+
+ await UrlbarTestUtils.promisePopupClose(win);
+ await BrowserTestUtils.closeWindow(win);
+}
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_no_results.js b/browser/components/urlbar/tests/browser/browser_searchMode_no_results.js
new file mode 100644
index 0000000000..9ecc5573fc
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_no_results.js
@@ -0,0 +1,290 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests entering search mode and there are no results in the view.
+ */
+
+"use strict";
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+add_setup(async function () {
+ // In order to open the view without any results, we need to be in search mode
+ // with an empty search string so that no heuristic result is shown, and the
+ // empty search must yield zero additional results. We'll enter search mode
+ // using the bookmarks one-off, and first we'll delete all bookmarks so that
+ // there are no results.
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ // Also clear history so that using the alias of our test engine doesn't
+ // inadvertently return any history results due to bug 1658646.
+ await PlacesUtils.history.clear();
+
+ // Add a top site so we're guaranteed the view has at least one result to
+ // show initially with an empty search. Otherwise the view won't even open.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "browser.newtabpage.activity-stream.default.sites",
+ "http://example.com/",
+ ],
+ ],
+ });
+ await updateTopSites(sites => sites.length);
+});
+
+// Basic test for entering search mode with no results.
+add_task(async function basic() {
+ await withNewWindow(async win => {
+ // Do an empty search.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "",
+ });
+
+ // Initially there should be at least the top site we added above.
+ Assert.greater(
+ UrlbarTestUtils.getResultCount(win),
+ 0,
+ "Top sites should be present initially"
+ );
+ Assert.ok(
+ !win.gURLBar.panel.hasAttribute("noresults"),
+ "Panel has results, therefore should not have noresults attribute"
+ );
+
+ // Enter search mode by clicking the bookmarks one-off.
+ await UrlbarTestUtils.enterSearchMode(win, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(win),
+ 0,
+ "Zero results since no bookmarks exist"
+ );
+ Assert.equal(
+ win.gURLBar.panel.getAttribute("noresults"),
+ "true",
+ "Panel has no results, therefore should have noresults attribute"
+ );
+
+ // Exit search mode by backspacing. The top sites should be shown again.
+ await UrlbarTestUtils.exitSearchMode(win, { backspace: true });
+ Assert.greater(
+ UrlbarTestUtils.getResultCount(win),
+ 0,
+ "Top sites should be present again"
+ );
+ Assert.ok(
+ !win.gURLBar.panel.hasAttribute("noresults"),
+ "noresults attribute should be absent again"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(win);
+ });
+});
+
+// When the urlbar is in search mode, has no results, and is not focused,
+// focusing it should auto-open the view.
+add_task(async function autoOpen() {
+ await withNewWindow(async win => {
+ // Do an empty search.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "",
+ });
+
+ // Initially there should be at least the top site we added above.
+ Assert.greater(
+ UrlbarTestUtils.getResultCount(win),
+ 0,
+ "Top sites should be present initially"
+ );
+ Assert.ok(
+ !win.gURLBar.panel.hasAttribute("noresults"),
+ "Panel has results, therefore should not have noresults attribute"
+ );
+
+ // Enter search mode by clicking the bookmarks one-off.
+ await UrlbarTestUtils.enterSearchMode(win, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(win),
+ 0,
+ "Zero results since no bookmarks exist"
+ );
+ Assert.equal(
+ win.gURLBar.panel.getAttribute("noresults"),
+ "true",
+ "Panel has no results, therefore should have noresults attribute"
+ );
+
+ // Blur the urlbar.
+ win.gURLBar.blur();
+ await UrlbarTestUtils.assertSearchMode(win, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ });
+
+ // Click the urlbar.
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {}, win);
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(win),
+ 0,
+ "Still zero results since no bookmarks exist"
+ );
+ Assert.equal(
+ win.gURLBar.panel.getAttribute("noresults"),
+ "true",
+ "Panel still has no results, therefore should have noresults attribute"
+ );
+
+ // Exit search mode by backspacing. The top sites should be shown again.
+ await UrlbarTestUtils.exitSearchMode(win, { backspace: true });
+ Assert.greater(
+ UrlbarTestUtils.getResultCount(win),
+ 0,
+ "Top sites should be present again"
+ );
+ Assert.ok(
+ !win.gURLBar.panel.hasAttribute("noresults"),
+ "noresults attribute should be absent again"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(win);
+ });
+});
+
+// When the urlbar is in search mode, the user backspaces over the final char
+// (but remains in search mode), and there are no results, the view should
+// remain open.
+add_task(async function backspaceRemainOpen() {
+ await withNewWindow(async win => {
+ // Do a one-char search.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "x",
+ });
+ Assert.greater(
+ UrlbarTestUtils.getResultCount(win),
+ 0,
+ "At least the heuristic result should be shown"
+ );
+ Assert.ok(
+ !win.gURLBar.panel.hasAttribute("noresults"),
+ "Panel has results, therefore should not have noresults attribute"
+ );
+
+ // Enter search mode by clicking the bookmarks one-off.
+ await UrlbarTestUtils.enterSearchMode(win, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+
+ // The heursitic should not be shown since we don't show it in local search
+ // modes.
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(win),
+ 0,
+ "No results should be present"
+ );
+ Assert.ok(
+ win.gURLBar.panel.hasAttribute("noresults"),
+ "Panel has no results, therefore should have noresults attribute"
+ );
+
+ // Backspace. The search string will now be empty.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+ EventUtils.synthesizeKey("KEY_Backspace", {}, win);
+ await searchPromise;
+ Assert.ok(UrlbarTestUtils.isPopupOpen(win), "View remains open");
+ await UrlbarTestUtils.assertSearchMode(win, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(win),
+ 0,
+ "Zero results since no bookmarks exist"
+ );
+ Assert.equal(
+ win.gURLBar.panel.getAttribute("noresults"),
+ "true",
+ "Panel has no results, therefore should have noresults attribute"
+ );
+
+ // Exit search mode by backspacing. The top sites should be shown.
+ await UrlbarTestUtils.exitSearchMode(win, { backspace: true });
+ Assert.greater(
+ UrlbarTestUtils.getResultCount(win),
+ 0,
+ "Top sites should be present again"
+ );
+ Assert.ok(
+ !win.gURLBar.panel.hasAttribute("noresults"),
+ "noresults attribute should be absent again"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(win);
+ });
+});
+
+// Types a search alias and then a space to enter search mode, with no results.
+// The one-offs should be shown.
+add_task(async function spaceToEnterSearchMode() {
+ let engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ });
+ engine.alias = "@test";
+
+ await withNewWindow(async win => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: engine.alias,
+ });
+
+ // We need to wait for two searches: The first enters search mode, the
+ // second does the search in search mode.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+ EventUtils.synthesizeKey(" ", {}, win);
+ await searchPromise;
+
+ Assert.equal(UrlbarTestUtils.getResultCount(win), 0, "Zero results");
+ Assert.equal(
+ win.gURLBar.panel.getAttribute("noresults"),
+ "true",
+ "Panel has no results, therefore should have noresults attribute"
+ );
+ await UrlbarTestUtils.assertSearchMode(win, {
+ engineName: engine.name,
+ entry: "typed",
+ });
+ this.Assert.equal(
+ UrlbarTestUtils.getOneOffSearchButtonsVisible(window),
+ true,
+ "One-offs are visible"
+ );
+
+ await UrlbarTestUtils.exitSearchMode(win, { backspace: true });
+ await UrlbarTestUtils.promisePopupClose(win);
+ });
+});
+
+/**
+ * Opens a new window, waits for it to load, calls a callback, and closes the
+ * window. We use a new window in each task so that the view starts with a
+ * blank slate each time.
+ *
+ * @param {Function} callback
+ * Will be called as: callback(newWindow)
+ */
+async function withNewWindow(callback) {
+ // Start in a new window so we have a blank slate.
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await callback(win);
+ await BrowserTestUtils.closeWindow(win);
+}
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_oneOffButton.js b/browser/components/urlbar/tests/browser/browser_searchMode_oneOffButton.js
new file mode 100644
index 0000000000..1ba0d3283b
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_oneOffButton.js
@@ -0,0 +1,108 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests one-off search button behavior with search mode.
+ */
+
+const TEST_ENGINE_NAME = "test engine";
+
+add_setup(async function () {
+ await SearchTestUtils.installSearchExtension({
+ name: TEST_ENGINE_NAME,
+ keyword: "@test",
+ });
+});
+
+add_task(async function test() {
+ info("Test no one-off buttons are selected when entering search mode");
+
+ info("Open the result popup");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+
+ info("Select one of one-off button");
+ const oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window);
+ await TestUtils.waitForCondition(
+ () => !oneOffs._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ ok(oneOffs.selectedButton, "There is a selected one-off button");
+
+ info("Enter search mode");
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ });
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "oneoff",
+ });
+ ok(!oneOffs.selectedButton, "There is no selected one-off button");
+});
+
+add_task(async function () {
+ info(
+ "Test the status of the selected one-off button when exiting search mode with backspace"
+ );
+
+ info("Open the result popup");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+
+ info("Select one of one-off button");
+ const oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window);
+ await TestUtils.waitForCondition(
+ () => !oneOffs._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ ok(oneOffs.selectedButton, "There is a selected one-off button");
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: oneOffs.selectedButton.engine.name,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ entry: "oneoff",
+ isPreview: true,
+ });
+
+ info("Exit from search mode");
+ await UrlbarTestUtils.exitSearchMode(window);
+ ok(!oneOffs.selectedButton, "There is no any selected one-off button");
+});
+
+add_task(async function () {
+ info(
+ "Test the status of the selected one-off button when exiting search mode with clicking close button"
+ );
+
+ info("Open the result popup");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+
+ info("Select one of one-off button");
+ const oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window);
+ await TestUtils.waitForCondition(
+ () => !oneOffs._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ ok(oneOffs.selectedButton, "There is a selected one-off button");
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: oneOffs.selectedButton.engine.name,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ entry: "oneoff",
+ isPreview: true,
+ });
+
+ info("Exit from search mode");
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ ok(!oneOffs.selectedButton, "There is no any selected one-off button");
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_pickResult.js b/browser/components/urlbar/tests/browser/browser_searchMode_pickResult.js
new file mode 100644
index 0000000000..ac45b3e5c7
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_pickResult.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that search mode is exited after picking a result.
+ */
+
+"use strict";
+
+const BOOKMARK_URL = "http://www.example.com/browser_searchMode_pickResult.js";
+
+add_setup(async function () {
+ // Add a bookmark so we can enter bookmarks search mode and pick it.
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: BOOKMARK_URL,
+ });
+ registerCleanupFunction(async () => {
+ await PlacesUtils.bookmarks.eraseEverything();
+ });
+});
+
+// Opens a new tab, enters search mode, does a search for our test bookmark, and
+// picks it. Uses a variety of initial URLs and search strings in order to hit
+// different branches in setURI. Search mode should be exited in all cases.
+add_task(async function pickResult() {
+ for (let test of [
+ // initialURL, searchString
+ ["about:blank", BOOKMARK_URL],
+ ["about:blank", new URL(BOOKMARK_URL).origin],
+ ["about:blank", new URL(BOOKMARK_URL).pathname],
+ [BOOKMARK_URL, BOOKMARK_URL],
+ [BOOKMARK_URL, new URL(BOOKMARK_URL).origin],
+ [BOOKMARK_URL, new URL(BOOKMARK_URL).pathname],
+ ]) {
+ await doPickResultTest(...test);
+ }
+});
+
+async function doPickResultTest(initialURL, searchString) {
+ info(
+ "doPickResultTest with args: " +
+ JSON.stringify({
+ initialURL,
+ searchString,
+ })
+ );
+
+ await BrowserTestUtils.withNewTab(initialURL, async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ fireInputEvent: true,
+ });
+
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+
+ // Arrow down to the bookmark result.
+ let firstResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ if (!firstResult.heuristic) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ let foundResult = false;
+ for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ if (result.url == BOOKMARK_URL) {
+ foundResult = true;
+ break;
+ }
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ Assert.ok(foundResult, "The bookmark result should have been found");
+
+ // Press enter.
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ Assert.equal(
+ gBrowser.currentURI.spec,
+ BOOKMARK_URL,
+ "Should have loaded the bookmarked URL"
+ );
+
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_preview.js b/browser/components/urlbar/tests/browser/browser_searchMode_preview.js
new file mode 100644
index 0000000000..19df744663
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_preview.js
@@ -0,0 +1,489 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests search mode preview.
+ */
+
+"use strict";
+
+const TEST_ENGINE_NAME = "Test";
+
+add_setup(async function () {
+ await SearchTestUtils.installSearchExtension({
+ name: TEST_ENGINE_NAME,
+ keyword: "@test",
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.quickactions", false]],
+ });
+
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+/**
+ * @param {Node} button
+ * A one-off button.
+ * @param {boolean} [isPreview]
+ * Whether the expected search mode should be a preview. Defaults to true.
+ * @returns {object}
+ * The search mode object expected when that one-off is selected.
+ */
+function getExpectedSearchMode(button, isPreview = true) {
+ let expectedSearchMode = {
+ entry: "oneoff",
+ isPreview,
+ };
+ if (button.engine) {
+ expectedSearchMode.engineName = button.engine.name;
+ let engine = Services.search.getEngineByName(button.engine.name);
+ if (engine.isGeneralPurposeEngine) {
+ expectedSearchMode.source = UrlbarUtils.RESULT_SOURCE.SEARCH;
+ }
+ } else {
+ expectedSearchMode.source = button.source;
+ }
+
+ return expectedSearchMode;
+}
+
+// Tests that cycling through token alias engines enters search mode preview.
+add_task(async function tokenAlias() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ });
+
+ let result;
+ while (gURLBar.searchMode?.engineName != TEST_ENGINE_NAME) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ let index = UrlbarTestUtils.getSelectedRowIndex(window);
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ let expectedSearchMode = {
+ engineName: result.searchParams.engine,
+ isPreview: true,
+ entry: "keywordoffer",
+ };
+ let engine = Services.search.getEngineByName(result.searchParams.engine);
+ if (engine.isGeneralPurposeEngine) {
+ expectedSearchMode.source = UrlbarUtils.RESULT_SOURCE.SEARCH;
+ }
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+ }
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+ // Test that we are in confirmed search mode.
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: result.searchParams.engine,
+ entry: "keywordoffer",
+ });
+ await UrlbarTestUtils.exitSearchMode(window);
+});
+
+// Tests that starting to type a query exits search mode preview in favour of
+// full search mode.
+add_task(async function startTyping() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ });
+ while (gURLBar.searchMode?.engineName != TEST_ENGINE_NAME) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ isPreview: true,
+ entry: "keywordoffer",
+ });
+
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("M");
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "keywordoffer",
+ });
+ await UrlbarTestUtils.exitSearchMode(window);
+});
+
+// Tests that highlighting a search shortcut Top Site enters search mode
+// preview.
+add_task(async function topSites() {
+ // Enable search shortcut Top Sites.
+ await PlacesUtils.history.clear();
+ await updateTopSites(
+ sites => sites && sites[0] && sites[0].searchTopSite,
+ true
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ fireInputEvent: true,
+ });
+
+ // We previously verified that the first Top Site is a search shortcut.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ let searchTopSite = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: searchTopSite.searchParams.engine,
+ isPreview: true,
+ entry: "topsites_urlbar",
+ });
+ await UrlbarTestUtils.exitSearchMode(window);
+});
+
+// Tests that search mode preview is exited when the view is closed.
+add_task(async function closeView() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ });
+
+ while (gURLBar.searchMode?.engineName != TEST_ENGINE_NAME) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ isPreview: true,
+ entry: "keywordoffer",
+ });
+
+ // We should close search mode when closing the view.
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ // Check search mode isn't re-entered when re-opening the view.
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ if (gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Tests that search more preview is exited when the user switches tabs.
+add_task(async function tabSwitch() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ });
+
+ while (gURLBar.searchMode?.engineName != TEST_ENGINE_NAME) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ isPreview: true,
+ entry: "keywordoffer",
+ });
+
+ // Open a new tab then switch back to the original tab.
+ let tab1 = gBrowser.selectedTab;
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+// Tests that search mode is previewed when the user down arrows through the
+// one-offs.
+add_task(async function oneOff_downArrow() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ let oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window);
+ await TestUtils.waitForCondition(
+ () => !oneOffs._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+
+ // Key down through all results.
+ for (let i = 0; i < resultCount; i++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+
+ // Key down again. The first one-off should be selected.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+
+ // Check for the one-off's search mode previews.
+ while (oneOffs.selectedButton != oneOffs.settingsButton) {
+ await UrlbarTestUtils.assertSearchMode(
+ window,
+ getExpectedSearchMode(oneOffs.selectedButton)
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+
+ // Check that selecting the search settings button leaves search mode preview.
+ Assert.equal(
+ oneOffs.selectedButton,
+ oneOffs.settingsButton,
+ "The settings button is selected."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ // Closing the view should also exit search mode preview.
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.assertSearchMode(window, null);
+});
+
+// Tests that search mode is previewed when the user Alt+down arrows through the
+// one-offs. This subtest also highlights a keywordoffer result (the first Top
+// Site) before Alt+Arrowing to the one-offs. This checks that the search mode
+// previews from keywordoffer results are overwritten by selected one-offs.
+add_task(async function oneOff_alt_downArrow() {
+ // Add some visits to a URL so we have multiple Top Sites.
+ await PlacesUtils.history.clear();
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits("https://example.com/");
+ }
+ await updateTopSites(
+ sites =>
+ sites &&
+ sites[0]?.searchTopSite &&
+ sites[1]?.url == "https://example.com/",
+ true
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ let oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window);
+ await TestUtils.waitForCondition(
+ () => !oneOffs._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+
+ // Key down to the first result and check that it enters search mode preview.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ let searchTopSite = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: searchTopSite.searchParams.engine,
+ isPreview: true,
+ entry: "topsites_urlbar",
+ });
+
+ // Alt+down. The first one-off should be selected.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ // Check for the one-offs' search mode previews.
+ while (oneOffs.selectedButton) {
+ await UrlbarTestUtils.assertSearchMode(
+ window,
+ getExpectedSearchMode(oneOffs.selectedButton)
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ }
+
+ // Now key down without a modifier. We should move to the second result and
+ // have no search mode preview.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 1,
+ "The second result is selected."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ // Arrow back up to the keywordoffer result and check for search mode preview.
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: searchTopSite.searchParams.engine,
+ isPreview: true,
+ entry: "topsites_urlbar",
+ });
+
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.assertSearchMode(window, null);
+});
+
+// Tests that search mode is previewed when the user is in full search mode
+// and down arrows through the one-offs.
+add_task(async function fullSearchMode_oneOff_downArrow() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ let oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window);
+ await TestUtils.waitForCondition(
+ () => !oneOffs._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+ let oneOffButtons = oneOffs.getSelectableButtons(true);
+
+ await UrlbarTestUtils.enterSearchMode(window);
+ let expectedSearchMode = getExpectedSearchMode(oneOffButtons[0], false);
+ // Sanity check: we are in the correct search mode.
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+
+ // Key down through all results.
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+ for (let i = 0; i < resultCount; i++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ // If the result is a shortcut, it will enter preview mode.
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ await UrlbarTestUtils.assertSearchMode(
+ window,
+ Object.assign(expectedSearchMode, {
+ isPreview: !!result.searchParams.keyword,
+ })
+ );
+ }
+
+ // Key down again. The first one-off should be selected.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ // Check that we show the correct preview as we cycle through the one-offs.
+ while (oneOffs.selectedButton != oneOffs.settingsButton) {
+ await UrlbarTestUtils.assertSearchMode(
+ window,
+ getExpectedSearchMode(oneOffs.selectedButton, true)
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+
+ // We should still be in the same search mode after cycling through all the
+ // one-offs.
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// Tests that search mode is previewed when the user is in full search mode
+// and alt+down arrows through the one-offs. This subtest also checks that
+// exiting full search mode while alt+arrowing through the one-offs enters
+// search mode preview for subsequent one-offs.
+add_task(async function fullSearchMode_oneOff_alt_downArrow() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ let oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window);
+ let oneOffButtons = oneOffs.getSelectableButtons(true);
+ await TestUtils.waitForCondition(
+ () => !oneOffs._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+
+ await UrlbarTestUtils.enterSearchMode(window);
+ let expectedSearchMode = getExpectedSearchMode(oneOffButtons[0], false);
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+
+ // Key down to the first result.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+
+ // Alt+down. The first one-off should be selected.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ // Cycle through the first half of the one-offs and verify that search mode
+ // preview is entered.
+ Assert.greater(
+ oneOffButtons.length,
+ 1,
+ "Sanity check: We should have at least two one-offs."
+ );
+ for (let i = 1; i < oneOffButtons.length / 2; i++) {
+ await UrlbarTestUtils.assertSearchMode(
+ window,
+ getExpectedSearchMode(oneOffs.selectedButton, true)
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ }
+ // Now click out of search mode.
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ // Now check for the remaining one-offs' search mode previews.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ while (oneOffs.selectedButton) {
+ await UrlbarTestUtils.assertSearchMode(
+ window,
+ getExpectedSearchMode(oneOffs.selectedButton, true)
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.assertSearchMode(window, null);
+});
+
+// Tests that the original search mode is preserved when going through some
+// one-off buttons and then back down in the results list.
+add_task(async function fullSearchMode_oneOff_restore_on_down() {
+ info("Add a few visits to top sites");
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits([
+ "https://1.example.com/",
+ "https://2.example.com/",
+ "https://3.example.com/",
+ ]);
+ }
+ await updateTopSites(sites => sites?.length > 2, false);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ let oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window);
+ let oneOffButtons = oneOffs.getSelectableButtons(true);
+ await TestUtils.waitForCondition(
+ () => !oneOffs._rebuilding,
+ "Waiting for one-offs to finish rebuilding"
+ );
+
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ });
+ let expectedSearchMode = getExpectedSearchMode(
+ oneOffButtons.find(b => b.source == UrlbarUtils.RESULT_SOURCE.HISTORY),
+ false
+ );
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+ info("Down to the first result");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+ info("Alt+down to the first one-off.");
+ Assert.greater(
+ oneOffButtons.length,
+ 1,
+ "Sanity check: We should have at least two one-offs."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ await UrlbarTestUtils.assertSearchMode(
+ window,
+ getExpectedSearchMode(oneOffs.selectedButton, true)
+ );
+ info("Go again down through the list of results");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+
+ // Now do a similar test without initial search mode.
+ info("Exit search mode.");
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ info("Down to the first result");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ info("select a one-off to start preview");
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ await UrlbarTestUtils.assertSearchMode(
+ window,
+ getExpectedSearchMode(oneOffs.selectedButton, true)
+ );
+ info("Go again through the list of results");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ await PlacesUtils.history.clear();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_sessionStore.js b/browser/components/urlbar/tests/browser/browser_searchMode_sessionStore.js
new file mode 100644
index 0000000000..ef3fabe636
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_sessionStore.js
@@ -0,0 +1,332 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests search mode and session store. Also tests that search mode is
+ * duplicated when duplicating tabs, since tab duplication is handled by session
+ * store.
+ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
+ TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.sys.mjs",
+});
+
+// This test takes a long time on the OS X 10.14 machines, so request a longer
+// timeout. See bug 1671045. This may also fix a different failure on Linux in
+// bug 1671087, but it's not clear. Regardless, a longer timeout won't hurt.
+requestLongerTimeout(5);
+
+const SEARCH_STRING = "test browser_sessionStore.js";
+const URL = "http://example.com/";
+
+// A URL in gInitialPages. We test this separately since SessionStore sometimes
+// takes different paths for these URLs.
+const INITIAL_URL = "about:newtab";
+
+// The following tasks make sure non-null search mode is restored.
+
+add_task(async function initialPageOnRestore() {
+ await doTest({
+ urls: [INITIAL_URL],
+ searchModeTabIndex: 0,
+ exitSearchMode: false,
+ switchTabsAfterEnteringSearchMode: false,
+ });
+});
+
+add_task(async function switchToInitialPage() {
+ await doTest({
+ urls: [URL, INITIAL_URL],
+ searchModeTabIndex: 1,
+ exitSearchMode: false,
+ switchTabsAfterEnteringSearchMode: true,
+ });
+});
+
+add_task(async function nonInitialPageOnRestore() {
+ await doTest({
+ urls: [URL],
+ searchModeTabIndex: 0,
+ exitSearchMode: false,
+ switchTabsAfterEnteringSearchMode: false,
+ });
+});
+
+add_task(async function switchToNonInitialPage() {
+ await doTest({
+ urls: [INITIAL_URL, URL],
+ searchModeTabIndex: 1,
+ exitSearchMode: false,
+ switchTabsAfterEnteringSearchMode: true,
+ });
+});
+
+// The following tasks enter and then exit search mode to make sure that no
+// search mode is restored.
+
+add_task(async function initialPageOnRestore_exit() {
+ await doTest({
+ urls: [INITIAL_URL],
+ searchModeTabIndex: 0,
+ exitSearchMode: true,
+ switchTabsAfterEnteringSearchMode: false,
+ });
+});
+
+add_task(async function switchToInitialPage_exit() {
+ await doTest({
+ urls: [URL, INITIAL_URL],
+ searchModeTabIndex: 1,
+ exitSearchMode: true,
+ switchTabsAfterEnteringSearchMode: true,
+ });
+});
+
+add_task(async function nonInitialPageOnRestore_exit() {
+ await doTest({
+ urls: [URL],
+ searchModeTabIndex: 0,
+ exitSearchMode: true,
+ switchTabsAfterEnteringSearchMode: false,
+ });
+});
+
+add_task(async function switchToNonInitialPage_exit() {
+ await doTest({
+ urls: [INITIAL_URL, URL],
+ searchModeTabIndex: 1,
+ exitSearchMode: true,
+ switchTabsAfterEnteringSearchMode: true,
+ });
+});
+
+/**
+ * The main test function. Opens some URLs in a new window, enters search mode
+ * in one of the tabs, closes the window, restores it, and makes sure that
+ * search mode is restored properly.
+ *
+ * @param {object} options
+ * Options object
+ * @param {Array} options.urls
+ * Array of string URLs to open.
+ * @param {number} options.searchModeTabIndex
+ * The index of the tab in which to enter search mode.
+ * @param {boolean} options.exitSearchMode
+ * If true, search mode will be immediately exited after entering it. Use
+ * this to make sure search mode is *not* restored after it's exited.
+ * @param {boolean} options.switchTabsAfterEnteringSearchMode
+ * If true, we'll switch to a tab other than the one that search mode was
+ * entered in before closing the window. `urls` should contain more than one
+ * URL in this case.
+ */
+async function doTest({
+ urls,
+ searchModeTabIndex,
+ exitSearchMode,
+ switchTabsAfterEnteringSearchMode,
+}) {
+ let searchModeURL = urls[searchModeTabIndex];
+ let otherTabIndex = (searchModeTabIndex + 1) % urls.length;
+ let otherURL = urls[otherTabIndex];
+
+ await withNewWindow(urls, async win => {
+ if (win.gBrowser.selectedTab != win.gBrowser.tabs[searchModeTabIndex]) {
+ await BrowserTestUtils.switchTab(
+ win.gBrowser,
+ win.gBrowser.tabs[searchModeTabIndex]
+ );
+ }
+
+ Assert.equal(
+ win.gBrowser.currentURI.spec,
+ searchModeURL,
+ `Sanity check: Tab at index ${searchModeTabIndex} is correct`
+ );
+ Assert.equal(
+ searchModeURL == INITIAL_URL,
+ win.gInitialPages.includes(win.gBrowser.currentURI.spec),
+ `Sanity check: ${searchModeURL} is or is not in gInitialPages as expected`
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: SEARCH_STRING,
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.enterSearchMode(win, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+
+ if (exitSearchMode) {
+ await UrlbarTestUtils.exitSearchMode(win);
+ }
+
+ // Make sure session store is updated.
+ await TabStateFlusher.flush(win.gBrowser.selectedBrowser);
+
+ if (switchTabsAfterEnteringSearchMode) {
+ await BrowserTestUtils.switchTab(
+ win.gBrowser,
+ win.gBrowser.tabs[otherTabIndex]
+ );
+ }
+ });
+
+ let restoredURL = switchTabsAfterEnteringSearchMode
+ ? otherURL
+ : searchModeURL;
+
+ let win = await restoreWindow(restoredURL);
+
+ Assert.equal(
+ win.gBrowser.currentURI.spec,
+ restoredURL,
+ "Sanity check: Initially selected tab in restored window is correct"
+ );
+
+ if (switchTabsAfterEnteringSearchMode) {
+ // Switch back to the tab with search mode.
+ await BrowserTestUtils.switchTab(
+ win.gBrowser,
+ win.gBrowser.tabs[searchModeTabIndex]
+ );
+ }
+
+ if (exitSearchMode) {
+ // If we exited search mode, it should be null.
+ await new Promise(r => win.setTimeout(r, 500));
+ await UrlbarTestUtils.assertSearchMode(win, null);
+ } else {
+ // If we didn't exit search mode, it should be restored.
+ await TestUtils.waitForCondition(
+ () => win.gURLBar.searchMode,
+ "Waiting for search mode to be restored"
+ );
+ await UrlbarTestUtils.assertSearchMode(win, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ });
+ Assert.equal(
+ win.gURLBar.value,
+ SEARCH_STRING,
+ "Search string should be restored"
+ );
+ }
+
+ await BrowserTestUtils.closeWindow(win);
+}
+
+async function openTabMenuFor(tab) {
+ let tabMenu = tab.ownerDocument.getElementById("tabContextMenu");
+
+ let tabMenuShown = BrowserTestUtils.waitForEvent(tabMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(
+ tab,
+ { type: "contextmenu" },
+ tab.ownerGlobal
+ );
+ await tabMenuShown;
+
+ return tabMenu;
+}
+
+// Tests that search mode is duplicated when duplicating tabs. Note that tab
+// duplication is handled by session store.
+add_task(async function duplicateTabs() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://example.net/"
+ );
+ gBrowser.selectedTab = tab;
+ // Enter search mode with a search string in the current tab.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: SEARCH_STRING,
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+
+ // Now duplicate the current tab using the context menu item.
+ const menu = await openTabMenuFor(gBrowser.selectedTab);
+ let tabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ gBrowser.currentURI.spec
+ );
+ menu.activateItem(document.getElementById("context_duplicateTab"));
+ let newTab = await tabPromise;
+ Assert.equal(
+ gBrowser.selectedTab,
+ newTab,
+ "Sanity check: The duplicated tab is now the selected tab"
+ );
+
+ // Wait for search mode, then check it and the input value.
+ await TestUtils.waitForCondition(
+ () => gURLBar.searchMode,
+ "Waiting for search mode to be duplicated/restored"
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ });
+ Assert.equal(
+ gURLBar.value,
+ SEARCH_STRING,
+ "Search string should be duplicated/restored"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(newTab);
+ gURLBar.handleRevert();
+});
+
+/**
+ * Opens a new browser window with the given URLs, calls a callback, and then
+ * closes the window.
+ *
+ * @param {Array} urls
+ * Array of string URLs to open.
+ * @param {Function} callback
+ * The callback.
+ */
+async function withNewWindow(urls, callback) {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ for (let url of urls) {
+ await BrowserTestUtils.openNewForegroundTab({
+ url,
+ gBrowser: win.gBrowser,
+ waitForLoad: url != "about:newtab",
+ });
+ if (url == "about:newtab") {
+ await TestUtils.waitForCondition(
+ () => win.gBrowser.currentURI.spec == "about:newtab",
+ "Waiting for about:newtab"
+ );
+ }
+ }
+ BrowserTestUtils.removeTab(win.gBrowser.tabs[0]);
+ await callback(win);
+ await BrowserTestUtils.closeWindow(win);
+}
+
+/**
+ * Uses SessionStore to reopen the last closed window.
+ *
+ * @param {string} expectedRestoredURL
+ * The URL you expect will be restored in the selected browser.
+ */
+async function restoreWindow(expectedRestoredURL) {
+ let winPromise = BrowserTestUtils.waitForNewWindow();
+ let win = SessionStore.undoCloseWindow(0);
+ await winPromise;
+ await TestUtils.waitForCondition(
+ () => win.gBrowser.currentURI.spec == expectedRestoredURL,
+ "Waiting for restored selected browser to have expected URI"
+ );
+ return win;
+}
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_setURI.js b/browser/components/urlbar/tests/browser/browser_searchMode_setURI.js
new file mode 100644
index 0000000000..46f0a84256
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_setURI.js
@@ -0,0 +1,119 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that search mode remains active or is exited when setURI is called,
+ * depending on the situation.
+ */
+
+"use strict";
+
+// Opens a new tab, does a search, enters search mode, and then manually calls
+// setURI. Uses a variety of initial URLs, search strings, and setURI arguments
+// in order to hit different branches in setURI. Search mode should remain
+// active or be exited as appropriate.
+add_task(async function setURI() {
+ for (let test of [
+ // initialURL, searchString, url, expectSearchMode
+
+ ["about:blank", "", null, true],
+ ["about:blank", "", "about:blank", true],
+ ["about:blank", "", "http://www.example.com/", true],
+
+ ["about:blank", "about:blank", null, false],
+ ["about:blank", "about:blank", "about:blank", false],
+ ["about:blank", "about:blank", "http://www.example.com/", false],
+
+ ["about:blank", "http://www.example.com/", null, true],
+ ["about:blank", "http://www.example.com/", "about:blank", true],
+ ["about:blank", "http://www.example.com/", "http://www.example.com/", true],
+
+ ["about:blank", "not a URL", null, true],
+ ["about:blank", "not a URL", "about:blank", true],
+ ["about:blank", "not a URL", "http://www.example.com/", true],
+
+ ["http://www.example.com/", "", null, true],
+ ["http://www.example.com/", "", "about:blank", true],
+ ["http://www.example.com/", "", "http://www.example.com/", true],
+
+ ["http://www.example.com/", "about:blank", null, false],
+ ["http://www.example.com/", "about:blank", "about:blank", false],
+ [
+ "http://www.example.com/",
+ "about:blank",
+ "http://www.example.com/",
+ false,
+ ],
+
+ ["http://www.example.com/", "http://www.example.com/", null, true],
+ ["http://www.example.com/", "http://www.example.com/", "about:blank", true],
+ [
+ "http://www.example.com/",
+ "http://www.example.com/",
+ "http://www.example.com/",
+ true,
+ ],
+
+ ["http://www.example.com/", "not a URL", null, true],
+ ["http://www.example.com/", "not a URL", "about:blank", true],
+ ["http://www.example.com/", "not a URL", "http://www.example.com/", true],
+ ]) {
+ await doSetURITest(...test);
+ }
+});
+
+async function doSetURITest(initialURL, searchString, url, expectSearchMode) {
+ info(
+ "doSetURITest with args: " +
+ JSON.stringify({
+ initialURL,
+ searchString,
+ url,
+ expectSearchMode,
+ })
+ );
+
+ await BrowserTestUtils.withNewTab(initialURL, async () => {
+ if (searchString) {
+ // Do a search with the search string.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ } else {
+ // Open top sites.
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ }
+
+ // Enter search mode and close the view.
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ Assert.strictEqual(
+ gBrowser.selectedBrowser.userTypedValue,
+ searchString,
+ `userTypedValue should be ${searchString}`
+ );
+
+ // Call setURI.
+ let uri = url ? Services.io.newURI(url) : null;
+ gURLBar.setURI(uri);
+
+ await UrlbarTestUtils.assertSearchMode(
+ window,
+ !expectSearchMode
+ ? null
+ : {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ }
+ );
+
+ gURLBar.handleRevert();
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_suggestions.js b/browser/components/urlbar/tests/browser/browser_searchMode_suggestions.js
new file mode 100644
index 0000000000..5aa3412580
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_suggestions.js
@@ -0,0 +1,579 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests search suggestions in search mode.
+ */
+
+const DEFAULT_ENGINE_NAME = "Test";
+const SUGGESTIONS_ENGINE_NAME = "searchSuggestionEngine.xml";
+const MANY_SUGGESTIONS_ENGINE_NAME = "searchSuggestionEngineMany.xml";
+const MAX_RESULT_COUNT = UrlbarPrefs.get("maxRichResults");
+
+let suggestionsEngine;
+let expectedFormHistoryResults = [];
+
+add_setup(async function () {
+ suggestionsEngine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + SUGGESTIONS_ENGINE_NAME,
+ });
+
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: DEFAULT_ENGINE_NAME,
+ keyword: "@test",
+ },
+ { setAsDefault: true }
+ );
+ await Services.search.moveEngine(suggestionsEngine, 0);
+
+ async function cleanup() {
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ }
+ await cleanup();
+ registerCleanupFunction(cleanup);
+
+ // Add some form history for our test engine.
+ for (let i = 0; i < MAX_RESULT_COUNT; i++) {
+ let value = `hello formHistory ${i}`;
+ await UrlbarTestUtils.formHistory.add([
+ { value, source: suggestionsEngine.name },
+ ]);
+ expectedFormHistoryResults.push({
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ searchParams: {
+ suggestion: value,
+ engine: suggestionsEngine.name,
+ },
+ });
+ }
+
+ // Add other form history.
+ await UrlbarTestUtils.formHistory.add([
+ { value: "hello formHistory global" },
+ { value: "hello formHistory other", source: "other engine" },
+ ]);
+
+ registerCleanupFunction(async () => {
+ await UrlbarTestUtils.formHistory.clear();
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", false],
+ ["browser.urlbar.suggest.quickactions", false],
+ ],
+ });
+});
+
+add_task(async function emptySearch() {
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.update2.emptySearchBehavior", 2]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ Assert.equal(gURLBar.value, "", "Urlbar value should be cleared.");
+ // For the empty search case, we expect to get the form history relative to
+ // the picked engine and no heuristic.
+ await checkResults(expectedFormHistoryResults);
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await SpecialPowers.popPrefEnv();
+ });
+});
+
+add_task(async function emptySearch_withRestyledHistory() {
+ // URLs with the same host as the search engine.
+ await PlacesTestUtils.addVisits([
+ `http://mochi.test/`,
+ `http://mochi.test/redirect`,
+ // Should not be returned because it's a redirect target.
+ {
+ uri: `http://mochi.test/target`,
+ transition: PlacesUtils.history.TRANSITIONS.REDIRECT_TEMPORARY,
+ referrer: `http://mochi.test/redirect`,
+ },
+ // Can be restyled and dupes form history.
+ "http://mochi.test:8888/?terms=hello+formHistory+0",
+ // Can be restyled but does not dupe form history.
+ "http://mochi.test:8888/?terms=ciao",
+ ]);
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.update2.emptySearchBehavior", 2]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ Assert.equal(gURLBar.value, "", "Urlbar value should be cleared.");
+ // For the empty search case, we expect to get the form history relative to
+ // the picked engine, history without redirects, and no heuristic.
+ await checkResults([
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ searchParams: {
+ suggestion: "ciao",
+ engine: suggestionsEngine.name,
+ },
+ },
+ ...expectedFormHistoryResults.slice(0, MAX_RESULT_COUNT - 3),
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ url: `http://mochi.test/redirect`,
+ },
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ url: `http://mochi.test/`,
+ },
+ ]);
+
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await SpecialPowers.popPrefEnv();
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function emptySearch_withRestyledHistory_noSearchHistory() {
+ // URLs with the same host as the search engine.
+ await PlacesTestUtils.addVisits([
+ `http://mochi.test/`,
+ `http://mochi.test/redirect`,
+ // Can be restyled but does not dupe form history.
+ "http://mochi.test:8888/?terms=ciao",
+ ]);
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.update2.emptySearchBehavior", 2],
+ ["browser.urlbar.maxHistoricalSearchSuggestions", 0],
+ ],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ Assert.equal(gURLBar.value, "", "Urlbar value should be cleared.");
+ // maxHistoricalSearchSuggestions == 0, so form history should not be
+ // present.
+ await checkResults([
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ url: `http://mochi.test/redirect`,
+ },
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ url: `http://mochi.test/`,
+ },
+ ]);
+
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await SpecialPowers.popPrefEnv();
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function emptySearch_behavior() {
+ // URLs with the same host as the search engine.
+ await PlacesTestUtils.addVisits([`http://mochi.test/`]);
+
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.update2.emptySearchBehavior", 0]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ Assert.equal(gURLBar.value, "", "Urlbar value should be cleared.");
+ // For the empty search case, we expect to get the form history relative to
+ // the picked engine, history without redirects, and no heuristic.
+ await checkResults([]);
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+
+ // We should still show history for empty searches when not in search mode.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: " ",
+ });
+ await checkResults([
+ {
+ heuristic: true,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ searchParams: {
+ query: " ",
+ engine: DEFAULT_ENGINE_NAME,
+ },
+ },
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ url: `http://mochi.test/`,
+ },
+ ]);
+ await UrlbarTestUtils.promisePopupClose(window);
+ await SpecialPowers.popPrefEnv();
+ });
+
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.update2.emptySearchBehavior", 1]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ Assert.equal(gURLBar.value, "", "Urlbar value should be cleared.");
+ // For the empty search case, we expect to get the form history relative to
+ // the picked engine, history without redirects, and no heuristic.
+ await checkResults([...expectedFormHistoryResults]);
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await SpecialPowers.popPrefEnv();
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function emptySearch_local() {
+ await PlacesTestUtils.addVisits([`http://mochi.test/`]);
+
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.update2.emptySearchBehavior", 0]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ });
+ Assert.equal(gURLBar.value, "", "Urlbar value should be cleared.");
+ // Even when emptySearchBehavior is 0, we still show the user's most frecent
+ // history for an empty search.
+ await checkResults([
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ url: `http://mochi.test/`,
+ },
+ ]);
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await SpecialPowers.popPrefEnv();
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function nonEmptySearch() {
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ let query = "hello";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: query,
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ Assert.equal(gURLBar.value, query, "Urlbar value should be set.");
+ // We expect to get the heuristic and all the suggestions.
+ await checkResults([
+ {
+ heuristic: true,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ searchParams: {
+ query,
+ engine: suggestionsEngine.name,
+ },
+ },
+ ...expectedFormHistoryResults.slice(0, MAX_RESULT_COUNT - 3),
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ searchParams: {
+ query,
+ suggestion: `${query}foo`,
+ engine: suggestionsEngine.name,
+ },
+ },
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ searchParams: {
+ query,
+ suggestion: `${query}bar`,
+ engine: suggestionsEngine.name,
+ },
+ },
+ ]);
+
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+add_task(async function nonEmptySearch_nonMatching() {
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ let query = "ciao";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: query,
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+ Assert.equal(gURLBar.value, query, "Urlbar value should be set.");
+ // We expect to get the heuristic and the remote suggestions since the local
+ // ones don't match.
+ await checkResults([
+ {
+ heuristic: true,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ searchParams: {
+ query,
+ engine: suggestionsEngine.name,
+ },
+ },
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ searchParams: {
+ query,
+ suggestion: `${query}foo`,
+ engine: suggestionsEngine.name,
+ },
+ },
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ searchParams: {
+ query,
+ suggestion: `${query}bar`,
+ engine: suggestionsEngine.name,
+ },
+ },
+ ]);
+
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+add_task(async function nonEmptySearch_withHistory() {
+ let manySuggestionsEngine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + MANY_SUGGESTIONS_ENGINE_NAME,
+ });
+ // URLs with the same host as the search engine.
+ let query = "ciao";
+ await PlacesTestUtils.addVisits([
+ `http://mochi.test/${query}`,
+ `http://mochi.test/${query}1`,
+ // Should not be returned because it has a different host, even if it
+ // matches the host in the path.
+ `http://example.com/mochi.test/${query}`,
+ ]);
+
+ function makeSuggestionResult(suffix) {
+ return {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ searchParams: {
+ query,
+ suggestion: `${query}${suffix}`,
+ engine: manySuggestionsEngine.name,
+ },
+ };
+ }
+
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: query,
+ });
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: manySuggestionsEngine.name,
+ });
+ Assert.equal(gURLBar.value, query, "Urlbar value should be set.");
+
+ await checkResults([
+ {
+ heuristic: true,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ searchParams: {
+ query,
+ engine: manySuggestionsEngine.name,
+ },
+ },
+ makeSuggestionResult("foo"),
+ makeSuggestionResult("bar"),
+ makeSuggestionResult("1"),
+ makeSuggestionResult("2"),
+ makeSuggestionResult("3"),
+ makeSuggestionResult("4"),
+ makeSuggestionResult("5"),
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ url: `http://mochi.test/${query}1`,
+ },
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ url: `http://mochi.test/${query}`,
+ },
+ ]);
+
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ info("Test again with history before suggestions");
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchSuggestionsFirst", false]],
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: query,
+ });
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: manySuggestionsEngine.name,
+ });
+ Assert.equal(gURLBar.value, query, "Urlbar value should be set.");
+
+ await checkResults([
+ {
+ heuristic: true,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ searchParams: {
+ query,
+ engine: manySuggestionsEngine.name,
+ },
+ },
+ makeSuggestionResult("foo"),
+ makeSuggestionResult("bar"),
+ makeSuggestionResult("1"),
+ makeSuggestionResult("2"),
+ makeSuggestionResult("3"),
+ makeSuggestionResult("4"),
+ makeSuggestionResult("5"),
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ url: `http://mochi.test/${query}1`,
+ },
+ {
+ heuristic: false,
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ url: `http://mochi.test/${query}`,
+ },
+ ]);
+
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await SpecialPowers.popPrefEnv();
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function nonEmptySearch_url() {
+ await BrowserTestUtils.withNewTab("about:robots", async function (browser) {
+ let query = "http://www.example.com/";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: query,
+ });
+ await UrlbarTestUtils.enterSearchMode(window);
+
+ // The heuristic result for a search that's a valid URL should be a search
+ // result, not a URL result.
+ await checkResults([
+ {
+ heuristic: true,
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ searchParams: {
+ query,
+ engine: suggestionsEngine.name,
+ },
+ },
+ ]);
+
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+});
+
+async function checkResults(expectedResults) {
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ expectedResults.length,
+ "Check results count."
+ );
+ for (let i = 0; i < expectedResults.length; ++i) {
+ info(`Checking result at index ${i}`);
+ let expected = expectedResults[i];
+ let actual = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+
+ // Check each property defined in the expected result against the property
+ // in the actual result.
+ for (let key of Object.keys(expected)) {
+ // For searchParams, remove undefined properties in the actual result so
+ // that the expected result doesn't need to include them.
+ if (key == "searchParams") {
+ let actualSearchParams = actual.searchParams;
+ for (let spKey of Object.keys(actualSearchParams)) {
+ if (actualSearchParams[spKey] === undefined) {
+ delete actualSearchParams[spKey];
+ }
+ }
+ }
+ Assert.deepEqual(
+ actual[key],
+ expected[key],
+ `${key} should match at result index ${i}.`
+ );
+ }
+ }
+}
diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_switchTabs.js b/browser/components/urlbar/tests/browser/browser_searchMode_switchTabs.js
new file mode 100644
index 0000000000..c4f541c9cd
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_switchTabs.js
@@ -0,0 +1,317 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that search mode is stored per tab and restored when switching tabs.
+ */
+
+"use strict";
+
+// Enters search mode using the one-off buttons.
+add_task(async function switchTabs() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.quickactions", false]],
+ });
+
+ // Open three tabs. We'll enter search mode in tabs 0 and 2.
+ let tabs = [];
+ for (let i = 0; i < 3; i++) {
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: "http://example.com/" + i,
+ });
+ tabs.push(tab);
+ }
+
+ // Switch to tab 0.
+ await BrowserTestUtils.switchTab(gBrowser, tabs[0]);
+
+ // Do a search and enter search mode. Pass fireInputEvent so that
+ // userTypedValue is set and restored when we switch back to this tab. This
+ // isn't really necessary but it simulates the user's typing, and it also
+ // means that we'll start a search when we switch back to this tab.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+
+ // Switch to tab 1. Search mode should be exited.
+ await BrowserTestUtils.switchTab(gBrowser, tabs[1]);
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ // Switch back to tab 0. We should do a search (for "test") and re-enter
+ // search mode.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ await BrowserTestUtils.switchTab(gBrowser, tabs[0]);
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ });
+ Assert.equal(
+ gURLBar.value,
+ "test",
+ "Value should remain the search string after switching back"
+ );
+
+ // Switch to tab 2. Search mode should be exited.
+ await BrowserTestUtils.switchTab(gBrowser, tabs[2]);
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ // Do another search (in tab 2) and enter search mode. Use a different source
+ // from tab 0 just to use something different.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test tab 2",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.TABS,
+ });
+
+ // Switch back to tab 0. We should do a search and still be in search mode.
+ searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ await BrowserTestUtils.switchTab(gBrowser, tabs[0]);
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ });
+ Assert.equal(
+ gURLBar.value,
+ "test",
+ "Value should remain the search string after switching back"
+ );
+
+ // Switch to tab 1. Search mode should be exited.
+ await BrowserTestUtils.switchTab(gBrowser, tabs[1]);
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ // Switch back to tab 2. We should do a search and re-enter search mode.
+ searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ await BrowserTestUtils.switchTab(gBrowser, tabs[2]);
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.TABS,
+ entry: "oneoff",
+ });
+ Assert.equal(
+ gURLBar.value,
+ "test tab 2",
+ "Value should remain the search string after switching back"
+ );
+
+ // Exit search mode.
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+
+ // Switch to tab 0. We should do a search and re-enter search mode.
+ searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ await BrowserTestUtils.switchTab(gBrowser, tabs[0]);
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ });
+ Assert.equal(
+ gURLBar.value,
+ "test",
+ "Value should remain the search string after switching back"
+ );
+
+ // Switch back to tab 2. We should do a search but search mode should be
+ // inactive.
+ searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ await BrowserTestUtils.switchTab(gBrowser, tabs[2]);
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ Assert.equal(
+ gURLBar.value,
+ "test tab 2",
+ "Value should remain the search string after switching back"
+ );
+
+ // Switch back to tab 0. We should do a search and re-enter search mode.
+ searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ await BrowserTestUtils.switchTab(gBrowser, tabs[0]);
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ });
+ Assert.equal(
+ gURLBar.value,
+ "test",
+ "Value should remain the search string after switching back"
+ );
+
+ // Exit search mode.
+ await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
+
+ // Switch back to tab 2. We should do a search but search mode should be
+ // inactive.
+ searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ await BrowserTestUtils.switchTab(gBrowser, tabs[2]);
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ Assert.equal(
+ gURLBar.value,
+ "test tab 2",
+ "Value should remain the search string after switching back"
+ );
+
+ // Switch back to tab 0. We should do a search but search mode should be
+ // inactive.
+ searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ await BrowserTestUtils.switchTab(gBrowser, tabs[0]);
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ Assert.equal(
+ gURLBar.value,
+ "test",
+ "Value should remain the search string after switching back"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ for (let tab of tabs) {
+ BrowserTestUtils.removeTab(tab);
+ }
+});
+
+// Start loading a SERP from search mode then immediately switch to a new tab so
+// the SERP finishes loading in the background. Switch back to the SERP tab and
+// observe that we don't re-enter search mode despite having an entry for that
+// tab in UrlbarInput._searchModesByBrowser. See bug 1675926.
+//
+// This subtest intermittently does not test bug 1675926 (NB: this does not mean
+// it is an intermittent failure). The false-positive occurs if the SERP page
+// finishes loading before we switch tabs. We include this subtest so we have
+// one covering real-world behaviour. A subtest that is guaranteed to test this
+// behaviour that does not simulate real world behaviour is included below.
+add_task(async function slow_load() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", false]],
+ });
+ const engineName = "Test";
+ let extension = await SearchTestUtils.installSearchExtension(
+ {
+ name: engineName,
+ },
+ { skipUnload: true }
+ );
+
+ const originalTab = gBrowser.selectedTab;
+ const newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.enterSearchMode(window, { engineName });
+
+ const loadPromise = BrowserTestUtils.browserLoaded(newTab.linkedBrowser);
+ // Select the search mode heuristic to load the example.com SERP.
+ EventUtils.synthesizeKey("KEY_Enter");
+ // Switch away from the tab before we let it load.
+ await BrowserTestUtils.switchTab(gBrowser, originalTab);
+ await loadPromise;
+
+ // Switch back to the search mode tab and confirm we don't restore search
+ // mode.
+ await BrowserTestUtils.switchTab(gBrowser, newTab);
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ BrowserTestUtils.removeTab(newTab);
+ await SpecialPowers.popPrefEnv();
+ await extension.unload();
+});
+
+// Tests the same behaviour as slow_load, but in a more reliable way using
+// non-real-world behaviour.
+add_task(async function slow_load_guaranteed() {
+ const engineName = "Test";
+ let extension = await SearchTestUtils.installSearchExtension(
+ {
+ name: engineName,
+ },
+ { skipUnload: true }
+ );
+
+ const backgroundTab = BrowserTestUtils.addTab(gBrowser);
+
+ // Simulate a tab that was in search mode, loaded a SERP, then was switched
+ // away from before setURI was called.
+ backgroundTab.ownerGlobal.gURLBar.searchMode = { engineName };
+ let loadPromise = BrowserTestUtils.browserLoaded(backgroundTab.linkedBrowser);
+ BrowserTestUtils.loadURIString(
+ backgroundTab.linkedBrowser,
+ "http://example.com/?search=test"
+ );
+ await loadPromise;
+
+ // Switch to the background mode tab and confirm we don't restore search mode.
+ await BrowserTestUtils.switchTab(gBrowser, backgroundTab);
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ BrowserTestUtils.removeTab(backgroundTab);
+ await extension.unload();
+});
+
+// Enters search mode by typing a restriction char with no search string.
+// Search mode and the search string should be restored after switching back to
+// the tab.
+add_task(async function userTypedValue_empty() {
+ await doUserTypedValueTest("");
+});
+
+// Enters search mode by typing a restriction char followed by a search string.
+// Search mode and the search string should be restored after switching back to
+// the tab.
+add_task(async function userTypedValue_nonEmpty() {
+ await doUserTypedValueTest("foo bar");
+});
+
+/**
+ * Enters search mode by typing a restriction char followed by a search string,
+ * opens a new tab and immediately closes it so we switch back to the search
+ * mode tab, and checks the search mode state and input value.
+ *
+ * @param {string} searchString
+ * The search string to enter search mode with.
+ */
+async function doUserTypedValueTest(searchString) {
+ let value = `${UrlbarTokenizer.RESTRICT.BOOKMARK} ${searchString}`;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "typed",
+ });
+ Assert.equal(
+ gURLBar.value,
+ searchString,
+ "Sanity check: Value is the search string"
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({ gBrowser });
+ BrowserTestUtils.removeTab(tab);
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "typed",
+ });
+ Assert.equal(
+ gURLBar.value,
+ searchString,
+ "Value should remain the search string after switching back"
+ );
+
+ gURLBar.handleRevert();
+}
diff --git a/browser/components/urlbar/tests/browser/browser_searchSettings.js b/browser/components/urlbar/tests/browser/browser_searchSettings.js
new file mode 100644
index 0000000000..2cded38c99
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchSettings.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function () {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "a",
+ });
+
+ // Since the current tab is blank the preferences pane will load there
+ let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ let button = document.getElementById("urlbar-anon-search-settings");
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ });
+ await loaded;
+
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ "about:preferences#search",
+ "Should have loaded the right page"
+ );
+ }
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchSingleWordNotification.js b/browser/components/urlbar/tests/browser/browser_searchSingleWordNotification.js
new file mode 100644
index 0000000000..36a065d58e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchSingleWordNotification.js
@@ -0,0 +1,372 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+"use strict";
+
+let gDNSResolved = false;
+add_setup(async function () {
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.fixup.domainwhitelist.localhost");
+ });
+});
+
+function promiseNotification(aBrowser, value, expected, input) {
+ return new Promise(resolve => {
+ let notificationBox = aBrowser.getNotificationBox(aBrowser.selectedBrowser);
+ if (expected) {
+ info("Waiting for " + value + " notification");
+ resolve(
+ BrowserTestUtils.waitForNotificationInNotificationBox(
+ notificationBox,
+ value
+ )
+ );
+ } else {
+ setTimeout(() => {
+ is(
+ notificationBox.getNotificationWithValue(value),
+ null,
+ `We are expecting to not get a notification for ${input}`
+ );
+ resolve();
+ }, 1000);
+ }
+ });
+}
+
+async function runURLBarSearchTest({
+ valueToOpen,
+ enterSearchMode,
+ expectSearch,
+ expectNotification,
+ expectDNSResolve,
+ aWindow = window,
+}) {
+ gDNSResolved = false;
+ // Test both directly setting a value and pressing enter, or setting the
+ // value through input events, like the user would do.
+ const setValueFns = [
+ value => {
+ aWindow.gURLBar.value = value;
+ if (enterSearchMode) {
+ // Ensure to open the panel.
+ UrlbarTestUtils.fireInputEvent(aWindow);
+ }
+ },
+ value => {
+ return UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: aWindow,
+ value,
+ });
+ },
+ ];
+
+ for (let i = 0; i < setValueFns.length; ++i) {
+ await setValueFns[i](valueToOpen);
+ let topic = "uri-fixup-check-dns";
+ let observer = (aSubject, aTopicInner, aData) => {
+ if (aTopicInner == topic) {
+ gDNSResolved = true;
+ }
+ };
+ Services.obs.addObserver(observer, topic);
+
+ if (enterSearchMode) {
+ if (!expectSearch) {
+ throw new Error("Must execute a search in search mode");
+ }
+ await UrlbarTestUtils.enterSearchMode(aWindow);
+ }
+
+ let expectedURI;
+ if (!expectSearch) {
+ expectedURI = "http://" + valueToOpen + "/";
+ } else {
+ expectedURI = (await Services.search.getDefault()).getSubmission(
+ valueToOpen,
+ null,
+ "keyword"
+ ).uri.spec;
+ }
+ aWindow.gURLBar.focus();
+ let docLoadPromise = BrowserTestUtils.waitForDocLoadAndStopIt(
+ expectedURI,
+ aWindow.gBrowser.selectedBrowser
+ );
+ EventUtils.synthesizeKey("VK_RETURN", {}, aWindow);
+
+ if (!enterSearchMode) {
+ await promiseNotification(
+ aWindow.gBrowser,
+ "keyword-uri-fixup",
+ expectNotification,
+ valueToOpen
+ );
+ }
+ await docLoadPromise;
+
+ if (expectNotification) {
+ let notificationBox = aWindow.gBrowser.getNotificationBox(
+ aWindow.gBrowser.selectedBrowser
+ );
+ let notification =
+ notificationBox.getNotificationWithValue("keyword-uri-fixup");
+ // Confirm the notification only on the last loop.
+ if (i == setValueFns.length - 1) {
+ docLoadPromise = BrowserTestUtils.waitForDocLoadAndStopIt(
+ "http://" + valueToOpen + "/",
+ aWindow.gBrowser.selectedBrowser
+ );
+ notification.buttonContainer.querySelector("button").click();
+ await docLoadPromise;
+ } else {
+ notificationBox.currentNotification.close();
+ }
+ }
+
+ Services.obs.removeObserver(observer, topic);
+ Assert.equal(
+ gDNSResolved,
+ expectDNSResolve,
+ `Should${expectDNSResolve ? "" : " not"} DNS resolve "${valueToOpen}"`
+ );
+ }
+}
+
+add_task(async function test_navigate_full_domain() {
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "about:blank"
+ ));
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await runURLBarSearchTest({
+ valueToOpen: "www.singlewordtest.org",
+ expectSearch: false,
+ expectNotification: false,
+ expectDNSResolve: false,
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function test_navigate_decimal_ip() {
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "about:blank"
+ ));
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await runURLBarSearchTest({
+ valueToOpen: "1234",
+ expectSearch: true,
+ expectNotification: false,
+ expectDNSResolve: false, // Possible IP in numeric format.
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function test_navigate_decimal_ip_with_path() {
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "about:blank"
+ ));
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await runURLBarSearchTest({
+ valueToOpen: "1234/12",
+ expectSearch: true,
+ expectNotification: false,
+ expectDNSResolve: false,
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function test_navigate_large_number() {
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "about:blank"
+ ));
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await runURLBarSearchTest({
+ valueToOpen: "123456789012345",
+ expectSearch: true,
+ expectNotification: false,
+ expectDNSResolve: false, // Possible IP in numeric format.
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function test_navigate_small_hex_number() {
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "about:blank"
+ ));
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await runURLBarSearchTest({
+ valueToOpen: "0x1f00ffff",
+ expectSearch: true,
+ expectNotification: false,
+ expectDNSResolve: false, // Possible IP in numeric format.
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function test_navigate_large_hex_number() {
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "about:blank"
+ ));
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await runURLBarSearchTest({
+ valueToOpen: "0x7f0000017f000001",
+ expectSearch: true,
+ expectNotification: false,
+ expectDNSResolve: false, // Possible IP in numeric format.
+ });
+ gBrowser.removeTab(tab);
+});
+
+function get_test_function_for_localhost_with_hostname(
+ hostName,
+ isPrivate = false
+) {
+ return async function test_navigate_single_host() {
+ info(`Test ${hostName}${isPrivate ? " in Private Browsing mode" : ""}`);
+ const pref = "browser.fixup.domainwhitelist.localhost";
+ let win;
+ if (isPrivate) {
+ let promiseWin = BrowserTestUtils.waitForNewWindow();
+ win = OpenBrowserWindow({ private: true });
+ await promiseWin;
+ await SimpleTest.promiseFocus(win);
+ } else {
+ win = window;
+ }
+
+ // Remove the domain from the whitelist
+ Services.prefs.setBoolPref(pref, false);
+
+ // The notification should not appear because the default value of
+ // browser.urlbar.dnsResolveSingleWordsAfterSearch is 0
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser: win.gBrowser,
+ url: "about:blank",
+ },
+ browser =>
+ runURLBarSearchTest({
+ valueToOpen: hostName,
+ expectSearch: true,
+ expectNotification: false,
+ expectDNSResolve: false,
+ aWindow: win,
+ })
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.dnsResolveSingleWordsAfterSearch", 1]],
+ });
+
+ // The notification should appear, unless we are in private browsing mode.
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser: win.gBrowser,
+ url: "about:blank",
+ },
+ browser =>
+ runURLBarSearchTest({
+ valueToOpen: hostName,
+ expectSearch: true,
+ expectNotification: true,
+ expectDNSResolve: true,
+ aWindow: win,
+ })
+ );
+
+ // check pref value
+ let prefValue = Services.prefs.getBoolPref(pref);
+ is(prefValue, !isPrivate, "Pref should have the correct state.");
+
+ // Now try again with the pref set.
+ // In a private window, the notification should appear again.
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser: win.gBrowser,
+ url: "about:blank",
+ },
+ browser =>
+ runURLBarSearchTest({
+ valueToOpen: hostName,
+ expectSearch: isPrivate,
+ expectNotification: isPrivate,
+ expectDNSResolve: isPrivate,
+ aWindow: win,
+ })
+ );
+
+ if (isPrivate) {
+ info("Waiting for private window to close");
+ await BrowserTestUtils.closeWindow(win);
+ await SimpleTest.promiseFocus(window);
+ }
+
+ await SpecialPowers.popPrefEnv();
+ };
+}
+
+add_task(get_test_function_for_localhost_with_hostname("localhost"));
+add_task(get_test_function_for_localhost_with_hostname("localhost."));
+add_task(get_test_function_for_localhost_with_hostname("localhost", true));
+
+add_task(async function test_dnsResolveSingleWordsAfterSearch() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.dnsResolveSingleWordsAfterSearch", 0],
+ ["browser.fixup.domainwhitelist.localhost", false],
+ ],
+ });
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "about:blank",
+ },
+ browser =>
+ runURLBarSearchTest({
+ valueToOpen: "localhost",
+ expectSearch: true,
+ expectNotification: false,
+ expectDNSResolve: false,
+ })
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_navigate_invalid_url() {
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "about:blank"
+ ));
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await runURLBarSearchTest({
+ valueToOpen: "mozilla is awesome",
+ expectSearch: true,
+ expectNotification: false,
+ expectDNSResolve: false,
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function test_search_mode() {
+ info("When in search mode we should never query the DNS");
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.suggest.enabled", false]],
+ });
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ "about:blank"
+ ));
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await runURLBarSearchTest({
+ enterSearchMode: true,
+ valueToOpen: "mozilla",
+ expectSearch: true,
+ expectNotification: false,
+ expectDNSResolve: false,
+ });
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_searchSuggestions.js b/browser/components/urlbar/tests/browser/browser_searchSuggestions.js
new file mode 100644
index 0000000000..8a226a3c4c
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchSuggestions.js
@@ -0,0 +1,341 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests checks that search suggestions can be acted upon correctly
+ * e.g. selection with modifiers, copying text.
+ */
+
+"use strict";
+
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+const MAX_CHARS_PREF = "browser.urlbar.maxCharsForSearchSuggestions";
+
+// Must run first.
+add_task(async function prepare() {
+ let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ setAsDefault: true,
+ });
+ await UrlbarTestUtils.formHistory.clear();
+ registerCleanupFunction(async function () {
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
+
+ // Clicking suggestions causes visits to search results pages, so clear that
+ // history now.
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ });
+});
+
+add_task(async function clickSuggestion() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ let [idx, suggestion, engineName] = await getFirstSuggestion();
+ Assert.equal(
+ engineName,
+ "browser_searchSuggestionEngine searchSuggestionEngine.xml",
+ "Expected suggestion engine"
+ );
+
+ let uri = (await Services.search.getDefault()).getSubmission(suggestion).uri;
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ uri.spec
+ );
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, idx);
+ EventUtils.synthesizeMouseAtCenter(element, {}, window);
+ await loadPromise;
+
+ let formHistory = (
+ await UrlbarTestUtils.formHistory.search({ source: engineName })
+ ).map(entry => entry.value);
+ Assert.deepEqual(
+ formHistory,
+ ["foofoo"],
+ "Should find form history after adding it"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+});
+
+async function testPressEnterOnSuggestion(
+ expectedUrl = null,
+ keyModifiers = {}
+) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ let [idx, suggestion, engineName] = await getFirstSuggestion();
+ Assert.equal(
+ engineName,
+ "browser_searchSuggestionEngine searchSuggestionEngine.xml",
+ "Expected suggestion engine"
+ );
+
+ let hasExpectedUrl = !!expectedUrl;
+ if (!expectedUrl) {
+ expectedUrl = (await Services.search.getDefault()).getSubmission(suggestion)
+ .uri.spec;
+ }
+
+ let promiseLoad = BrowserTestUtils.waitForDocLoadAndStopIt(
+ expectedUrl,
+ gBrowser.selectedBrowser
+ );
+
+ let promiseFormHistory;
+ if (!hasExpectedUrl) {
+ promiseFormHistory = UrlbarTestUtils.formHistory.promiseChanged("add");
+ }
+
+ for (let i = 0; i < idx; ++i) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ EventUtils.synthesizeKey("KEY_Enter", keyModifiers);
+
+ await promiseLoad;
+
+ if (!hasExpectedUrl) {
+ await promiseFormHistory;
+ let formHistory = (
+ await UrlbarTestUtils.formHistory.search({ source: engineName })
+ ).map(entry => entry.value);
+ Assert.deepEqual(
+ formHistory,
+ ["foofoo"],
+ "Should find form history after adding it"
+ );
+ }
+
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+}
+
+add_task(async function plainEnterOnSuggestion() {
+ await testPressEnterOnSuggestion();
+});
+
+add_task(async function ctrlEnterOnSuggestion() {
+ await testPressEnterOnSuggestion("https://www.foofoo.com/", {
+ ctrlKey: true,
+ });
+});
+
+add_task(async function copySuggestionText() {
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ let [idx, suggestion] = await getFirstSuggestion();
+ for (let i = 0; i < idx; ++i) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ gURLBar.select();
+ await SimpleTest.promiseClipboardChange(suggestion, () => {
+ goDoCommand("cmd_copy");
+ });
+});
+
+add_task(async function typeMaxChars() {
+ gURLBar.focus();
+
+ let maxChars = 10;
+ await SpecialPowers.pushPrefEnv({
+ set: [[MAX_CHARS_PREF, maxChars]],
+ });
+
+ // Make a string as long as maxChars and type it.
+ let value = "";
+ for (let i = 0; i < maxChars; i++) {
+ value += String.fromCharCode("a".charCodeAt(0) + i);
+ }
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ });
+
+ // Suggestions should be fetched since we allow them when typing, and the
+ // value so far isn't longer than maxChars anyway.
+ await assertSuggestions([value + "foo", value + "bar"]);
+
+ // Now type some additional chars. Suggestions should still be fetched since
+ // we allow them when typing.
+ for (let i = 0; i < 3; i++) {
+ let char = String.fromCharCode("z".charCodeAt(0) - i);
+ value += char;
+ EventUtils.synthesizeKey(char);
+ await assertSuggestions([value + "foo", value + "bar"]);
+ }
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function pasteMaxChars() {
+ gURLBar.focus();
+
+ let maxChars = 10;
+ await SpecialPowers.pushPrefEnv({
+ set: [[MAX_CHARS_PREF, maxChars]],
+ });
+
+ // Make a string as long as maxChars and paste it.
+ let value = "";
+ for (let i = 0; i < maxChars; i++) {
+ value += String.fromCharCode("a".charCodeAt(0) + i);
+ }
+ await selectAndPaste(value);
+
+ // Suggestions should be fetched since the pasted string is not longer than
+ // maxChars.
+ await assertSuggestions([value + "foo", value + "bar"]);
+
+ // Now type some additional chars. Suggestions should still be fetched since
+ // we allow them when typing.
+ for (let i = 0; i < 3; i++) {
+ let char = String.fromCharCode("z".charCodeAt(0) - i);
+ value += char;
+ EventUtils.synthesizeKey(char);
+ await assertSuggestions([value + "foo", value + "bar"]);
+ }
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function pasteMoreThanMaxChars() {
+ gURLBar.focus();
+
+ let maxChars = 10;
+ await SpecialPowers.pushPrefEnv({
+ set: [[MAX_CHARS_PREF, maxChars]],
+ });
+
+ // Make a string longer than maxChars and paste it.
+ let value = "";
+ for (let i = 0; i < 2 * maxChars; i++) {
+ value += String.fromCharCode("a".charCodeAt(0) + i);
+ }
+ await selectAndPaste(value);
+
+ // Suggestions should not be fetched since the value was pasted and it was
+ // longer than maxChars.
+ await assertSuggestions([]);
+
+ // Now type some additional chars. Suggestions should now be fetched since we
+ // allow them when typing.
+ for (let i = 0; i < 3; i++) {
+ let char = String.fromCharCode("z".charCodeAt(0) - i);
+ value += char;
+ EventUtils.synthesizeKey(char);
+ await assertSuggestions([value + "foo", value + "bar"]);
+ }
+
+ // Paste again. The string is longer than maxChars, so suggestions should not
+ // be fetched.
+ await selectAndPaste(value);
+ await assertSuggestions([]);
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function heuristicAddsFormHistory() {
+ await UrlbarTestUtils.formHistory.clear();
+ let formHistory = (await UrlbarTestUtils.formHistory.search()).map(
+ entry => entry.value
+ );
+ Assert.deepEqual(formHistory, [], "Form history should be empty initially");
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(result.heuristic);
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.SEARCH);
+ Assert.equal(result.searchParams.query, "foo");
+
+ let uri = (await Services.search.getDefault()).getSubmission("foo").uri;
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ uri.spec
+ );
+ let formHistoryPromise = UrlbarTestUtils.formHistory.promiseChanged("add");
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+ EventUtils.synthesizeMouseAtCenter(element, {}, window);
+ await loadPromise;
+
+ await formHistoryPromise;
+ formHistory = (
+ await UrlbarTestUtils.formHistory.search({
+ source: result.searchParams.engine,
+ })
+ ).map(entry => entry.value);
+ Assert.deepEqual(
+ formHistory,
+ ["foo"],
+ "Should find form history after adding it"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+});
+
+async function getFirstSuggestion() {
+ let results = await getSuggestionResults();
+ if (!results.length) {
+ return [-1, null, null];
+ }
+ let result = results[0];
+ return [
+ result.index,
+ result.searchParams.suggestion,
+ result.searchParams.engine,
+ ];
+}
+
+async function getSuggestionResults() {
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ let results = [];
+ let matchCount = UrlbarTestUtils.getResultCount(window);
+ for (let i = 0; i < matchCount; i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ if (
+ result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
+ result.searchParams.suggestion
+ ) {
+ result.index = i;
+ results.push(result);
+ }
+ }
+ return results;
+}
+
+async function assertSuggestions(expectedSuggestions) {
+ let results = await getSuggestionResults();
+ let actualSuggestions = results.map(r => r.searchParams.suggestion);
+ Assert.deepEqual(
+ actualSuggestions,
+ expectedSuggestions,
+ "Expected suggestions"
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/browser_searchTelemetry.js b/browser/components/urlbar/tests/browser/browser_searchTelemetry.js
new file mode 100644
index 0000000000..61ddff4c2d
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_searchTelemetry.js
@@ -0,0 +1,220 @@
+"use strict";
+
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const MAX_FORM_HISTORY_PREF = "browser.urlbar.maxHistoricalSearchSuggestions";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+// Must run first.
+add_task(async function prepare() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [SUGGEST_URLBAR_PREF, true],
+ [MAX_FORM_HISTORY_PREF, 2],
+ ],
+ });
+
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ setAsDefault: true,
+ });
+
+ registerCleanupFunction(async function () {
+ // Clicking urlbar results causes visits to their associated pages, so clear
+ // that history now.
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ });
+
+ // Move the mouse away from the urlbar one-offs so that a one-off engine is
+ // not inadvertently selected.
+ await EventUtils.promiseNativeMouseEvent({
+ type: "mousemove",
+ target: window.document.documentElement,
+ offsetX: 0,
+ offsetY: 0,
+ });
+});
+
+add_task(async function heuristicResultMouse() {
+ await compareCounts(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "heuristicResult",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Should be of type search"
+ );
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+ EventUtils.synthesizeMouseAtCenter(element, {});
+ await loadPromise;
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+ });
+});
+
+add_task(async function heuristicResultKeyboard() {
+ await compareCounts(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "heuristicResult",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Should be of type search"
+ );
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.sendKey("return");
+ await loadPromise;
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+ });
+});
+
+add_task(async function searchSuggestionMouse() {
+ await compareCounts(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "searchSuggestion",
+ });
+ let idx = await getFirstSuggestionIndex();
+ Assert.greaterOrEqual(idx, 0, "there should be a first suggestion");
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ idx
+ );
+ EventUtils.synthesizeMouseAtCenter(element, {});
+ await loadPromise;
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+ });
+});
+
+add_task(async function searchSuggestionKeyboard() {
+ await compareCounts(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "searchSuggestion",
+ });
+ let idx = await getFirstSuggestionIndex();
+ Assert.greaterOrEqual(idx, 0, "there should be a first suggestion");
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ while (idx--) {
+ EventUtils.sendKey("down");
+ }
+ EventUtils.sendKey("return");
+ await loadPromise;
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+ });
+});
+
+add_task(async function formHistoryMouse() {
+ await compareCounts(async function () {
+ await UrlbarTestUtils.formHistory.add(["foofoo", "foobar"]);
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ let index = await getFirstSuggestionIndex();
+ Assert.greaterOrEqual(index, 0, "there should be a first suggestion");
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.SEARCH);
+ Assert.equal(result.source, UrlbarUtils.RESULT_SOURCE.HISTORY);
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ index
+ );
+ EventUtils.synthesizeMouseAtCenter(element, {});
+ await loadPromise;
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+ });
+});
+
+add_task(async function formHistoryKeyboard() {
+ await compareCounts(async function () {
+ await UrlbarTestUtils.formHistory.add(["foofoo", "foobar"]);
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ let index = await getFirstSuggestionIndex();
+ Assert.greaterOrEqual(index, 0, "there should be a first suggestion");
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.SEARCH);
+ Assert.equal(result.source, UrlbarUtils.RESULT_SOURCE.HISTORY);
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ while (index--) {
+ EventUtils.sendKey("down");
+ }
+ EventUtils.sendKey("return");
+ await loadPromise;
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+ });
+});
+
+/**
+ * This does three things: gets current telemetry/FHR counts, calls
+ * clickCallback, gets telemetry/FHR counts again to compare them to the old
+ * counts.
+ *
+ * @param {Function} clickCallback Use this to open the urlbar popup and choose
+ * and click a result.
+ */
+async function compareCounts(clickCallback) {
+ // Search events triggered by clicks (not the Return key in the urlbar) are
+ // recorded in three places:
+ // * Telemetry histogram named "SEARCH_COUNTS"
+ // * FHR
+
+ let engine = await Services.search.getDefault();
+
+ let histogramKey = `other-${engine.name}.urlbar`;
+ let histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
+ histogram.clear();
+
+ gURLBar.focus();
+ await clickCallback();
+
+ TelemetryTestUtils.assertKeyedHistogramSum(histogram, histogramKey, 1);
+}
+
+/**
+ * Returns the index of the first search suggestion in the urlbar results.
+ *
+ * @returns {number} An index, or -1 if there are no search suggestions.
+ */
+async function getFirstSuggestionIndex() {
+ const matchCount = UrlbarTestUtils.getResultCount(window);
+ for (let i = 0; i < matchCount; i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ if (
+ result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
+ result.searchParams.suggestion
+ ) {
+ return i;
+ }
+ }
+ return -1;
+}
diff --git a/browser/components/urlbar/tests/browser/browser_search_bookmarks_from_bookmarks_menu.js b/browser/components/urlbar/tests/browser/browser_search_bookmarks_from_bookmarks_menu.js
new file mode 100644
index 0000000000..e42fcc9f7f
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_search_bookmarks_from_bookmarks_menu.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function searchBookmarksFromBooksmarksMenu() {
+ // Add Button to toolbar
+ CustomizableUI.addWidgetToArea(
+ "bookmarks-menu-button",
+ CustomizableUI.AREA_NAVBAR,
+ 0
+ );
+ let bookmarksMenuButton = document.getElementById("bookmarks-menu-button");
+ ok(bookmarksMenuButton, "Bookmarks Menu Button added");
+
+ // Open Bookmarks-Menu-Popup
+ let bookmarksMenuPopup = document.getElementById("BMB_bookmarksPopup");
+ let PopupShownPromise = BrowserTestUtils.waitForEvent(
+ bookmarksMenuPopup,
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(bookmarksMenuButton, {
+ type: "mousedown",
+ });
+ await PopupShownPromise;
+ ok(true, "Bookmarks Menu Popup shown");
+
+ // Click on 'Search Bookmarks'
+ let searchBookmarksButton = document.getElementById("BMB_searchBookmarks");
+ ok(
+ BrowserTestUtils.is_visible(
+ searchBookmarksButton,
+ "'Search Bookmarks Button' is visible."
+ )
+ );
+ EventUtils.synthesizeMouseAtCenter(searchBookmarksButton, {});
+
+ await new Promise(resolve => {
+ window.gURLBar.controller.addQueryListener({
+ onViewOpen() {
+ window.gURLBar.controller.removeQueryListener(this);
+ resolve();
+ },
+ });
+ });
+
+ // Verify URLBar is in search mode with correct restriction
+ is(
+ gURLBar.searchMode?.source,
+ UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ "Addressbar in correct mode."
+ );
+
+ resetCUIAndReinitUrlbarInput();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_search_history_from_history_panel.js b/browser/components/urlbar/tests/browser/browser_search_history_from_history_panel.js
new file mode 100644
index 0000000000..b901a87736
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_search_history_from_history_panel.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { CustomizableUITestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CustomizableUITestUtils.sys.mjs"
+);
+let gCUITestUtils = new CustomizableUITestUtils(window);
+
+add_task(async function searchHistoryFromHistoryPanel() {
+ // Add Button to toolbar
+ CustomizableUI.addWidgetToArea(
+ "history-panelmenu",
+ CustomizableUI.AREA_NAVBAR,
+ 0
+ );
+ registerCleanupFunction(() => {
+ resetCUIAndReinitUrlbarInput();
+ });
+
+ let historyButton = document.getElementById("history-panelmenu");
+ ok(historyButton, "History button appears in Panel Menu");
+
+ historyButton.click();
+
+ let historyPanel = document.getElementById("PanelUI-history");
+ let promise = BrowserTestUtils.waitForEvent(historyPanel, "ViewShown");
+ await promise;
+ ok(historyPanel.getAttribute("visible"), "History Panel is in view");
+
+ // Click on 'Search Bookmarks'
+ let searchHistoryButton = document.getElementById("appMenuSearchHistory");
+ ok(
+ BrowserTestUtils.is_visible(
+ searchHistoryButton,
+ "'Search History Button' is visible."
+ )
+ );
+ EventUtils.synthesizeMouseAtCenter(searchHistoryButton, {});
+
+ await new Promise(resolve => {
+ window.gURLBar.controller.addQueryListener({
+ onViewOpen() {
+ window.gURLBar.controller.removeQueryListener(this);
+ resolve();
+ },
+ });
+ });
+
+ // Verify URLBar is in search mode with correct restriction
+ is(
+ gURLBar.searchMode?.source,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ "Addressbar in correct mode."
+ );
+ gURLBar.searchMode = null;
+ gURLBar.blur();
+});
+
+add_task(async function searchHistoryFromAppMenuHistoryButton() {
+ // Open main menu and click on 'History' button
+ await gCUITestUtils.openMainMenu();
+ let historyButton = document.getElementById("appMenu-history-button");
+ historyButton.click();
+
+ let historyPanel = document.getElementById("PanelUI-history");
+ let promise = BrowserTestUtils.waitForEvent(historyPanel, "ViewShown");
+ await promise;
+ ok(historyPanel.getAttribute("visible"), "History Panel is in view");
+
+ // Click on 'Search Bookmarks'
+ let searchHistoryButton = document.getElementById("appMenuSearchHistory");
+ ok(
+ BrowserTestUtils.is_visible(
+ searchHistoryButton,
+ "'Search History Button' is visible."
+ )
+ );
+ EventUtils.synthesizeMouseAtCenter(searchHistoryButton, {});
+
+ await new Promise(resolve => {
+ window.gURLBar.controller.addQueryListener({
+ onViewOpen() {
+ window.gURLBar.controller.removeQueryListener(this);
+ resolve();
+ },
+ });
+ });
+
+ // Verify URLBar is in search mode with correct restriction
+ is(
+ gURLBar.searchMode?.source,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ "Addressbar in correct mode."
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_selectStaleResults.js b/browser/components/urlbar/tests/browser/browser_selectStaleResults.js
new file mode 100644
index 0000000000..16366f5b33
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_selectStaleResults.js
@@ -0,0 +1,311 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This test makes sure that arrowing down and up through the view's results
+// works correctly with regard to stale results.
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarView: "resource:///modules/UrlbarView.sys.mjs",
+});
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.quickactions", false]],
+ });
+
+ // Increase the timeout of the remove-stale-rows timer so that it doesn't
+ // interfere with the tests.
+ let originalRemoveStaleRowsTimeout = UrlbarView.removeStaleRowsTimeout;
+ UrlbarView.removeStaleRowsTimeout = 1000;
+ registerCleanupFunction(() => {
+ UrlbarView.removeStaleRowsTimeout = originalRemoveStaleRowsTimeout;
+ });
+});
+
+// This tests the case where queryContext.results.length < the number of rows in
+// the view, i.e., the view contains stale rows.
+add_task(async function viewContainsStaleRows() {
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ let maxResults = UrlbarPrefs.get("maxRichResults");
+ let halfResults = Math.floor(maxResults / 2);
+
+ // Add enough visits to pages with "xx" in the title to fill up half the view.
+ for (let i = 0; i < halfResults; i++) {
+ await PlacesTestUtils.addVisits({
+ uri: "http://mochi.test:8888/" + i,
+ title: "xx" + i,
+ });
+ }
+
+ // Add enough visits to pages with "x" in the title to fill up the entire
+ // view.
+ for (let i = 0; i < maxResults; i++) {
+ await PlacesTestUtils.addVisits({
+ uri: "http://example.com/" + i,
+ title: "x" + i,
+ });
+ }
+
+ gURLBar.focus();
+
+ // Search for "x" and wait for the search to finish. All the "x" results
+ // added above should be in the view. (Actually one fewer will be in the
+ // view due to the heuristic result, but that's not important.)
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "x",
+ fireInputEvent: true,
+ });
+
+ // Below we'll do a search for "xx". Get the row that will show the last
+ // result in that search.
+ let row = UrlbarTestUtils.getRowAt(window, halfResults);
+
+ // Add a mutation listener on that row. Wait for its "stale" attribute to be
+ // removed.
+ let mutationPromise = new Promise(resolve => {
+ let observer = new MutationObserver(mutations => {
+ for (let mut of mutations) {
+ if (mut.attributeName == "stale" && !row.hasAttribute("stale")) {
+ observer.disconnect();
+ resolve();
+ break;
+ }
+ }
+ });
+ observer.observe(row, { attributes: true });
+ });
+
+ // Type another "x" so that we search for "xx", but don't wait for the search
+ // to finish. Instead, wait for the row's stale attribute to be removed.
+ EventUtils.synthesizeKey("x");
+ info("Waiting for 'stale' attribute to be removed... ");
+ await mutationPromise;
+
+ // Now arrow down. The search, which is still ongoing, will now stop and the
+ // view won't be updated anymore.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+
+ // Wait for the search to stop.
+ info("Waiting for the search to stop... ");
+ await gURLBar.lastQueryContextPromise;
+
+ // The query context for the last search ("xx") should contain only
+ // halfResults + 1 results (+ 1 for the heuristic).
+ Assert.ok(gURLBar.controller._lastQueryContextWrapper);
+ let { queryContext } = gURLBar.controller._lastQueryContextWrapper;
+ Assert.ok(queryContext);
+ Assert.equal(queryContext.results.length, halfResults + 1);
+
+ // But there should be maxResults visible rows in the view.
+ let items = Array.from(
+ UrlbarTestUtils.getResultsContainer(window).children
+ ).filter(r => BrowserTestUtils.is_visible(r));
+ Assert.equal(items.length, maxResults);
+
+ // Arrow down through all the results. After arrowing down from the last "xx"
+ // result, the stale "x" results should be selected. We should *not* enter
+ // the one-off search buttons at that point.
+ for (let i = 1; i < maxResults; i++) {
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), i);
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.equal(result.element.row.result.rowIndex, i);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+
+ // Now the first one-off should be selected.
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), -1);
+ Assert.equal(gURLBar.view.oneOffSearchButtons.selectedButtonIndex, 0);
+
+ // Arrow back up through all the results.
+ for (let i = maxResults - 1; i >= 0; i--) {
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), i);
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// This tests the case where, before the search finishes, stale results have
+// been removed and replaced with non-stale results.
+add_task(async function staleReplacedWithFresh() {
+ // For this test, we need one set of results that's added quickly and another
+ // set that's added after a delay. We do an initial search and wait for both
+ // sets to be added. Then we do another search, but this time only wait for
+ // the fast results to be added, and then we arrow down to stop the search
+ // before the delayed results are added. The order in which things should
+ // happen after the second search goes like this:
+ //
+ // (1) second search
+ // (2) fast results are added
+ // (3) remove-stale-rows timer fires and removes stale rows (the rows from
+ // the delayed set of results from the first search)
+ // (4) we arrow down to stop the search
+ //
+ // We use history for the fast results and a slow search engine for the
+ // delayed results.
+ //
+ // NB: If this test ends up failing, it may be because the remove-stale-rows
+ // timer fires before the history results are added. i.e., steps 2 and 3
+ // above happen out of order. If that happens, try increasing
+ // UrlbarView.removeStaleRowsTimeout above.
+
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ // Enable search suggestions, and add an engine that returns suggestions on a
+ // delay.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", true]],
+ });
+ let engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngineSlow.xml",
+ });
+ let oldDefaultEngine = await Services.search.getDefault();
+ await Services.search.moveEngine(engine, 0);
+ await Services.search.setDefault(
+ engine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ let maxResults = UrlbarPrefs.get("maxRichResults");
+
+ // Add enough visits to pages with "test" in the title to fill up the entire
+ // view.
+ for (let i = 0; i < maxResults; i++) {
+ await PlacesTestUtils.addVisits({
+ uri: "http://example.com/" + i,
+ title: "test" + i,
+ });
+ }
+
+ gURLBar.focus();
+
+ // Search for "tes" and wait for the search to finish.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "tes",
+ fireInputEvent: true,
+ });
+
+ // Sanity check the results. They should be:
+ //
+ // tes -- Search with searchSuggestionEngineSlow [heuristic]
+ // tesfoo [search suggestion]
+ // tesbar [search suggestion]
+ // test9 [history]
+ // test8 [history]
+ // test7 [history]
+ // test6 [history]
+ // test5 [history]
+ // test4 [history]
+ // test3 [history]
+ let count = UrlbarTestUtils.getResultCount(window);
+ Assert.equal(count, maxResults);
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(result.heuristic);
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.ok(result.searchParams);
+ Assert.equal(result.searchParams.suggestion, "tesfoo");
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.ok(result.searchParams);
+ Assert.equal(result.searchParams.suggestion, "tesbar");
+ for (let i = 3; i < maxResults; i++) {
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.URL);
+ Assert.equal(result.title, "test" + (maxResults - i + 2));
+ }
+
+ // Below we'll do a search for "test" *but* not wait for the two search
+ // suggestion results to be added. We'll only wait for the history results to
+ // be added. To determine when the history results are added, use a mutation
+ // listener on the node containing the rows, and wait until the title of the
+ // next-to-last row is "test2". At that point, the results should be:
+ //
+ // test -- Search with searchSuggestionEngineSlow
+ // test9
+ // test8
+ // test7
+ // test6
+ // test5
+ // test4
+ // test3
+ // test2
+ // test1
+ let mutationPromise = new Promise(resolve => {
+ let observer = new MutationObserver(mutations => {
+ let row = UrlbarTestUtils.getRowAt(window, maxResults - 2);
+ if (row && row._elements.get("title").textContent == "test2") {
+ observer.disconnect();
+ resolve();
+ }
+ });
+ observer.observe(UrlbarTestUtils.getResultsContainer(window), {
+ subtree: true,
+ characterData: true,
+ childList: true,
+ attributes: true,
+ });
+ });
+
+ // Now type a "t" so that we search for "test", but only wait for history
+ // results to be added, as described above.
+ EventUtils.synthesizeKey("t");
+ info("Waiting for the 'test2' row... ");
+ await mutationPromise;
+
+ // Now arrow down. The search, which is still ongoing, will now stop and the
+ // view won't be updated anymore.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+
+ // Wait for the search to stop.
+ info("Waiting for the search to stop... ");
+ await gURLBar.lastQueryContextPromise;
+
+ // Sanity check the results. They should be as described above.
+ count = UrlbarTestUtils.getResultCount(window);
+ Assert.equal(count, maxResults);
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(result.heuristic);
+ Assert.equal(result.element.row.result.rowIndex, 0);
+ for (let i = 1; i < maxResults; i++) {
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.URL);
+ Assert.equal(result.title, "test" + (maxResults - i));
+ Assert.equal(result.element.row.result.rowIndex, i);
+ }
+
+ // Arrow down through all the results. After arrowing down from "test3", we
+ // should continue on to "test2". We should *not* enter the one-off search
+ // buttons at that point.
+ for (let i = 1; i < maxResults; i++) {
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), i);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+
+ // Now the first one-off should be selected.
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), -1);
+ Assert.equal(gURLBar.view.oneOffSearchButtons.selectedButtonIndex, 0);
+
+ // Arrow back up through all the results.
+ for (let i = maxResults - 1; i >= 0; i--) {
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), i);
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+ await SpecialPowers.popPrefEnv();
+ await Services.search.setDefault(
+ oldDefaultEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_selectionKeyNavigation.js b/browser/components/urlbar/tests/browser/browser_selectionKeyNavigation.js
new file mode 100644
index 0000000000..89ba179833
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_selectionKeyNavigation.js
@@ -0,0 +1,200 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test makes sure that the up/down and page-up/down properly adjust the
+// selection. See also browser_caret_navigation.js and
+// browser_urlbar_tabKeyBehavior.js.
+
+"use strict";
+
+const MAX_RESULTS = UrlbarPrefs.get("maxRichResults");
+
+add_setup(async function () {
+ for (let i = 0; i < MAX_RESULTS; i++) {
+ await PlacesTestUtils.addVisits("http://example.com/" + i);
+ }
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+add_task(async function downKey() {
+ for (const ctrlKey of [false, true]) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "The heuristic autofill result should be selected initially"
+ );
+ for (let i = 1; i < MAX_RESULTS; i++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown", { ctrlKey });
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), i);
+ }
+ EventUtils.synthesizeKey("KEY_ArrowDown", { ctrlKey });
+ let oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window);
+ Assert.ok(oneOffs.selectedButton, "A one-off should now be selected");
+ while (oneOffs.selectedButton) {
+ EventUtils.synthesizeKey("KEY_ArrowDown", { ctrlKey });
+ }
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "The heuristic autofill result should be selected again"
+ );
+ }
+});
+
+add_task(async function upKey() {
+ for (const ctrlKey of [false, true]) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "The heuristic autofill result should be selected initially"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowUp", { ctrlKey });
+ let oneOffs = UrlbarTestUtils.getOneOffSearchButtons(window);
+ Assert.ok(oneOffs.selectedButton, "A one-off should now be selected");
+ while (oneOffs.selectedButton) {
+ EventUtils.synthesizeKey("KEY_ArrowUp", { ctrlKey });
+ }
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ MAX_RESULTS - 1,
+ "The last result should be selected"
+ );
+ for (let i = 1; i < MAX_RESULTS; i++) {
+ EventUtils.synthesizeKey("KEY_ArrowUp", { ctrlKey });
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ MAX_RESULTS - i - 1
+ );
+ }
+ }
+});
+
+add_task(async function pageDownKey() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "The heuristic autofill result should be selected initially"
+ );
+ let pageCount = Math.ceil((MAX_RESULTS - 1) / UrlbarUtils.PAGE_UP_DOWN_DELTA);
+ for (let i = 0; i < pageCount; i++) {
+ EventUtils.synthesizeKey("KEY_PageDown");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ Math.min((i + 1) * UrlbarUtils.PAGE_UP_DOWN_DELTA, MAX_RESULTS - 1)
+ );
+ }
+ EventUtils.synthesizeKey("KEY_PageDown");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "Page down at end should wrap around to first result"
+ );
+});
+
+add_task(async function pageUpKey() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "The heuristic autofill result should be selected initially"
+ );
+ EventUtils.synthesizeKey("KEY_PageUp");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ MAX_RESULTS - 1,
+ "Page up at start should wrap around to last result"
+ );
+ let pageCount = Math.ceil((MAX_RESULTS - 1) / UrlbarUtils.PAGE_UP_DOWN_DELTA);
+ for (let i = 0; i < pageCount; i++) {
+ EventUtils.synthesizeKey("KEY_PageUp");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ Math.max(MAX_RESULTS - 1 - (i + 1) * UrlbarUtils.PAGE_UP_DOWN_DELTA, 0)
+ );
+ }
+});
+
+add_task(async function pageDownKeyShowsView() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeKey("KEY_PageDown");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window));
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), 0);
+});
+
+add_task(async function pageUpKeyShowsView() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeKey("KEY_PageUp");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window));
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), 0);
+});
+
+add_task(async function pageDownKeyWithCtrlKey() {
+ const previousTab = gBrowser.selectedTab;
+ const currentTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_PageDown", { ctrlKey: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.equal(gBrowser.selectedTab, previousTab);
+ BrowserTestUtils.removeTab(currentTab);
+});
+
+add_task(async function pageUpKeyWithCtrlKey() {
+ const previousTab = gBrowser.selectedTab;
+ const currentTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_PageUp", { ctrlKey: true });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.equal(gBrowser.selectedTab, previousTab);
+ BrowserTestUtils.removeTab(currentTab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_separatePrivateDefault.js b/browser/components/urlbar/tests/browser/browser_separatePrivateDefault.js
new file mode 100644
index 0000000000..8cdc0e746b
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_separatePrivateDefault.js
@@ -0,0 +1,223 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests the 'Search in a Private Window' result of the address bar.
+// Tests here don't have a different private engine, for that see
+// browser_separatePrivateDefault_differentPrivateEngine.js
+
+const serverInfo = {
+ scheme: "http",
+ host: "localhost",
+ port: 20709, // Must be identical to what is in searchSuggestionEngine2.xml
+};
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", true],
+ ["browser.search.separatePrivateDefault.urlbarResult.enabled", true],
+ ["browser.search.separatePrivateDefault", true],
+ ["browser.urlbar.suggest.searches", true],
+ ],
+ });
+
+ // Add some history for the empty panel.
+ await PlacesTestUtils.addVisits([
+ {
+ uri: "http://example.com/",
+ transition: PlacesUtils.history.TRANSITIONS.TYPED,
+ },
+ ]);
+
+ // Add a search suggestion engine and move it to the front so that it appears
+ // as the first one-off.
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ setAsDefault: true,
+ setAsDefaultPrivate: true,
+ });
+
+ // Add another engine in the first one-off position.
+ let engine2 = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "POSTSearchEngine.xml",
+ });
+ await Services.search.moveEngine(engine2, 0);
+
+ // Add an engine with an alias.
+ await SearchTestUtils.installSearchExtension({
+ name: "MozSearch",
+ keyword: "alias",
+ });
+
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+async function AssertNoPrivateResult(win) {
+ let count = await UrlbarTestUtils.getResultCount(win);
+ Assert.ok(count > 0, "Sanity check result count");
+ for (let i = 0; i < count; ++i) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(win, i);
+ Assert.ok(
+ result.type != UrlbarUtils.RESULT_TYPE.SEARCH ||
+ !result.searchParams.inPrivateWindow,
+ "Check this result is not a 'Search in a Private Window' one"
+ );
+ }
+}
+
+async function AssertPrivateResult(win, engine, isPrivateEngine) {
+ let count = await UrlbarTestUtils.getResultCount(win);
+ Assert.ok(count > 1, "Sanity check result count");
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Check result type"
+ );
+ Assert.ok(result.searchParams.inPrivateWindow, "Check inPrivateWindow");
+ Assert.equal(
+ result.searchParams.isPrivateEngine,
+ isPrivateEngine,
+ "Check isPrivateEngine"
+ );
+ Assert.equal(
+ result.searchParams.engine,
+ engine.name,
+ "Check the search engine"
+ );
+ return result;
+}
+
+add_task(async function test_nonsearch() {
+ info(
+ "Test that 'Search in a Private Window' does not appear with non-search results"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exa",
+ });
+ await AssertNoPrivateResult(window);
+});
+
+add_task(async function test_search() {
+ info(
+ "Test that 'Search in a Private Window' appears with only search results"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "unique198273982173",
+ });
+ await AssertPrivateResult(window, await Services.search.getDefault(), false);
+});
+
+add_task(async function test_search_urlbar_result_disabled() {
+ info("Test that 'Search in a Private Window' does not appear when disabled");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.urlbarResult.enabled", false],
+ ],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "unique198273982173",
+ });
+ await AssertNoPrivateResult(window);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_search_disabled_suggestions() {
+ info(
+ "Test that 'Search in a Private Window' appears if suggestions are disabled"
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", false]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "unique198273982173",
+ });
+ await AssertPrivateResult(window, await Services.search.getDefault(), false);
+ await SpecialPowers.popPrefEnv();
+});
+
+// TODO: (Bug 1658620) Write a new subtest for this behaviour with the update2
+// pref on.
+// add_task(async function test_oneoff_selected_keyboard() {
+// info(
+// "Test that 'Search in a Private Window' with keyboard opens the selected one-off engine if there's no private engine"
+// );
+// await SpecialPowers.pushPrefEnv({
+// set: [
+// ["browser.urlbar.update2", false],
+// ["browser.urlbar.update2.oneOffsRefresh", false],
+// ],
+// });
+// await UrlbarTestUtils.promiseAutocompleteResultPopup({
+// window,
+// value: "unique198273982173",
+// });
+// await AssertPrivateResult(window, await Services.search.getDefault(), false);
+// // Select the 'Search in a Private Window' result, alt down to select the
+// // first one-off button, Enter. It should open a pb window, but using the
+// // selected one-off engine.
+// let promiseWindow = BrowserTestUtils.waitForNewWindow({
+// url:
+// "http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/print_postdata.sjs",
+// });
+// // Select the private result.
+// EventUtils.synthesizeKey("KEY_ArrowDown");
+// // Select the first one-off button.
+// EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+// EventUtils.synthesizeKey("VK_RETURN");
+// let win = await promiseWindow;
+// Assert.ok(
+// PrivateBrowsingUtils.isWindowPrivate(win),
+// "Should open a private window"
+// );
+// await BrowserTestUtils.closeWindow(win);
+// await SpecialPowers.popPrefEnv();
+// });
+
+// TODO: (Bug 1658620) Write a new subtest for this behaviour with the update2
+// pref on.
+// add_task(async function test_oneoff_selected_mouse() {
+// info(
+// "Test that 'Search in a Private Window' with mouse opens the selected one-off engine if there's no private engine"
+// );
+// await SpecialPowers.pushPrefEnv({
+// set: [
+// ["browser.urlbar.update2", false],
+// ["browser.urlbar.update2.oneOffsRefresh", false],
+// ],
+// });
+// await UrlbarTestUtils.promiseAutocompleteResultPopup({
+// window,
+// value: "unique198273982173",
+// });
+// await AssertPrivateResult(window, await Services.search.getDefault(), false);
+// // Select the 'Search in a Private Window' result, alt down to select the
+// // first one-off button, Enter. It should open a pb window, but using the
+// // selected one-off engine.
+// let promiseWindow = BrowserTestUtils.waitForNewWindow({
+// url:
+// "http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/print_postdata.sjs",
+// });
+// // Select the private result.
+// EventUtils.synthesizeKey("KEY_ArrowDown");
+// // Select the first one-off button.
+// EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+// // Click on the result.
+// let element = UrlbarTestUtils.getSelectedRow(window);
+// EventUtils.synthesizeMouseAtCenter(element, {});
+// let win = await promiseWindow;
+// Assert.ok(
+// PrivateBrowsingUtils.isWindowPrivate(win),
+// "Should open a private window"
+// );
+// await BrowserTestUtils.closeWindow(win);
+// await SpecialPowers.popPrefEnv();
+// });
diff --git a/browser/components/urlbar/tests/browser/browser_separatePrivateDefault_differentEngine.js b/browser/components/urlbar/tests/browser/browser_separatePrivateDefault_differentEngine.js
new file mode 100644
index 0000000000..58a60d68a9
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_separatePrivateDefault_differentEngine.js
@@ -0,0 +1,354 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests the 'Search in a Private Window' result of the address bar.
+
+const serverInfo = {
+ scheme: "http",
+ host: "localhost",
+ port: 20709, // Must be identical to what is in searchSuggestionEngine2.xml
+};
+
+let gAliasEngine;
+let gPrivateEngine;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", true],
+ ["browser.search.separatePrivateDefault.urlbarResult.enabled", true],
+ ["browser.search.separatePrivateDefault", true],
+ ["browser.urlbar.suggest.searches", true],
+ ],
+ });
+
+ // Add some history for the empty panel.
+ await PlacesTestUtils.addVisits([
+ {
+ uri: "http://example.com/",
+ transition: PlacesUtils.history.TRANSITIONS.TYPED,
+ },
+ ]);
+
+ // Add a search suggestion engine and move it to the front so that it appears
+ // as the first one-off.
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ setAsDefault: true,
+ });
+ gPrivateEngine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine2.xml",
+ setAsDefaultPrivate: true,
+ });
+
+ // Add another engine in the first one-off position.
+ let engine2 = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "POSTSearchEngine.xml",
+ });
+ await Services.search.moveEngine(engine2, 0);
+
+ // Add an engine with an alias.
+ await SearchTestUtils.installSearchExtension({
+ name: "MozSearch",
+ keyword: "alias",
+ });
+ gAliasEngine = Services.search.getEngineByName("MozSearch");
+
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+async function AssertNoPrivateResult(win) {
+ let count = await UrlbarTestUtils.getResultCount(win);
+ for (let i = 0; i < count; ++i) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(win, i);
+ Assert.ok(
+ result.type != UrlbarUtils.RESULT_TYPE.SEARCH ||
+ !result.searchParams.inPrivateWindow,
+ "Check this result is not a 'Search in a Private Window' one"
+ );
+ }
+}
+
+async function AssertPrivateResult(win, engine, isPrivateEngine) {
+ let count = await UrlbarTestUtils.getResultCount(win);
+ Assert.ok(count > 1, "Sanity check result count");
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Check result type"
+ );
+ Assert.ok(result.searchParams.inPrivateWindow, "Check inPrivateWindow");
+ Assert.equal(
+ result.searchParams.isPrivateEngine,
+ isPrivateEngine,
+ "Check isPrivateEngine"
+ );
+ Assert.equal(
+ result.searchParams.engine,
+ engine.name,
+ "Check the search engine"
+ );
+ return result;
+}
+
+// Tests from here on have a different default private engine.
+
+add_task(async function test_search_private_engine() {
+ info(
+ "Test that 'Search in a Private Window' reports a separate private engine"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "unique198273982173",
+ });
+ await AssertPrivateResult(window, gPrivateEngine, true);
+});
+
+add_task(async function test_privateWindow() {
+ info(
+ "Test that 'Search in a Private Window' does not appear in a private window"
+ );
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: privateWin,
+ value: "unique198273982173",
+ });
+ await AssertNoPrivateResult(privateWin);
+ await BrowserTestUtils.closeWindow(privateWin);
+});
+
+add_task(async function test_permanentPB() {
+ info(
+ "Test that 'Search in a Private Window' does not appear in Permanent Private Browsing"
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.privatebrowsing.autostart", true]],
+ });
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "unique198273982173",
+ });
+ await AssertNoPrivateResult(win);
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_openPBWindow() {
+ info(
+ "Test that 'Search in a Private Window' opens the search in a new Private Window"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "unique198273982173",
+ });
+ await AssertPrivateResult(
+ window,
+ await Services.search.getDefaultPrivate(),
+ true
+ );
+
+ await withHttpServer(serverInfo, async () => {
+ let promiseWindow = BrowserTestUtils.waitForNewWindow({
+ url: "http://localhost:20709/?terms=unique198273982173",
+ maybeErrorPage: true,
+ });
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("VK_RETURN");
+ let win = await promiseWindow;
+ Assert.ok(
+ PrivateBrowsingUtils.isWindowPrivate(win),
+ "Should open a private window"
+ );
+ await BrowserTestUtils.closeWindow(win);
+ });
+});
+
+// TODO: (Bug 1658620) Write a new subtest for this behaviour with the update2
+// pref on.
+// add_task(async function test_oneoff_selected_with_private_engine_mouse() {
+// info(
+// "Test that 'Search in a Private Window' opens the private engine even if a one-off is selected"
+// );
+// await SpecialPowers.pushPrefEnv({
+// set: [
+// ["browser.urlbar.update2", false],
+// ["browser.urlbar.update2.oneOffsRefresh", false],
+// ],
+// });
+// await UrlbarTestUtils.promiseAutocompleteResultPopup({
+// window,
+// value: "unique198273982173",
+// });
+// await AssertPrivateResult(
+// window,
+// await Services.search.getDefaultPrivate(),
+// true
+// );
+
+// await withHttpServer(serverInfo, async () => {
+// // Select the 'Search in a Private Window' result, alt down to select the
+// // first one-off button, Click on the result. It should open a pb window using
+// // the private search engine, because it has been set.
+// let promiseWindow = BrowserTestUtils.waitForNewWindow({
+// url: "http://localhost:20709/?terms=unique198273982173",
+// maybeErrorPage: true,
+// });
+// // Select the private result.
+// EventUtils.synthesizeKey("KEY_ArrowDown");
+// // Select the first one-off button.
+// EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+// // Click on the result.
+// let element = UrlbarTestUtils.getSelectedRow(window);
+// EventUtils.synthesizeMouseAtCenter(element, {});
+// let win = await promiseWindow;
+// Assert.ok(
+// PrivateBrowsingUtils.isWindowPrivate(win),
+// "Should open a private window"
+// );
+// await BrowserTestUtils.closeWindow(win);
+// });
+// await SpecialPowers.popPrefEnv();
+// });
+
+// TODO: (Bug 1658620) Write a new subtest for this behaviour with the update2
+// pref on.
+// add_task(async function test_oneoff_selected_with_private_engine_keyboard() {
+// info(
+// "Test that 'Search in a Private Window' opens the private engine even if a one-off is selected"
+// );
+// await SpecialPowers.pushPrefEnv({
+// set: [
+// ["browser.urlbar.update2", false],
+// ["browser.urlbar.update2.oneOffsRefresh", false],
+// ],
+// });
+// await UrlbarTestUtils.promiseAutocompleteResultPopup({
+// window,
+// value: "unique198273982173",
+// });
+// await AssertPrivateResult(
+// window,
+// await Services.search.getDefaultPrivate(),
+// true
+// );
+
+// await withHttpServer(serverInfo, async () => {
+// // Select the 'Search in a Private Window' result, alt down to select the
+// // first one-off button, Enter. It should open a pb window, but using the
+// // selected one-off engine.
+// let promiseWindow = BrowserTestUtils.waitForNewWindow({
+// url: "http://localhost:20709/?terms=unique198273982173",
+// maybeErrorPage: true,
+// });
+// // Select the private result.
+// EventUtils.synthesizeKey("KEY_ArrowDown");
+// // Select the first one-off button.
+// EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+// EventUtils.synthesizeKey("VK_RETURN");
+// let win = await promiseWindow;
+// Assert.ok(
+// PrivateBrowsingUtils.isWindowPrivate(win),
+// "Should open a private window"
+// );
+// await BrowserTestUtils.closeWindow(win);
+// });
+// await SpecialPowers.popPrefEnv();
+// });
+
+add_task(async function test_alias_no_query() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.update2.emptySearchBehavior", 2]],
+ });
+ info(
+ "Test that 'Search in a Private Window' doesn't appear if an alias is typed with no query"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "alias ",
+ });
+ // Wait for the second new search that starts when search mode is entered.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: gAliasEngine.name,
+ entry: "typed",
+ });
+ await AssertNoPrivateResult(window);
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_alias_query() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.update2.emptySearchBehavior", 2]],
+ });
+ info(
+ "Test that 'Search in a Private Window' appears when an alias is typed with a query"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "alias something",
+ });
+ // Wait for the second new search that starts when search mode is entered.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: "MozSearch",
+ entry: "typed",
+ });
+ await AssertPrivateResult(window, gAliasEngine, true);
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_restrict() {
+ info(
+ "Test that 'Search in a Private Window' doesn's appear for just the restriction token"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: UrlbarTokenizer.RESTRICT.SEARCH,
+ });
+ await AssertNoPrivateResult(window);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: UrlbarTokenizer.RESTRICT.SEARCH + " ",
+ });
+ await AssertNoPrivateResult(window);
+ await UrlbarTestUtils.exitSearchMode(window);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: " " + UrlbarTokenizer.RESTRICT.SEARCH,
+ });
+ await AssertNoPrivateResult(window);
+});
+
+add_task(async function test_restrict_search() {
+ info(
+ "Test that 'Search in a Private Window' has the right string with the restriction token"
+ );
+ let engine = await Services.search.getDefaultPrivate();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: UrlbarTokenizer.RESTRICT.SEARCH + "test",
+ });
+ let result = await AssertPrivateResult(window, engine, true);
+ Assert.equal(result.searchParams.query, "test");
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test" + UrlbarTokenizer.RESTRICT.SEARCH,
+ });
+ result = await AssertPrivateResult(window, engine, true);
+ Assert.equal(result.searchParams.query, "test");
+});
diff --git a/browser/components/urlbar/tests/browser/browser_shortcuts_add_search_engine.js b/browser/components/urlbar/tests/browser/browser_shortcuts_add_search_engine.js
new file mode 100644
index 0000000000..1fef68de30
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_shortcuts_add_search_engine.js
@@ -0,0 +1,243 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test adding engines through search shortcut buttons.
+// A more complete coverage of the detection of engines is available in
+// browser_add_search_engine.js
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+const BASE_URL =
+ "http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", false]],
+ });
+ // Ensure initial state.
+ UrlbarTestUtils.getOneOffSearchButtons(window).invalidateCache();
+});
+
+add_task(async function shortcuts_none() {
+ info("Checks the shortcuts with a page that doesn't offer any engines.");
+ let url = "http://mochi.test:8888/";
+ await BrowserTestUtils.withNewTab(url, async () => {
+ let shortcutButtons = UrlbarTestUtils.getOneOffSearchButtons(window);
+ let rebuildPromise = BrowserTestUtils.waitForEvent(
+ shortcutButtons,
+ "rebuild"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await rebuildPromise;
+
+ Assert.ok(
+ !Array.from(shortcutButtons.buttons.children).some(b =>
+ b.classList.contains("searchbar-engine-one-off-add-engine")
+ ),
+ "Check there's no buttons to add engines"
+ );
+ });
+});
+
+add_task(async function test_shortcuts() {
+ await do_test_shortcuts(button => {
+ info("Click on button");
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ });
+ await do_test_shortcuts(button => {
+ info("Enter on button");
+ let shortcuts = UrlbarTestUtils.getOneOffSearchButtons(window);
+ while (shortcuts.selectedButton != button) {
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ }
+ EventUtils.synthesizeKey("KEY_Enter");
+ });
+});
+
+/**
+ * Test add engine shortcuts.
+ *
+ * @param {Function} activateTask a function receiveing the shortcut button to
+ * activate as argument. The scope of this function is to activate the
+ * shortcut button.
+ */
+async function do_test_shortcuts(activateTask) {
+ info("Checks the shortcuts with a page that offers two engines.");
+ let url = getRootDirectory(gTestPath) + "add_search_engine_two.html";
+ await BrowserTestUtils.withNewTab(url, async () => {
+ let shortcutButtons = UrlbarTestUtils.getOneOffSearchButtons(window);
+ let rebuildPromise = BrowserTestUtils.waitForEvent(
+ shortcutButtons,
+ "rebuild"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await rebuildPromise;
+
+ let addEngineButtons = Array.from(shortcutButtons.buttons.children).filter(
+ b => b.classList.contains("searchbar-engine-one-off-add-engine")
+ );
+ Assert.equal(
+ addEngineButtons.length,
+ 2,
+ "Check there's two buttons to add engines"
+ );
+
+ for (let button of addEngineButtons) {
+ Assert.ok(BrowserTestUtils.is_visible(button));
+ Assert.ok(button.hasAttribute("image"));
+ await document.l10n.translateElements([button]);
+ Assert.ok(
+ button.getAttribute("tooltiptext").includes("add_search_engine_")
+ );
+ Assert.ok(
+ button.getAttribute("engine-name").startsWith("add_search_engine_")
+ );
+ Assert.ok(
+ button.classList.contains("searchbar-engine-one-off-add-engine")
+ );
+ }
+
+ info("Activate the first button");
+ rebuildPromise = BrowserTestUtils.waitForEvent(shortcutButtons, "rebuild");
+ let enginePromise = promiseEngine("engine-added", "add_search_engine_0");
+ await activateTask(addEngineButtons[0]);
+ info("await engine install");
+ let engine = await enginePromise;
+ info("await rebuild");
+ await rebuildPromise;
+
+ Assert.ok(
+ UrlbarTestUtils.isPopupOpen(window),
+ "Urlbar view is still open."
+ );
+
+ addEngineButtons = Array.from(shortcutButtons.buttons.children).filter(b =>
+ b.classList.contains("searchbar-engine-one-off-add-engine")
+ );
+ Assert.equal(
+ addEngineButtons.length,
+ 1,
+ "Check there's one button to add engines"
+ );
+ Assert.equal(
+ addEngineButtons[0].getAttribute("engine-name"),
+ "add_search_engine_1"
+ );
+ let installedEngineButton = addEngineButtons[0].previousElementSibling;
+ Assert.equal(installedEngineButton.engine.name, "add_search_engine_0");
+
+ info("Remove the added engine");
+ rebuildPromise = BrowserTestUtils.waitForEvent(shortcutButtons, "rebuild");
+ await Services.search.removeEngine(engine);
+ await rebuildPromise;
+ Assert.equal(
+ Array.from(shortcutButtons.buttons.children).filter(b =>
+ b.classList.contains("searchbar-engine-one-off-add-engine")
+ ).length,
+ 2,
+ "Check there's two buttons to add engines"
+ );
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ info("Switch to a new tab and check the buttons are not persisted");
+ await BrowserTestUtils.withNewTab("about:robots", async () => {
+ rebuildPromise = BrowserTestUtils.waitForEvent(
+ shortcutButtons,
+ "rebuild"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await rebuildPromise;
+ Assert.ok(
+ !Array.from(shortcutButtons.buttons.children).some(b =>
+ b.classList.contains("searchbar-engine-one-off-add-engine")
+ ),
+ "Check there's no option to add engines"
+ );
+ });
+ });
+}
+
+add_task(async function shortcuts_many() {
+ info("Checks the shortcuts with a page that offers many engines.");
+ let url = getRootDirectory(gTestPath) + "add_search_engine_many.html";
+ await BrowserTestUtils.withNewTab(url, async () => {
+ let shortcutButtons = UrlbarTestUtils.getOneOffSearchButtons(window);
+ let rebuildPromise = BrowserTestUtils.waitForEvent(
+ shortcutButtons,
+ "rebuild"
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await rebuildPromise;
+
+ let addEngineButtons = Array.from(shortcutButtons.buttons.children).filter(
+ b => b.classList.contains("searchbar-engine-one-off-add-engine")
+ );
+ Assert.equal(
+ addEngineButtons.length,
+ gURLBar.addSearchEngineHelper.maxInlineEngines,
+ "Check there's a maximum of `maxInlineEngines` buttons to add engines"
+ );
+ });
+});
+
+function promiseEngine(expectedData, expectedEngineName) {
+ info(`Waiting for engine ${expectedData}`);
+ return TestUtils.topicObserved(
+ "browser-search-engine-modified",
+ (engine, data) => {
+ info(`Got engine ${engine.wrappedJSObject.name} ${data}`);
+ return (
+ expectedData == data &&
+ expectedEngineName == engine.wrappedJSObject.name
+ );
+ }
+ ).then(([engine, data]) => engine);
+}
+
+add_task(async function shortcuts_without_other_engines() {
+ info("Checks the shortcuts without other engines.");
+
+ info("Remove search engines except default");
+ const defaultEngine = Services.search.defaultEngine;
+ const engines = await Services.search.getVisibleEngines();
+ for (const engine of engines) {
+ if (defaultEngine.name !== engine.name) {
+ await Services.search.removeEngine(engine);
+ }
+ }
+
+ info("Remove local engines");
+ for (const { pref } of UrlbarUtils.LOCAL_SEARCH_MODES) {
+ await SpecialPowers.pushPrefEnv({
+ set: [[`browser.urlbar.${pref}`, false]],
+ });
+ }
+
+ const url = getRootDirectory(gTestPath) + "add_search_engine_many.html";
+ await BrowserTestUtils.withNewTab(url, async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+
+ const shortcutButtons = UrlbarTestUtils.getOneOffSearchButtons(window);
+ Assert.ok(shortcutButtons.container.hidden, "It should be hidden");
+ });
+
+ Services.search.restoreDefaultEngines();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_speculative_connect.js b/browser/components/urlbar/tests/browser/browser_speculative_connect.js
new file mode 100644
index 0000000000..3b98169699
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_speculative_connect.js
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 test ensures that we setup a speculative network connection to
+// the site in various cases:
+// 1. search engine if it's the first result
+// 2. mousedown event before the http request happens(in mouseup).
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine2.xml";
+
+const serverInfo = {
+ scheme: "http",
+ host: "localhost",
+ port: 20709, // Must be identical to what is in searchSuggestionEngine2.xml
+};
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.autoFill", true],
+ ["browser.urlbar.suggest.searches", true],
+ ["browser.urlbar.speculativeConnect.enabled", true],
+ // In mochitest this number is 0 by default but we have to turn it on.
+ ["network.http.speculative-parallel-limit", 6],
+ // The http server is using IPv4, so it's better to disable IPv6 to avoid
+ // weird networking problem.
+ ["network.dns.disableIPv6", true],
+ ],
+ });
+
+ // Ensure we start from a clean situation.
+ await PlacesUtils.history.clear();
+
+ await PlacesTestUtils.addVisits([
+ {
+ uri: `${serverInfo.scheme}://${serverInfo.host}:${serverInfo.port}`,
+ title: "test visit for speculative connection",
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ },
+ ]);
+
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ setAsDefault: true,
+ });
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ });
+});
+
+add_task(async function search_test() {
+ // We speculative connect to the search engine only if suggestions are enabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.suggest.enabled", true]],
+ });
+ await withHttpServer(serverInfo, async server => {
+ let connectionNumber = server.connectionNumber;
+ info("Searching for 'foo'");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ fireInputEvent: true,
+ });
+ // Check if the first result is with type "searchengine"
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ details.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "The first result is a search"
+ );
+ await UrlbarTestUtils.promiseSpeculativeConnections(
+ server,
+ connectionNumber + 1
+ );
+ });
+});
+
+add_task(async function popup_mousedown_test() {
+ // Disable search suggestions and autofill, to avoid other speculative
+ // connections.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.suggest.enabled", false],
+ ["browser.urlbar.autoFill", false],
+ ],
+ });
+ await withHttpServer(serverInfo, async server => {
+ let connectionNumber = server.connectionNumber;
+ let searchString = "ocal";
+ let completeValue = `${serverInfo.scheme}://${serverInfo.host}:${serverInfo.port}/`;
+ info(`Searching for '${searchString}'`);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ let listitem = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ details.url,
+ completeValue,
+ "The second item has the url we visited."
+ );
+
+ info("Clicking on the second result");
+ EventUtils.synthesizeMouseAtCenter(listitem, { type: "mousedown" }, window);
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRow(window),
+ listitem,
+ "The second item is selected"
+ );
+ await UrlbarTestUtils.promiseSpeculativeConnections(
+ server,
+ connectionNumber + 1
+ );
+ });
+});
+
+add_task(async function test_autofill() {
+ // Disable search suggestions but enable autofill.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.suggest.enabled", false],
+ ["browser.urlbar.autoFill", true],
+ ],
+ });
+ await withHttpServer(serverInfo, async server => {
+ let connectionNumber = server.connectionNumber;
+ let searchString = serverInfo.host;
+ info(`Searching for '${searchString}'`);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ let completeValue = `${serverInfo.scheme}://${serverInfo.host}:${serverInfo.port}/`;
+ Assert.equal(details.url, completeValue, `Autofilled value is as expected`);
+ await UrlbarTestUtils.promiseSpeculativeConnections(
+ server,
+ connectionNumber + 1
+ );
+ });
+});
+
+add_task(async function test_autofill_privateContext() {
+ info("Autofill in private context.");
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ registerCleanupFunction(async () => {
+ let promisePBExit = TestUtils.topicObserved("last-pb-context-exited");
+ await BrowserTestUtils.closeWindow(privateWin);
+ await promisePBExit;
+ });
+ await withHttpServer(serverInfo, async server => {
+ let connectionNumber = server.connectionNumber;
+ let searchString = serverInfo.host;
+ info(`Searching for '${searchString}'`);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: privateWin,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(privateWin, 0);
+ let completeValue = `${serverInfo.scheme}://${serverInfo.host}:${serverInfo.port}/`;
+ Assert.equal(details.url, completeValue, `Autofilled value is as expected`);
+ await UrlbarTestUtils.promiseSpeculativeConnections(
+ server,
+ connectionNumber
+ );
+ });
+});
+
+add_task(async function test_no_heuristic_result() {
+ info("Don't speculative connect on results addition if there's no heuristic");
+ await withHttpServer(serverInfo, async server => {
+ let connectionNumber = server.connectionNumber;
+ info(`Searching for the empty string`);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ fireInputEvent: true,
+ });
+ ok(UrlbarTestUtils.getResultCount(window) > 0, "Has results");
+ let result = await UrlbarTestUtils.getSelectedRow(window);
+ Assert.strictEqual(result, null, `Should have no selection`);
+ await UrlbarTestUtils.promiseSpeculativeConnections(
+ server,
+ connectionNumber
+ );
+ });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js b/browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js
new file mode 100644
index 0000000000..de352efb59
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js
@@ -0,0 +1,236 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Tests that we don't speculatively connect when user certificates are installed
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+const host = "localhost";
+let uri;
+let handshakeDone = false;
+let expectingChooseCertificate = false;
+let chooseCertificateCalled = false;
+
+const clientAuthDialogs = {
+ chooseCertificate(
+ hostname,
+ port,
+ organization,
+ issuerOrg,
+ certList,
+ selectedIndex,
+ rememberClientAuthCertificate
+ ) {
+ ok(
+ expectingChooseCertificate,
+ `${
+ expectingChooseCertificate ? "" : "not "
+ }expecting chooseCertificate to be called`
+ );
+ is(certList.length, 1, "should have only one client certificate available");
+ selectedIndex.value = 0;
+ rememberClientAuthCertificate.value = false;
+ ok(
+ !chooseCertificateCalled,
+ "chooseCertificate should only be called once"
+ );
+ chooseCertificateCalled = true;
+ return true;
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogs"]),
+};
+
+/**
+ * A helper class to use with nsITLSServerConnectionInfo.setSecurityObserver.
+ * Implements nsITLSServerSecurityObserver and simulates an extremely
+ * rudimentary HTTP server that expects an HTTP/1.1 GET request and responds
+ * with a 200 OK.
+ */
+class SecurityObserver {
+ constructor(input, output) {
+ this.input = input;
+ this.output = output;
+ }
+
+ onHandshakeDone(socket, status) {
+ info("TLS handshake done");
+ handshakeDone = true;
+
+ let output = this.output;
+ this.input.asyncWait(
+ {
+ onInputStreamReady(readyInput) {
+ try {
+ let request = NetUtil.readInputStreamToString(
+ readyInput,
+ readyInput.available()
+ );
+ ok(
+ request.startsWith("GET /") && request.includes("HTTP/1.1"),
+ "expecting an HTTP/1.1 GET request"
+ );
+ let response =
+ "HTTP/1.1 200 OK\r\nContent-Type:text/plain\r\n" +
+ "Connection:Close\r\nContent-Length:2\r\n\r\nOK";
+ output.write(response, response.length);
+ } catch (e) {
+ console.log(e.message);
+ // This will fail when we close the speculative connection.
+ }
+ },
+ },
+ 0,
+ 0,
+ Services.tm.currentThread
+ );
+ }
+}
+
+function startServer(cert) {
+ let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
+ Ci.nsITLSServerSocket
+ );
+ tlsServer.init(-1, true, -1);
+ tlsServer.serverCert = cert;
+
+ let securityObservers = [];
+
+ let listener = {
+ onSocketAccepted(socket, transport) {
+ info("Accepted TLS client connection");
+ let connectionInfo = transport.securityCallbacks.getInterface(
+ Ci.nsITLSServerConnectionInfo
+ );
+ let input = transport.openInputStream(0, 0, 0);
+ let output = transport.openOutputStream(0, 0, 0);
+ connectionInfo.setSecurityObserver(new SecurityObserver(input, output));
+ },
+
+ onStopListening() {
+ info("onStopListening");
+ for (let securityObserver of securityObservers) {
+ securityObserver.input.close();
+ securityObserver.output.close();
+ }
+ },
+ };
+
+ tlsServer.setSessionTickets(false);
+ tlsServer.setRequestClientCertificate(Ci.nsITLSServerSocket.REQUEST_ALWAYS);
+
+ tlsServer.asyncListen(listener);
+
+ return tlsServer;
+}
+
+let server;
+
+function getTestServerCertificate() {
+ const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ for (const cert of certDB.getCerts()) {
+ if (cert.commonName == "Mochitest client") {
+ return cert;
+ }
+ }
+ return null;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.autoFill", true],
+ // Turn off search suggestion so we won't speculative connect to the search engine.
+ ["browser.search.suggest.enabled", false],
+ ["browser.urlbar.speculativeConnect.enabled", true],
+ // In mochitest this number is 0 by default but we have to turn it on.
+ ["network.http.speculative-parallel-limit", 6],
+ // The http server is using IPv4, so it's better to disable IPv6 to avoid weird
+ // networking problem.
+ ["network.dns.disableIPv6", true],
+ ["security.default_personal_cert", "Ask Every Time"],
+ ],
+ });
+
+ let clientAuthDialogsCID = MockRegistrar.register(
+ "@mozilla.org/nsClientAuthDialogs;1",
+ clientAuthDialogs
+ );
+
+ let cert = getTestServerCertificate();
+ server = startServer(cert);
+ uri = `https://${host}:${server.port}/`;
+ info(`running tls server at ${uri}`);
+ await PlacesTestUtils.addVisits([
+ {
+ uri,
+ title: "test visit for speculative connection",
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ },
+ ]);
+
+ certOverrideService.rememberValidityOverride(
+ "localhost",
+ server.port,
+ {},
+ cert,
+ true
+ );
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ MockRegistrar.unregister(clientAuthDialogsCID);
+ certOverrideService.clearValidityOverride("localhost", server.port, {});
+ });
+});
+
+add_task(
+ async function popup_mousedown_no_client_cert_dialog_until_navigate_test() {
+ // To not trigger autofill, search keyword starts from the second character.
+ let searchString = host.substr(1, 4);
+ let completeValue = uri;
+ info(`Searching for '${searchString}'`);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ let listitem = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ info(`The url of the second item is ${details.url}`);
+ is(details.url, completeValue, "The second item has the url we visited.");
+
+ expectingChooseCertificate = false;
+ EventUtils.synthesizeMouseAtCenter(listitem, { type: "mousedown" }, window);
+ is(
+ UrlbarTestUtils.getSelectedRow(window),
+ listitem,
+ "The second item is selected"
+ );
+
+ // We shouldn't have triggered a speculative connection, because a client
+ // certificate is installed.
+ SimpleTest.requestFlakyTimeout("Wait for UI");
+ await new Promise(resolve => setTimeout(resolve, 200));
+
+ // Now mouseup, expect that we choose a client certificate, and expect that
+ // we successfully load a page.
+ expectingChooseCertificate = true;
+ EventUtils.synthesizeMouseAtCenter(listitem, { type: "mouseup" }, window);
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ ok(chooseCertificateCalled, "chooseCertificate must have been called");
+ server.close();
+ }
+);
diff --git a/browser/components/urlbar/tests/browser/browser_stop.js b/browser/components/urlbar/tests/browser/browser_stop.js
new file mode 100644
index 0000000000..285071a3ff
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_stop.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests ensures the urlbar reflects the correct value if a page load is
+ * stopped immediately after loading.
+ */
+
+"use strict";
+
+const goodURL = "http://mochi.test:8888/";
+const badURL = "http://mochi.test:8888/whatever.html";
+
+add_task(async function () {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, goodURL);
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ is(
+ gURLBar.value,
+ BrowserUIUtils.trimURL(goodURL),
+ "location bar reflects loaded page"
+ );
+
+ await typeAndSubmitAndStop(badURL);
+ is(
+ gURLBar.value,
+ BrowserUIUtils.trimURL(goodURL),
+ "location bar reflects loaded page after stop()"
+ );
+ gBrowser.removeCurrentTab();
+
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
+ is(gURLBar.value, "", "location bar is empty");
+
+ await typeAndSubmitAndStop(badURL);
+ is(
+ gURLBar.value,
+ BrowserUIUtils.trimURL(badURL),
+ "location bar reflects stopped page in an empty tab"
+ );
+ gBrowser.removeCurrentTab();
+});
+
+async function typeAndSubmitAndStop(url) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: url,
+ fireInputEvent: true,
+ });
+
+ let docLoadPromise = BrowserTestUtils.waitForDocLoadAndStopIt(
+ url,
+ gBrowser.selectedBrowser
+ );
+
+ // When the load is stopped, tabbrowser calls gURLBar.setURI and then calls
+ // onStateChange on its progress listeners. So to properly wait until the
+ // urlbar value has been updated, add our own progress listener here.
+ let progressPromise = new Promise(resolve => {
+ let listener = {
+ onStateChange(browser, webProgress, request, stateFlags, status) {
+ if (
+ webProgress.isTopLevel &&
+ stateFlags & Ci.nsIWebProgressListener.STATE_STOP
+ ) {
+ gBrowser.removeTabsProgressListener(listener);
+ resolve();
+ }
+ },
+ };
+ gBrowser.addTabsProgressListener(listener);
+ });
+
+ gURLBar.handleCommand();
+ await Promise.all([docLoadPromise, progressPromise]);
+}
diff --git a/browser/components/urlbar/tests/browser/browser_stopSearchOnSelection.js b/browser/components/urlbar/tests/browser/browser_stopSearchOnSelection.js
new file mode 100644
index 0000000000..0a1ef1b057
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_stopSearchOnSelection.js
@@ -0,0 +1,113 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests that when a search is stopped due to the user selecting a result,
+ * the view doesn't update after that.
+ */
+
+"use strict";
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngineSlow.xml";
+
+// This should match the `timeout` query param used in the suggestions URL in
+// the test engine.
+const TEST_ENGINE_SUGGESTIONS_TIMEOUT = 3000;
+
+// The number of suggestions returned by the test engine.
+const TEST_ENGINE_NUM_EXPECTED_RESULTS = 2;
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", true]],
+ });
+ // Add a test search engine that returns suggestions on a delay.
+ let engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ setAsDefault: true,
+ });
+ await Services.search.moveEngine(engine, 0);
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+add_task(async function mainTest() {
+ // Open a tab that will match the search string below so that we're guaranteed
+ // to have more than one result (the heuristic result) so that we can change
+ // the selected result. We open a tab instead of adding a page in history
+ // because open tabs are kept in a memory SQLite table, so open-tab results
+ // are more likely than history results to be fetched before our slow search
+ // suggestions. This is important when the test runs on slow debug builds on
+ // slow machines.
+ await BrowserTestUtils.withNewTab("http://example.com/", async () => {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ // Do an initial search. There should be 4 results: heuristic, open tab,
+ // and the two suggestions.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "amp",
+ });
+ await TestUtils.waitForCondition(() => {
+ return (
+ UrlbarTestUtils.getResultCount(window) ==
+ 2 + TEST_ENGINE_NUM_EXPECTED_RESULTS
+ );
+ });
+
+ // Type a character to start a new search. The new search should still
+ // match the open tab so that the open-tab result appears again.
+ EventUtils.synthesizeKey("l");
+
+ // There should be 2 results immediately: heuristic and open tab.
+ await TestUtils.waitForCondition(() => {
+ return UrlbarTestUtils.getResultCount(window) == 2;
+ });
+
+ // Before the search completes, change the selected result. Pressing only
+ // the down arrow key ends up selecting the first one-off on Linux debug
+ // builds on the infrastructure for some reason, so arrow back up to
+ // select the heuristic result again. The important thing is to change
+ // the selection. It doesn't matter which result ends up selected.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+
+ // Wait for the new search to complete. It should be canceled due to the
+ // selection change, but it should still complete.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ // To make absolutely sure the suggestions don't appear after the search
+ // completes, wait a bit.
+ await new Promise(r =>
+ setTimeout(r, 1 + TEST_ENGINE_SUGGESTIONS_TIMEOUT)
+ );
+
+ // The heuristic result should reflect the new search, "ampl".
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Should have the correct result type"
+ );
+ Assert.equal(
+ result.searchParams.query,
+ "ampl",
+ "Should have the correct query"
+ );
+
+ // None of the other results should be "ampl" suggestions, i.e., amplfoo
+ // and amplbar should not be in the results.
+ let count = UrlbarTestUtils.getResultCount(window);
+ for (let i = 1; i < count; i++) {
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.ok(
+ result.type != UrlbarUtils.RESULT_TYPE.SEARCH ||
+ !["amplfoo", "amplbar"].includes(result.searchParams.suggestion),
+ "Suggestions should not contain the typed l char"
+ );
+ }
+ });
+ });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_stop_pending.js b/browser/components/urlbar/tests/browser/browser_stop_pending.js
new file mode 100644
index 0000000000..2e7056e972
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_stop_pending.js
@@ -0,0 +1,459 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+"use strict";
+
+const SLOW_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://www.example.com"
+ ) + "slow-page.sjs";
+const SLOW_PAGE2 =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://mochi.test:8888"
+ ) + "slow-page.sjs?faster";
+
+/**
+ * Check that if we:
+ * 1) have a loaded page
+ * 2) load a separate URL
+ * 3) before the URL for step 2 has finished loading, load a third URL
+ * we don't revert to the URL from (1).
+ */
+add_task(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://example.com",
+ true,
+ true
+ );
+
+ let initialValue = gURLBar.untrimmedValue;
+ let expectedURLBarChange = SLOW_PAGE;
+ let sawChange = false;
+ let handler = () => {
+ isnot(
+ gURLBar.untrimmedValue,
+ initialValue,
+ "Should not revert URL bar value!"
+ );
+ if (gURLBar.getAttribute("pageproxystate") == "valid") {
+ sawChange = true;
+ is(
+ gURLBar.untrimmedValue,
+ expectedURLBarChange,
+ "Should set expected URL bar value!"
+ );
+ }
+ };
+
+ let obs = new MutationObserver(handler);
+
+ obs.observe(gURLBar.textbox, { attributes: true });
+ gURLBar.value = SLOW_PAGE;
+ gURLBar.handleCommand();
+
+ // If this ever starts going intermittent, we've broken this.
+ await new Promise(resolve => setTimeout(resolve, 200));
+ expectedURLBarChange = SLOW_PAGE2;
+ let pageLoadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ gURLBar.value = expectedURLBarChange;
+ gURLBar.handleCommand();
+ is(
+ gURLBar.untrimmedValue,
+ expectedURLBarChange,
+ "Should not have changed URL bar value synchronously."
+ );
+ await pageLoadPromise;
+ ok(
+ sawChange,
+ "The URL bar change handler should have been called by the time the page was loaded"
+ );
+ obs.disconnect();
+ obs = null;
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Check that if we:
+ * 1) middle-click a link to a separate page whose server doesn't respond
+ * 2) we switch to that tab and stop the request
+ *
+ * The URL bar continues to contain the URL of the page we wanted to visit.
+ */
+add_task(async function () {
+ let socket = Cc["@mozilla.org/network/server-socket;1"].createInstance(
+ Ci.nsIServerSocket
+ );
+ socket.init(-1, true, -1);
+ const PORT = socket.port;
+ registerCleanupFunction(() => {
+ socket.close();
+ });
+
+ const BASE_PAGE = TEST_BASE_URL + "dummy_page.html";
+ const SLOW_HOST = `https://localhost:${PORT}/`;
+ info("Using URLs: " + SLOW_HOST);
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_PAGE);
+ info("opened tab");
+ await SpecialPowers.spawn(tab.linkedBrowser, [SLOW_HOST], URL => {
+ let link = content.document.createElement("a");
+ link.href = URL;
+ link.textContent = "click me to open a slow page";
+ link.id = "clickme";
+ content.document.body.appendChild(link);
+ });
+ info("added link");
+ let newTabPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabOpen"
+ );
+ // Middle click the link:
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#clickme",
+ { button: 1 },
+ tab.linkedBrowser
+ );
+ // get new tab, switch to it
+ let newTab = (await newTabPromise).target;
+ await BrowserTestUtils.switchTab(gBrowser, newTab);
+ is(gURLBar.untrimmedValue, SLOW_HOST, "Should have slow page in URL bar");
+ let browserStoppedPromise = BrowserTestUtils.browserStopped(
+ newTab.linkedBrowser,
+ null,
+ true
+ );
+ BrowserStop();
+ await browserStoppedPromise;
+
+ is(
+ gURLBar.untrimmedValue,
+ SLOW_HOST,
+ "Should still have slow page in URL bar after stop"
+ );
+ BrowserTestUtils.removeTab(newTab);
+ BrowserTestUtils.removeTab(tab);
+});
+/**
+ * Check that if we:
+ * 1) middle-click a link to a separate page whose server doesn't respond
+ * 2) we alter the URL on that page to some other server that doesn't respond
+ * 3) we stop the request
+ *
+ * The URL bar continues to contain the second URL.
+ */
+add_task(async function () {
+ let socket = Cc["@mozilla.org/network/server-socket;1"].createInstance(
+ Ci.nsIServerSocket
+ );
+ socket.init(-1, true, -1);
+ const PORT1 = socket.port;
+ let socket2 = Cc["@mozilla.org/network/server-socket;1"].createInstance(
+ Ci.nsIServerSocket
+ );
+ socket2.init(-1, true, -1);
+ const PORT2 = socket2.port;
+ registerCleanupFunction(() => {
+ socket.close();
+ socket2.close();
+ });
+
+ const BASE_PAGE = TEST_BASE_URL + "dummy_page.html";
+ const SLOW_HOST1 = `https://localhost:${PORT1}/`;
+ const SLOW_HOST2 = `https://localhost:${PORT2}/`;
+ info("Using URLs: " + SLOW_HOST1 + " and " + SLOW_HOST2);
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_PAGE);
+ info("opened tab");
+ await SpecialPowers.spawn(tab.linkedBrowser, [SLOW_HOST1], URL => {
+ let link = content.document.createElement("a");
+ link.href = URL;
+ link.textContent = "click me to open a slow page";
+ link.id = "clickme";
+ content.document.body.appendChild(link);
+ });
+ info("added link");
+ let newTabPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabOpen"
+ );
+ // Middle click the link:
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#clickme",
+ { button: 1 },
+ tab.linkedBrowser
+ );
+ // get new tab, switch to it
+ let newTab = (await newTabPromise).target;
+ await BrowserTestUtils.switchTab(gBrowser, newTab);
+ is(gURLBar.untrimmedValue, SLOW_HOST1, "Should have slow page in URL bar");
+ let browserStoppedPromise = BrowserTestUtils.browserStopped(
+ newTab.linkedBrowser,
+ null,
+ true
+ );
+ gURLBar.value = SLOW_HOST2;
+ gURLBar.handleCommand();
+ await browserStoppedPromise;
+
+ is(
+ gURLBar.untrimmedValue,
+ SLOW_HOST2,
+ "Should have second slow page in URL bar"
+ );
+ browserStoppedPromise = BrowserTestUtils.browserStopped(
+ newTab.linkedBrowser,
+ null,
+ true
+ );
+ BrowserStop();
+ await browserStoppedPromise;
+
+ is(
+ gURLBar.untrimmedValue,
+ SLOW_HOST2,
+ "Should still have second slow page in URL bar after stop"
+ );
+ BrowserTestUtils.removeTab(newTab);
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * 1) Try to load page 0 and wait for it to finish loading.
+ * 2) Try to load page 1 and wait for it to finish loading.
+ * 3) Try to load SLOW_PAGE, and then before it finishes loading, navigate back.
+ * - We should be taken to page 0.
+ */
+add_task(async function testCorrectUrlBarAfterGoingBackDuringAnotherLoad() {
+ // Load example.org
+ let page0 = "http://example.org/";
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ page0,
+ true,
+ true
+ );
+
+ // Load example.com in the same browser
+ let page1 = "http://example.com/";
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, page1);
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, page1);
+ await loaded;
+
+ let initialValue = gURLBar.untrimmedValue;
+ let expectedURLBarChange = SLOW_PAGE;
+ let sawChange = false;
+ let goneBack = false;
+ let handler = () => {
+ if (!goneBack) {
+ isnot(
+ gURLBar.untrimmedValue,
+ initialValue,
+ `Should not revert URL bar value to ${initialValue}`
+ );
+ }
+
+ if (gURLBar.getAttribute("pageproxystate") == "valid") {
+ sawChange = true;
+ is(
+ gURLBar.untrimmedValue,
+ expectedURLBarChange,
+ `Should set expected URL bar value - ${expectedURLBarChange}`
+ );
+ }
+ };
+
+ let obs = new MutationObserver(handler);
+
+ obs.observe(gURLBar.textbox, { attributes: true });
+ // Set the value of url bar to SLOW_PAGE
+ gURLBar.value = SLOW_PAGE;
+ gURLBar.handleCommand();
+
+ // Copied from the first test case:
+ // If this ever starts going intermittent, we've broken this.
+ await new Promise(resolve => setTimeout(resolve, 200));
+
+ expectedURLBarChange = page0;
+ let pageLoadPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ page0
+ );
+
+ // Wait until we can go back
+ await TestUtils.waitForCondition(() => tab.linkedBrowser.canGoBack);
+ ok(tab.linkedBrowser.canGoBack, "can go back");
+
+ // Navigate back from SLOW_PAGE. We should be taken to page 0 now.
+ tab.linkedBrowser.goBack();
+ goneBack = true;
+ is(
+ gURLBar.untrimmedValue,
+ SLOW_PAGE,
+ "Should not have changed URL bar value synchronously."
+ );
+ // Wait until page 0 have finished loading.
+ await pageLoadPromise;
+ is(
+ gURLBar.untrimmedValue,
+ page0,
+ "Should not have changed URL bar value synchronously."
+ );
+ ok(
+ sawChange,
+ "The URL bar change handler should have been called by the time the page was loaded"
+ );
+ obs.disconnect();
+ obs = null;
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * 1) Try to load page 1 and wait for it to finish loading.
+ * 2) Start loading SLOW_PAGE (it won't finish loading)
+ * 3) Reload the page. We should have loaded page 1 now.
+ */
+add_task(async function testCorrectUrlBarAfterReloadingDuringSlowPageLoad() {
+ // Load page 1 - example.com
+ let page1 = "http://example.com/";
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ page1,
+ true,
+ true
+ );
+
+ let initialValue = gURLBar.untrimmedValue;
+ let expectedURLBarChange = SLOW_PAGE;
+ let sawChange = false;
+ let hasReloaded = false;
+ let handler = () => {
+ if (!hasReloaded) {
+ isnot(
+ gURLBar.untrimmedValue,
+ initialValue,
+ "Should not revert URL bar value!"
+ );
+ }
+ if (gURLBar.getAttribute("pageproxystate") == "valid") {
+ sawChange = true;
+ is(
+ gURLBar.untrimmedValue,
+ expectedURLBarChange,
+ "Should set expected URL bar value!"
+ );
+ }
+ };
+
+ let obs = new MutationObserver(handler);
+
+ obs.observe(gURLBar.textbox, { attributes: true });
+ // Start loading SLOW_PAGE
+ gURLBar.value = SLOW_PAGE;
+ gURLBar.handleCommand();
+
+ // Copied from the first test: If this ever starts going intermittent,
+ // we've broken this.
+ await new Promise(resolve => setTimeout(resolve, 200));
+
+ expectedURLBarChange = page1;
+ let pageLoadPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ page1
+ );
+ // Reload the page
+ tab.linkedBrowser.reload();
+ hasReloaded = true;
+ is(
+ gURLBar.untrimmedValue,
+ SLOW_PAGE,
+ "Should not have changed URL bar value synchronously."
+ );
+ // Wait for page1 to be loaded due to a reload while the slow page was still loading
+ await pageLoadPromise;
+ ok(
+ sawChange,
+ "The URL bar change handler should have been called by the time the page was loaded"
+ );
+ obs.disconnect();
+ obs = null;
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * 1) Try to load example.com and wait for it to finish loading.
+ * 2) Start loading SLOW_PAGE and then stop the load before the load completes
+ * 3) Check that example.com has been loaded as a result of stopping SLOW_PAGE
+ * load.
+ */
+add_task(async function testCorrectUrlBarAfterStoppingTheLoad() {
+ // Load page 1
+ let page1 = "http://example.com/";
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ page1,
+ true,
+ true
+ );
+
+ let initialValue = gURLBar.untrimmedValue;
+ let expectedURLBarChange = SLOW_PAGE;
+ let sawChange = false;
+ let hasStopped = false;
+ let handler = () => {
+ if (!hasStopped) {
+ isnot(
+ gURLBar.untrimmedValue,
+ initialValue,
+ "Should not revert URL bar value!"
+ );
+ }
+ if (gURLBar.getAttribute("pageproxystate") == "valid") {
+ sawChange = true;
+ is(
+ gURLBar.untrimmedValue,
+ expectedURLBarChange,
+ "Should set expected URL bar value!"
+ );
+ }
+ };
+
+ let obs = new MutationObserver(handler);
+
+ obs.observe(gURLBar.textbox, { attributes: true });
+ // Start loading SLOW_PAGE
+ gURLBar.value = SLOW_PAGE;
+ gURLBar.handleCommand();
+
+ // Copied from the first test case:
+ // If this ever starts going intermittent, we've broken this.
+ await new Promise(resolve => setTimeout(resolve, 200));
+
+ // We expect page 1 to be loaded after the SLOW_PAGE load is stopped.
+ expectedURLBarChange = page1;
+ let pageLoadPromise = BrowserTestUtils.browserStopped(
+ tab.linkedBrowser,
+ SLOW_PAGE,
+ true
+ );
+ // Stop the SLOW_PAGE load
+ tab.linkedBrowser.stop();
+ hasStopped = true;
+ is(
+ gURLBar.untrimmedValue,
+ SLOW_PAGE,
+ "Should not have changed URL bar value synchronously."
+ );
+ // Wait for SLOW_PAGE load to stop
+ await pageLoadPromise;
+
+ ok(
+ sawChange,
+ "The URL bar change handler should have been called by the time the page was loaded"
+ );
+ obs.disconnect();
+ obs = null;
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_strip_on_share.js b/browser/components/urlbar/tests/browser/browser_strip_on_share.js
new file mode 100644
index 0000000000..39a3e23aa7
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_strip_on_share.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let listService;
+
+// Tests for the strip on share functionality of the urlbar.
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.query_stripping.strip_list", "stripParam"],
+ ["privacy.query_stripping.enabled", false],
+ ],
+ });
+
+ // Get the list service so we can wait for it to be fully initialized before running tests.
+ listService = Cc["@mozilla.org/query-stripping-list-service;1"].getService(
+ Ci.nsIURLQueryStrippingListService
+ );
+
+ await listService.testWaitForInit();
+});
+
+// Menu item should be visible, the whole url is copied without a selection, url should be stripped.
+add_task(async function testQueryParamIsStripped() {
+ await testMenuItemEnabled(false);
+});
+
+// Menu item should be visible, selecting the whole url, url should be stripped.
+add_task(async function testQueryParamIsStrippedSelectURL() {
+ await testMenuItemEnabled(true);
+});
+
+// We cannot strip anything, menu item should be hidden
+add_task(async function testUnknownQueryParam() {
+ await testMenuItemDisabled(
+ "https://www.example.com/?noStripParam=1234",
+ true,
+ false
+ );
+});
+
+// Selection is not a valid URI, menu item should be hidden
+add_task(async function testInvalidURI() {
+ await testMenuItemDisabled(
+ "https://www.example.com/?stripParam=1234",
+ true,
+ true
+ );
+});
+
+// Pref is not enabled, menu item should be hidden
+add_task(async function testPrefDisabled() {
+ await testMenuItemDisabled(
+ "https://www.example.com/?stripParam=1234",
+ false,
+ false
+ );
+});
+
+/**
+ * Opens a new tab, opens the ulr bar context menu and checks that the strip-on-share menu item is not visible
+ *
+ * @param {string} url - The url to be loaded
+ * @param {boolean} prefEnabled - Whether privacy.query_stripping.strip_on_share.enabled should be enabled for the test
+ * @param {boolean} selection - True: The whole url will be selected, false: Only part of the url will be selected
+ */
+async function testMenuItemDisabled(url, prefEnabled, selection) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.strip_on_share.enabled", prefEnabled]],
+ });
+ await BrowserTestUtils.withNewTab(url, async function (browser) {
+ gURLBar.focus();
+ if (selection) {
+ //select only part of the url
+ gURLBar.selectionStart = url.indexOf("example");
+ gURLBar.selectionEnd = url.indexOf("4");
+ }
+ let menuitem = await promiseContextualMenuitem("strip-on-share");
+ Assert.ok(
+ !BrowserTestUtils.is_visible(menuitem),
+ "Menu item is not visible"
+ );
+ let hidePromise = BrowserTestUtils.waitForEvent(
+ menuitem.parentElement,
+ "popuphidden"
+ );
+ menuitem.parentElement.hidePopup();
+ await hidePromise;
+ });
+}
+
+/**
+ * Opens a new tab, opens the url bar context menu and checks that the strip-on-share menu item is visible.
+ * Checks that the stripped version of the url is copied to the clipboard.
+ *
+ * @param {boolean} selectWholeUrl - Whether the whole url should be explicitely selected
+ */
+async function testMenuItemEnabled(selectWholeUrl) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.query_stripping.strip_on_share.enabled", true]],
+ });
+ let validUrl = "https://www.example.com/?stripParam=1234";
+ let strippedUrl = "https://www.example.com/";
+ await BrowserTestUtils.withNewTab(validUrl, async function (browser) {
+ gURLBar.focus();
+ if (selectWholeUrl) {
+ //select the whole url
+ gURLBar.select();
+ }
+ let menuitem = await promiseContextualMenuitem("strip-on-share");
+ Assert.ok(BrowserTestUtils.is_visible(menuitem), "Menu item is visible");
+ let hidePromise = BrowserTestUtils.waitForEvent(
+ menuitem.parentElement,
+ "popuphidden"
+ );
+ // Make sure the clean copy of the link will be copied to the clipboard
+ await SimpleTest.promiseClipboardChange(strippedUrl, () => {
+ menuitem.closest("menupopup").activateItem(menuitem);
+ });
+ await hidePromise;
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_suggestedIndex.js b/browser/components/urlbar/tests/browser/browser_suggestedIndex.js
new file mode 100644
index 0000000000..563202036a
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_suggestedIndex.js
@@ -0,0 +1,120 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that results with a suggestedIndex property end up in the expected
+// position.
+
+add_task(async function suggestedIndex() {
+ let result1 = new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: "http://mozilla.org/1" }
+ );
+ result1.suggestedIndex = 2;
+ let result2 = new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: "http://mozilla.org/2" }
+ );
+ result2.suggestedIndex = 6;
+
+ let provider = new UrlbarTestUtils.TestProvider({
+ results: [result1, result2],
+ });
+ UrlbarProvidersManager.registerProvider(provider);
+ async function clean() {
+ UrlbarProvidersManager.unregisterProvider(provider);
+ await PlacesUtils.history.clear();
+ }
+ registerCleanupFunction(clean);
+
+ let urls = [];
+ let maxResults = UrlbarPrefs.get("maxRichResults");
+ // Add more results, so that the sum of these results plus the above ones,
+ // will be greater than maxResults.
+ for (let i = 0; i < maxResults; ++i) {
+ urls.push("http://example.com/foo" + i);
+ }
+ await PlacesTestUtils.addVisits(urls);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ maxResults,
+ `There should be ${maxResults} results in the view.`
+ );
+
+ urls.reverse();
+ urls.unshift(
+ (await Services.search.getDefault()).getSubmission("foo").uri.spec
+ );
+ urls.splice(result1.suggestedIndex, 0, result1.payload.url);
+ urls.splice(result2.suggestedIndex, 0, result2.payload.url);
+ urls = urls.slice(0, maxResults);
+
+ let expected = [];
+ for (let i = 0; i < maxResults; ++i) {
+ let url = (await UrlbarTestUtils.getDetailsOfResultAt(window, i)).url;
+ expected.push(url);
+ }
+ // Check all the results.
+ Assert.deepEqual(expected, urls);
+
+ await clean();
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+add_task(async function suggestedIndex_append() {
+ // When suggestedIndex is greater than the number of results the result is
+ // appended.
+ let result = new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: "http://mozilla.org/append/" }
+ );
+ result.suggestedIndex = 4;
+
+ let provider = new UrlbarTestUtils.TestProvider({ results: [result] });
+ UrlbarProvidersManager.registerProvider(provider);
+ async function clean() {
+ UrlbarProvidersManager.unregisterProvider(provider);
+ await PlacesUtils.history.clear();
+ }
+ registerCleanupFunction(clean);
+
+ await PlacesTestUtils.addVisits("http://example.com/bar");
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "bar",
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 3,
+ `There should be 3 results in the view.`
+ );
+
+ let urls = [
+ (await Services.search.getDefault()).getSubmission("bar").uri.spec,
+ "http://example.com/bar",
+ "http://mozilla.org/append/",
+ ];
+
+ let expected = [];
+ for (let i = 0; i < 3; ++i) {
+ let url = (await UrlbarTestUtils.getDetailsOfResultAt(window, i)).url;
+ expected.push(url);
+ }
+ // Check all the results.
+ Assert.deepEqual(expected, urls);
+
+ await clean();
+ await UrlbarTestUtils.promisePopupClose(window);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_suppressFocusBorder.js b/browser/components/urlbar/tests/browser/browser_suppressFocusBorder.js
new file mode 100644
index 0000000000..c8d84e5c4c
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_suppressFocusBorder.js
@@ -0,0 +1,391 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the suppress-focus-border attribute is applied to the Urlbar
+ * correctly. Its purpose is to hide the focus border after the panel is closed.
+ * It also ensures we don't flash the border at the user after they click the
+ * Urlbar but before we decide we're opening the view.
+ */
+
+let TEST_RESULT = new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.HISTORY,
+ { url: "http://mozilla.org/" }
+);
+
+/**
+ * A test provider that awaits a promise before returning results.
+ */
+class AwaitPromiseProvider extends UrlbarTestUtils.TestProvider {
+ /**
+ * @param {object} args
+ * The constructor arguments for UrlbarTestUtils.TestProvider.
+ * @param {Promise} promise
+ * The promise that will be awaited before returning results.
+ */
+ constructor(args, promise) {
+ super(args);
+ this._promise = promise;
+ }
+
+ async startQuery(context, add) {
+ await this._promise;
+ for (let result of this._results) {
+ add(this, result);
+ }
+ }
+}
+
+add_setup(async function () {
+ await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
+
+ registerCleanupFunction(function () {
+ SpecialPowers.clipboardCopyString("");
+ });
+});
+
+add_task(async function afterMousedown_topSites() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ win.gURLBar.blur();
+
+ await withAwaitProvider(
+ { results: [TEST_RESULT], priority: Infinity },
+ getSuppressFocusPromise(win),
+ async () => {
+ Assert.ok(
+ !win.gURLBar.hasAttribute("suppress-focus-border"),
+ "Sanity check: the Urlbar does not have the supress-focus-border attribute."
+ );
+
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ if (win.gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {}, win);
+ });
+
+ let result = await UrlbarTestUtils.waitForAutocompleteResultAt(win, 0);
+ Assert.ok(
+ result,
+ "The provider returned a result after waiting for the suppress-focus-border attribute."
+ );
+
+ await UrlbarTestUtils.promisePopupClose(win);
+ Assert.ok(
+ !gURLBar.hasAttribute("suppress-focus-border"),
+ "The Urlbar no longer has the supress-focus-border attribute after close."
+ );
+ }
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function openLocation_topSites() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ await withAwaitProvider(
+ { results: [TEST_RESULT], priority: Infinity },
+ getSuppressFocusPromise(win),
+ async () => {
+ Assert.ok(
+ !win.gURLBar.hasAttribute("suppress-focus-border"),
+ "Sanity check: the Urlbar does not have the supress-focus-border attribute."
+ );
+
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ EventUtils.synthesizeKey("l", { accelKey: true }, win);
+ });
+
+ let result = await UrlbarTestUtils.waitForAutocompleteResultAt(win, 0);
+ Assert.ok(
+ result,
+ "The provider returned a result after waiting for the suppress-focus-border attribute."
+ );
+
+ await UrlbarTestUtils.promisePopupClose(win);
+ Assert.ok(
+ !win.gURLBar.hasAttribute("suppress-focus-border"),
+ "The Urlbar no longer has the supress-focus-border attribute after close."
+ );
+ }
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// Tests that the address bar loses the suppress-focus-border attribute if no
+// results are returned by a query. This simulates the user disabling Top Sites
+// then clicking the address bar.
+add_task(async function afterMousedown_noTopSites() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ await withAwaitProvider(
+ // Note that the provider returns no results.
+ { results: [], priority: Infinity },
+ getSuppressFocusPromise(win),
+ async () => {
+ Assert.ok(
+ !win.gURLBar.hasAttribute("suppress-focus-border"),
+ "Sanity check: the Urlbar does not have the supress-focus-border attribute."
+ );
+
+ EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {}, win);
+ // Because the panel opening may not be immediate, we must wait a bit.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 500));
+ Assert.ok(!UrlbarTestUtils.isPopupOpen(win), "The popup is not open.");
+
+ Assert.ok(
+ !win.gURLBar.hasAttribute("suppress-focus-border"),
+ "The Urlbar no longer has the supress-focus-border attribute."
+ );
+ }
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// Tests that we show the focus border when new tabs are opened.
+add_task(async function newTab() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ // Tabs opened with withNewTab don't focus the Urlbar, so we have to open one
+ // manually.
+ let tab = await openAboutNewTab(win);
+ await BrowserTestUtils.waitForCondition(
+ () => win.gURLBar.hasAttribute("focused"),
+ "Waiting for the Urlbar to become focused."
+ );
+ Assert.ok(
+ !win.gURLBar.hasAttribute(
+ "suppress-focus-border",
+ "The Urlbar does not have the suppress-focus-border attribute."
+ )
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// Tests that we show the focus border when a new tab is opened and the address
+// bar panel is already open.
+add_task(async function newTab_alreadyOpen() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ await withAwaitProvider(
+ { results: [TEST_RESULT], priority: Infinity },
+ getSuppressFocusPromise(win),
+ async () => {
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ EventUtils.synthesizeKey("l", { accelKey: true }, win);
+ });
+
+ let tab = await openAboutNewTab(win);
+ await BrowserTestUtils.waitForCondition(
+ () => !UrlbarTestUtils.isPopupOpen(win),
+ "Waiting for the Urlbar panel to close."
+ );
+ Assert.ok(
+ !win.gURLBar.hasAttribute(
+ "suppress-focus-border",
+ "The Urlbar does not have the suppress-focus-border attribute."
+ )
+ );
+ BrowserTestUtils.removeTab(tab);
+ }
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function searchTip() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ info("Set a pref to show a search tip button.");
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.searchTips.test.ignoreShowLimits", true]],
+ });
+
+ info("Open new tab.");
+ const tab = await openAboutNewTab(win);
+
+ info("Click the tip button.");
+ const result = await UrlbarTestUtils.getDetailsOfResultAt(win, 0);
+ const button = result.element.row._buttons.get("0");
+ await UrlbarTestUtils.promisePopupClose(win, () => {
+ EventUtils.synthesizeMouseAtCenter(button, {}, win);
+ });
+
+ Assert.ok(
+ !win.gURLBar.hasAttribute(
+ "suppress-focus-border",
+ "The Urlbar does not have the suppress-focus-border attribute."
+ )
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function interactionOnNewTab() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ info("Open about:newtab in new tab");
+ const tab = await openAboutNewTab(win);
+ await BrowserTestUtils.waitForCondition(
+ () => win.gBrowser.selectedTab === tab
+ );
+
+ await testInteractionsOnAboutNewTab(win);
+
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function interactionOnNewTabInPrivateWindow() {
+ const win = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ waitForTabURL: "about:privatebrowsing",
+ });
+ await testInteractionsOnAboutNewTab(win);
+ await BrowserTestUtils.closeWindow(win);
+ await SimpleTest.promiseFocus(window);
+});
+
+add_task(async function clickOnEdgeOfURLBar() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ win.gURLBar.blur();
+
+ Assert.ok(
+ !win.gURLBar.hasAttribute("suppress-focus-border"),
+ "URLBar does not have suppress-focus-border attribute"
+ );
+
+ const onHiddenFocusRemoved = BrowserTestUtils.waitForCondition(
+ () => !win.gURLBar._hideFocus
+ );
+
+ const container = win.document.getElementById("urlbar-input-container");
+ container.click();
+
+ await onHiddenFocusRemoved;
+ Assert.ok(
+ win.gURLBar.hasAttribute("suppress-focus-border"),
+ "suppress-focus-border is set from the beginning"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(win.window);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+async function testInteractionsOnAboutNewTab(win) {
+ info("Test for clicking on URLBar while showing about:newtab");
+ await testInteractionFeature(() => {
+ info("Click on URLBar");
+ EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {}, win);
+ }, win);
+
+ info("Test for typing on .fake-editable while showing about:newtab");
+ await testInteractionFeature(() => {
+ info("Type a character on .fake-editable");
+ EventUtils.synthesizeKey("v", {}, win);
+ }, win);
+ Assert.equal(win.gURLBar.value, "v", "URLBar value is correct");
+
+ info("Test for typing on .fake-editable while showing about:newtab");
+ await testInteractionFeature(() => {
+ info("Paste some words on .fake-editable");
+ SpecialPowers.clipboardCopyString("paste test");
+ win.document.commandDispatcher
+ .getControllerForCommand("cmd_paste")
+ .doCommand("cmd_paste");
+ SpecialPowers.clipboardCopyString("");
+ }, win);
+ Assert.equal(win.gURLBar.value, "paste test", "URLBar value is correct");
+}
+
+async function testInteractionFeature(interaction, win) {
+ info("Focus on URLBar");
+ win.gURLBar.value = "";
+ win.gURLBar.focus();
+ Assert.ok(
+ !win.gURLBar.hasAttribute("suppress-focus-border"),
+ "URLBar does not have suppress-focus-border attribute"
+ );
+
+ info("Click on search-handoff-button in newtab page");
+ await ContentTask.spawn(win.gBrowser.selectedBrowser, null, async () => {
+ await ContentTaskUtils.waitForCondition(() =>
+ content.document.querySelector(".search-handoff-button")
+ );
+ content.document.querySelector(".search-handoff-button").click();
+ });
+
+ await BrowserTestUtils.waitForCondition(
+ () => win.gURLBar._hideFocus,
+ "Wait until _hideFocus will be true"
+ );
+
+ const onHiddenFocusRemoved = BrowserTestUtils.waitForCondition(
+ () => !win.gURLBar._hideFocus
+ );
+
+ await interaction();
+
+ await onHiddenFocusRemoved;
+ Assert.ok(
+ win.gURLBar.hasAttribute("suppress-focus-border"),
+ "suppress-focus-border is set from the beginning"
+ );
+
+ const result = await UrlbarTestUtils.waitForAutocompleteResultAt(win, 0);
+ Assert.ok(result, "The provider returned a result");
+ await UrlbarTestUtils.promisePopupClose(win);
+}
+
+function getSuppressFocusPromise(win = window) {
+ return new Promise(resolve => {
+ let observer = new MutationObserver(() => {
+ if (
+ win.gURLBar.hasAttribute("suppress-focus-border") &&
+ !UrlbarTestUtils.isPopupOpen(win)
+ ) {
+ resolve();
+ observer.disconnect();
+ }
+ });
+ observer.observe(win.gURLBar.textbox, {
+ attributes: true,
+ attributeFilter: ["suppress-focus-border"],
+ });
+ });
+}
+
+async function withAwaitProvider(args, promise, callback) {
+ let provider = new AwaitPromiseProvider(args, promise);
+ UrlbarProvidersManager.registerProvider(provider);
+ try {
+ await callback();
+ } catch (ex) {
+ console.error(ex);
+ } finally {
+ UrlbarProvidersManager.unregisterProvider(provider);
+ }
+}
+
+async function openAboutNewTab(win = window) {
+ // We have to listen for the new tab using this brute force method.
+ // about:newtab is preloaded in the background. When about:newtab is opened,
+ // the cached version is shown. Since the page is already loaded,
+ // waitForNewTab does not detect it. It also doesn't fire the TabOpen event.
+ const tabCount = win.gBrowser.tabs.length;
+ EventUtils.synthesizeKey("t", { accelKey: true }, win);
+ await TestUtils.waitForCondition(
+ () => win.gBrowser.tabs.length === tabCount + 1,
+ "Waiting for background about:newtab to open."
+ );
+ return win.gBrowser.tabs[win.gBrowser.tabs.length - 1];
+}
diff --git a/browser/components/urlbar/tests/browser/browser_switchTab_closesUrlbarPopup.js b/browser/components/urlbar/tests/browser/browser_switchTab_closesUrlbarPopup.js
new file mode 100644
index 0000000000..a9b0eb7b1a
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_switchTab_closesUrlbarPopup.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Checks that switching tabs closes the urlbar popup.
+ */
+
+"use strict";
+
+add_task(async function () {
+ let tab1 = BrowserTestUtils.addTab(gBrowser);
+ let tab2 = BrowserTestUtils.addTab(gBrowser);
+
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+ });
+
+ // Add a couple of dummy entries to ensure the history popup will open.
+ await PlacesTestUtils.addVisits([
+ { uri: makeURI("http://example.com/foo") },
+ { uri: makeURI("http://example.com/foo/bar") },
+ ]);
+
+ // When urlbar in a new tab is focused, and a tab switch occurs,
+ // the urlbar popup should be closed
+ await BrowserTestUtils.switchTab(gBrowser, tab2);
+ gURLBar.focus(); // focus the urlbar in the tab we will switch to
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+ // Now open the popup.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ // Check that the popup closes when we switch tab.
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ return BrowserTestUtils.switchTab(gBrowser, tab2);
+ });
+ Assert.ok(true, "Popup was successfully closed");
+});
diff --git a/browser/components/urlbar/tests/browser/browser_switchTab_currentTab.js b/browser/components/urlbar/tests/browser/browser_switchTab_currentTab.js
new file mode 100644
index 0000000000..eccee800e3
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_switchTab_currentTab.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test ensures that switch to tab still works when the URI contains an
+ * encoded part.
+ */
+
+"use strict";
+
+add_task(async function test_switchTab_currentTab() {
+ registerCleanupFunction(PlacesUtils.history.clear);
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:robots#1" },
+ async () => {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:robots#2" },
+ async () => {
+ let context = await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "robot",
+ });
+ Assert.ok(
+ context.results.some(
+ result =>
+ result.type == UrlbarUtils.RESULT_TYPE.TAB_SWITCH &&
+ result.payload.url == "about:robots#1"
+ )
+ );
+ Assert.ok(
+ !context.results.some(
+ result =>
+ result.type == UrlbarUtils.RESULT_TYPE.TAB_SWITCH &&
+ result.payload.url == "about:robots#2"
+ )
+ );
+ }
+ );
+ }
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_switchTab_decodeuri.js b/browser/components/urlbar/tests/browser/browser_switchTab_decodeuri.js
new file mode 100644
index 0000000000..fe23eceaf9
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_switchTab_decodeuri.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test ensures that switch to tab still works when the URI contains an
+ * encoded part.
+ */
+
+"use strict";
+
+const TEST_URL = `${TEST_BASE_URL}dummy_page.html#test%7C1`;
+
+add_task(async function test_switchtab_decodeuri() {
+ info("Opening first tab");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Opening and selecting second tab");
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+
+ info("Wait for autocomplete");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "dummy_page",
+ });
+
+ info("Select autocomplete popup entry");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ UrlbarTestUtils.getSelectedRowIndex(window)
+ );
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.TAB_SWITCH);
+
+ info("switch-to-tab");
+ let tabSelectPromise = BrowserTestUtils.waitForEvent(
+ window,
+ "TabSelect",
+ false
+ );
+ EventUtils.synthesizeKey("KEY_Enter");
+ await tabSelectPromise;
+
+ Assert.equal(
+ gBrowser.selectedTab,
+ tab,
+ "Should have switched to the right tab"
+ );
+
+ gBrowser.removeCurrentTab();
+ await PlacesUtils.history.clear();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_switchTab_inputHistory.js b/browser/components/urlbar/tests/browser/browser_switchTab_inputHistory.js
new file mode 100644
index 0000000000..0da3161d0e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_switchTab_inputHistory.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests ensures that the urlbar adaptive behavior updates
+ * when using switch to tab in the address bar dropdown.
+ */
+
+"use strict";
+
+add_setup(async function () {
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+add_task(async function test_adaptive_with_search_term_and_switch_tab() {
+ await PlacesUtils.history.clear();
+ let urls = [
+ "https://example.com/",
+ "https://example.com/#cat",
+ "https://example.com/#cake",
+ "https://example.com/#car",
+ ];
+
+ info(`Load tabs in same order as urls`);
+ let tabs = [];
+ for (let url of urls) {
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url, false, true);
+ gBrowser.loadTabs([url], {
+ inBackground: true,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+
+ let tab = await tabPromise;
+ tabs.push(tab);
+ }
+
+ info(`Switch to tab 0`);
+ await BrowserTestUtils.switchTab(gBrowser, tabs[0]);
+
+ info("Wait for autocomplete");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ca",
+ });
+
+ let result1 = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.notEqual(result1.url, urls[1], `${urls[1]} url should not be first`);
+
+ info(`Scroll down to select the ${urls[1]} entry using keyboard`);
+ let result2 = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ UrlbarTestUtils.getSelectedRowIndex(window)
+ );
+
+ while (result2.url != urls[1]) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ result2 = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ UrlbarTestUtils.getSelectedRowIndex(window)
+ );
+ }
+
+ Assert.equal(
+ result2.type,
+ UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
+ "Selected entry should be tab switch"
+ );
+ Assert.equal(result2.url, urls[1]);
+
+ info("Visiting tab 1");
+ EventUtils.synthesizeKey("KEY_Enter");
+ Assert.equal(gBrowser.selectedTab, tabs[1], "Should have switched to tab 1");
+
+ info("Switch back to tab 0");
+ await BrowserTestUtils.switchTab(gBrowser, tabs[0]);
+
+ info("Wait for autocomplete");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "ca",
+ });
+
+ let result3 = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result3.url, urls[1], `${urls[1]} url should be first`);
+
+ for (let tab of tabs) {
+ BrowserTestUtils.removeTab(tab);
+ }
+});
diff --git a/browser/components/urlbar/tests/browser/browser_switchTab_override.js b/browser/components/urlbar/tests/browser/browser_switchTab_override.js
new file mode 100644
index 0000000000..dabce43612
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_switchTab_override.js
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This test ensures that overriding switch-to-tab correctly loads the page
+ * rather than switching to it.
+ */
+
+"use strict";
+
+const TEST_URL = `${TEST_BASE_URL}dummy_page.html`;
+
+add_task(async function test_switchtab_override() {
+ info("Opening first tab");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Opening and selecting second tab");
+ let secondTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ registerCleanupFunction(() => {
+ try {
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(secondTab);
+ } catch (ex) {
+ /* tabs may have already been closed in case of failure */
+ }
+ });
+
+ info("Wait for autocomplete");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "dummy_page",
+ });
+
+ info("Select second autocomplete popup entry");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ UrlbarTestUtils.getSelectedRowIndex(window)
+ );
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.TAB_SWITCH);
+
+ // Check to see if the switchtab label is visible and
+ // all other labels are hidden
+ const allLabels = document.getElementById("urlbar-label-box").children;
+ for (let label of allLabels) {
+ if (label.id == "urlbar-label-switchtab") {
+ Assert.ok(BrowserTestUtils.is_visible(label));
+ } else {
+ Assert.ok(BrowserTestUtils.is_hidden(label));
+ }
+ }
+
+ info("Override switch-to-tab");
+ let deferred = PromiseUtils.defer();
+ // In case of failure this would switch tab.
+ let onTabSelect = event => {
+ deferred.reject(new Error("Should have overridden switch to tab"));
+ };
+ gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect);
+ registerCleanupFunction(() => {
+ gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect);
+ });
+ // Otherwise it would load the page.
+ BrowserTestUtils.browserLoaded(secondTab.linkedBrowser).then(
+ deferred.resolve
+ );
+
+ EventUtils.synthesizeKey("KEY_Shift", { type: "keydown" });
+
+ // Checks that all labels are hidden when Shift is held down on the SwitchToTab result
+ for (let label of allLabels) {
+ Assert.ok(BrowserTestUtils.is_hidden(label));
+ }
+
+ registerCleanupFunction(() => {
+ // Avoid confusing next tests by leaving a pending keydown.
+ EventUtils.synthesizeKey("KEY_Shift", { type: "keyup" });
+ });
+
+ let attribute = "actionoverride";
+ Assert.ok(
+ gURLBar.view.panel.hasAttribute(attribute),
+ "We should be overriding"
+ );
+
+ EventUtils.synthesizeKey("KEY_Enter");
+ info(`gURLBar.value = ${gURLBar.value}`);
+ await deferred.promise;
+
+ // Blurring the urlbar should have cleared the override.
+ Assert.ok(
+ !gURLBar.view.panel.hasAttribute(attribute),
+ "We should not be overriding anymore"
+ );
+
+ await PlacesUtils.history.clear();
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(secondTab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_switchToTabHavingURI_aOpenParams.js b/browser/components/urlbar/tests/browser/browser_switchToTabHavingURI_aOpenParams.js
new file mode 100644
index 0000000000..1a0d2eef70
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_switchToTabHavingURI_aOpenParams.js
@@ -0,0 +1,217 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function test_ignoreFragment() {
+ let tabRefAboutHome = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:home#1"
+ );
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+ let numTabsAtStart = gBrowser.tabs.length;
+
+ switchTab("about:home#1", true);
+ switchTab("about:mozilla", true);
+
+ let hashChangePromise = ContentTask.spawn(
+ tabRefAboutHome.linkedBrowser,
+ [],
+ async function () {
+ await ContentTaskUtils.waitForEvent(this, "hashchange", true);
+ }
+ );
+ switchTab("about:home#2", true, {
+ ignoreFragment: "whenComparingAndReplace",
+ });
+ is(
+ tabRefAboutHome,
+ gBrowser.selectedTab,
+ "The same about:home tab should be switched to"
+ );
+ await hashChangePromise;
+ is(gBrowser.currentURI.ref, "2", "The ref should be updated to the new ref");
+ switchTab("about:mozilla", true);
+ switchTab("about:home#3", true, { ignoreFragment: "whenComparing" });
+ is(
+ tabRefAboutHome,
+ gBrowser.selectedTab,
+ "The same about:home tab should be switched to"
+ );
+ is(
+ gBrowser.currentURI.ref,
+ "2",
+ "The ref should be unchanged since the fragment is only ignored when comparing"
+ );
+ switchTab("about:mozilla", true);
+ switchTab("about:home#1", false);
+ isnot(
+ tabRefAboutHome,
+ gBrowser.selectedTab,
+ "Selected tab should not be initial about:blank tab"
+ );
+ is(
+ gBrowser.tabs.length,
+ numTabsAtStart + 1,
+ "Should have one new tab opened"
+ );
+ switchTab("about:mozilla", true);
+ switchTab("about:home", true, { ignoreFragment: "whenComparingAndReplace" });
+ await BrowserTestUtils.waitForCondition(function () {
+ return tabRefAboutHome.linkedBrowser.currentURI.spec == "about:home";
+ });
+ is(
+ tabRefAboutHome.linkedBrowser.currentURI.spec,
+ "about:home",
+ "about:home shouldn't have hash"
+ );
+ switchTab("about:about", false, {
+ ignoreFragment: "whenComparingAndReplace",
+ });
+ cleanupTestTabs();
+});
+
+add_task(async function test_ignoreQueryString() {
+ let tabRefAboutHome = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:home?hello=firefox"
+ );
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ switchTab("about:home?hello=firefox", true);
+ switchTab("about:home?hello=firefoxos", false);
+ // Remove the last opened tab to test ignoreQueryString option.
+ gBrowser.removeCurrentTab();
+ switchTab("about:home?hello=firefoxos", true, { ignoreQueryString: true });
+ is(
+ tabRefAboutHome,
+ gBrowser.selectedTab,
+ "Selected tab should be the initial about:home tab"
+ );
+ is(
+ gBrowser.currentURI.spec,
+ "about:home?hello=firefox",
+ "The spec should NOT be updated to the new query string"
+ );
+ cleanupTestTabs();
+});
+
+add_task(async function test_replaceQueryString() {
+ let tabRefAboutHome = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:home?hello=firefox"
+ );
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ switchTab("about:home", false);
+ switchTab("about:home?hello=firefox", true);
+ switchTab("about:home?hello=firefoxos", false);
+ // Remove the last opened tab to test replaceQueryString option.
+ gBrowser.removeCurrentTab();
+ switchTab("about:home?hello=firefoxos", true, { replaceQueryString: true });
+ is(
+ tabRefAboutHome,
+ gBrowser.selectedTab,
+ "Selected tab should be the initial about:home tab"
+ );
+ // Wait for the tab to load the new URI spec.
+ await BrowserTestUtils.browserLoaded(tabRefAboutHome.linkedBrowser);
+ is(
+ gBrowser.currentURI.spec,
+ "about:home?hello=firefoxos",
+ "The spec should be updated to the new spec"
+ );
+ cleanupTestTabs();
+});
+
+add_task(async function test_replaceQueryStringAndFragment() {
+ let tabRefAboutHome = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:home?hello=firefox#aaa"
+ );
+ let tabRefAboutMozilla = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla?hello=firefoxos#aaa"
+ );
+
+ switchTab("about:home", false);
+ gBrowser.removeCurrentTab();
+ switchTab("about:home?hello=firefox#aaa", true);
+ is(
+ tabRefAboutHome,
+ gBrowser.selectedTab,
+ "Selected tab should be the initial about:home tab"
+ );
+ switchTab("about:mozilla?hello=firefox#bbb", true, {
+ replaceQueryString: true,
+ ignoreFragment: "whenComparingAndReplace",
+ });
+ is(
+ tabRefAboutMozilla,
+ gBrowser.selectedTab,
+ "Selected tab should be the initial about:mozilla tab"
+ );
+ switchTab("about:home?hello=firefoxos#bbb", true, {
+ ignoreQueryString: true,
+ ignoreFragment: "whenComparingAndReplace",
+ });
+ is(
+ tabRefAboutHome,
+ gBrowser.selectedTab,
+ "Selected tab should be the initial about:home tab"
+ );
+ cleanupTestTabs();
+});
+
+add_task(async function test_ignoreQueryStringIgnoresFragment() {
+ let tabRefAboutHome = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:home?hello=firefox#aaa"
+ );
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla?hello=firefoxos#aaa"
+ );
+
+ switchTab("about:home?hello=firefox#bbb", false, { ignoreQueryString: true });
+ gBrowser.removeCurrentTab();
+ switchTab("about:home?hello=firefoxos#aaa", true, {
+ ignoreQueryString: true,
+ });
+ is(
+ tabRefAboutHome,
+ gBrowser.selectedTab,
+ "Selected tab should be the initial about:home tab"
+ );
+ cleanupTestTabs();
+});
+
+// Begin helpers
+
+function cleanupTestTabs() {
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+}
+
+function switchTab(aURI, aShouldFindExistingTab, aOpenParams = {}) {
+ // Build the description before switchToTabHavingURI deletes the object properties.
+ let msg =
+ `Should switch to existing ${aURI} tab if one existed, ` +
+ `${
+ aOpenParams.ignoreFragment ? "ignoring" : "including"
+ } fragment portion, `;
+ if (aOpenParams.replaceQueryString) {
+ msg += "replacing";
+ } else if (aOpenParams.ignoreQueryString) {
+ msg += "ignoring";
+ } else {
+ msg += "including";
+ }
+ msg += " query string.";
+ aOpenParams.triggeringPrincipal =
+ Services.scriptSecurityManager.getSystemPrincipal();
+ let tabFound = switchToTabHavingURI(aURI, true, aOpenParams);
+ is(tabFound, aShouldFindExistingTab, msg);
+}
+
+registerCleanupFunction(cleanupTestTabs);
diff --git a/browser/components/urlbar/tests/browser/browser_switchToTab_chiclet.js b/browser/components/urlbar/tests/browser/browser_switchToTab_chiclet.js
new file mode 100644
index 0000000000..6702ce340a
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_switchToTab_chiclet.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test for chiclet upon switching tab mode.
+ */
+
+"use strict";
+
+const TEST_URL = `${TEST_BASE_URL}dummy_page.html`;
+
+add_task(async function test_with_oneoff_button() {
+ info("Loading test page into first tab");
+ await BrowserTestUtils.loadURIString(gBrowser, TEST_URL);
+
+ info("Opening a new tab");
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ info("Wait for autocomplete");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+
+ info("Enter Tabs mode");
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.TABS,
+ });
+
+ info("Select first popup entry");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "dummy",
+ });
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ const result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ UrlbarTestUtils.getSelectedRowIndex(window)
+ );
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.TAB_SWITCH);
+
+ info("Enter escape key");
+ EventUtils.synthesizeKey("KEY_Escape");
+
+ info("Check label visibility");
+ const searchModeTitle = document.getElementById(
+ "urlbar-search-mode-indicator-title"
+ );
+ const switchTabLabel = document.getElementById("urlbar-label-switchtab");
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ BrowserTestUtils.is_visible(searchModeTitle) &&
+ searchModeTitle.textContent === "Tabs",
+ "Waiting until the search mode title will be visible"
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => BrowserTestUtils.is_hidden(switchTabLabel),
+ "Waiting until the switch tab label will be hidden"
+ );
+
+ await PlacesUtils.history.clear();
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function test_with_keytype() {
+ info("Loading test page into first tab");
+ await BrowserTestUtils.loadURIString(gBrowser, TEST_URL);
+
+ info("Opening a new tab");
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ info("Enter Tabs mode with keytype");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "%",
+ });
+
+ info("Select second popup entry");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "dummy",
+ });
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ const result = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ UrlbarTestUtils.getSelectedRowIndex(window)
+ );
+ Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.TAB_SWITCH);
+
+ info("Enter escape key");
+ EventUtils.synthesizeKey("KEY_Escape");
+
+ info("Check label visibility");
+ const searchModeTitle = document.getElementById(
+ "urlbar-search-mode-indicator-title"
+ );
+ const switchTabLabel = document.getElementById("urlbar-label-switchtab");
+ await BrowserTestUtils.waitForCondition(
+ () => BrowserTestUtils.is_hidden(searchModeTitle),
+ "Waiting until the search mode title will be hidden"
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => BrowserTestUtils.is_visible(switchTabLabel),
+ "Waiting until the switch tab label will be visible"
+ );
+
+ await PlacesUtils.history.clear();
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_switchToTab_closes_newtab.js b/browser/components/urlbar/tests/browser/browser_switchToTab_closes_newtab.js
new file mode 100644
index 0000000000..5031491d7e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_switchToTab_closes_newtab.js
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This tests that switch to tab from a blank tab switches and then closes
+ * the blank tab.
+ */
+
+"use strict";
+
+add_task(async function test_switchToTab_closes() {
+ let testURL =
+ "http://example.org/browser/browser/components/urlbar/tests/browser/dummy_page.html";
+
+ // Open the base tab
+ let baseTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, testURL);
+
+ if (baseTab.linkedBrowser.currentURI.spec == "about:blank") {
+ return;
+ }
+
+ // Open a blank tab to start the test from.
+ let testTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // Functions for TabClose and TabSelect
+ let tabClosePromise = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabClose",
+ false,
+ event => {
+ return event.originalTarget == testTab;
+ }
+ );
+ let tabSelectPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabSelect",
+ false,
+ event => {
+ return event.originalTarget == baseTab;
+ }
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "dummy",
+ });
+
+ // The first result is the heuristic, the second will be the switch to tab.
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ EventUtils.synthesizeMouseAtCenter(element, {}, window);
+
+ await Promise.all([tabSelectPromise, tabClosePromise]);
+
+ // Confirm that the selected tab is now the base tab
+ Assert.equal(
+ gBrowser.selectedTab,
+ baseTab,
+ "Should have switched to the correct tab"
+ );
+
+ gBrowser.removeTab(baseTab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_switchToTab_fullUrl_repeatedKeydown.js b/browser/components/urlbar/tests/browser/browser_switchToTab_fullUrl_repeatedKeydown.js
new file mode 100644
index 0000000000..8f80ac5841
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_switchToTab_fullUrl_repeatedKeydown.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/. */
+
+/**
+ * This tests that typing a url and picking a switch to tab actually switches
+ * to the right tab. Also tests repeated keydown/keyup events don't confuse
+ * override.
+ */
+
+"use strict";
+
+add_task(async function test_switchToTab_url() {
+ const TEST_URL = "https://example.org/browser/";
+
+ let baseTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+ let testTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // Functions for TabClose and TabSelect
+ let tabClosePromise = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabClose",
+ false,
+ event => event.target == testTab
+ );
+ let tabSelectPromise = BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "TabSelect",
+ false,
+ event => event.target == baseTab
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_URL,
+ fireInputEvent: true,
+ });
+ // The first result is the heuristic, the second will be the switch to tab.
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+
+ // Simulate a long press, on some platforms (Windows) it can generate multiple
+ // keydown events.
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keydown", repeat: 3 });
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keyup" });
+
+ // Pick the switch to tab result.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ await Promise.all([tabSelectPromise, tabClosePromise]);
+
+ // Confirm that the selected tab is now the base tab
+ Assert.equal(
+ gBrowser.selectedTab,
+ baseTab,
+ "Should have switched to the correct tab"
+ );
+
+ gBrowser.removeTab(baseTab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_tabKeyBehavior.js b/browser/components/urlbar/tests/browser/browser_tabKeyBehavior.js
new file mode 100644
index 0000000000..e326939581
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_tabKeyBehavior.js
@@ -0,0 +1,378 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test makes sure that the tab key properly adjusts the selection or moves
+// through toolbar items, depending on the urlbar state.
+// When the view is open, tab should go through results if the urlbar was
+// focused with the mouse, or has a typed string.
+
+"use strict";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.quickactions", false]],
+ });
+
+ for (let i = 0; i < UrlbarPrefs.get("maxRichResults"); i++) {
+ await PlacesTestUtils.addVisits("http://example.com/" + i);
+ }
+
+ registerCleanupFunction(PlacesUtils.history.clear);
+
+ CustomizableUI.addWidgetToArea("home-button", "nav-bar", 0);
+ CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar");
+ registerCleanupFunction(() => {
+ CustomizableUI.removeWidgetFromArea("home-button");
+ CustomizableUI.removeWidgetFromArea("sidebar-button");
+ });
+});
+
+add_task(async function tabWithSearchString() {
+ info("Tab with a search string");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ await expectTabThroughResults();
+ info("Reverse Tab with a search string");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ await expectTabThroughResults({ reverse: true });
+});
+
+add_task(async function tabNoSearchString() {
+ info("Tab without a search string");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ fireInputEvent: true,
+ });
+ await expectTabThroughToolbar();
+ info("Reverse Tab without a search string");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ fireInputEvent: true,
+ });
+ await expectTabThroughToolbar({ reverse: true });
+});
+
+add_task(async function tabAfterBlur() {
+ info("Tab after closing the view");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await expectTabThroughToolbar();
+});
+
+add_task(async function tabNoSearchStringMouseFocus() {
+ info("Tab in a new blank tab after mouse focus");
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await expectTabThroughResults();
+ });
+ info("Tab in a loaded tab after mouse focus");
+ await BrowserTestUtils.withNewTab("example.com", async () => {
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await expectTabThroughResults();
+ });
+});
+
+add_task(async function tabNoSearchStringKeyboardFocus() {
+ info("Tab in a new blank tab after keyboard focus");
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeKey("l", { accelKey: true });
+ });
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await expectTabThroughToolbar();
+ });
+ info("Tab in a loaded tab after keyboard focus");
+ await BrowserTestUtils.withNewTab("example.com", async () => {
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeKey("l", { accelKey: true });
+ });
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await expectTabThroughToolbar();
+ });
+});
+
+add_task(async function tabRetainedResultMouseFocus() {
+ info("Tab after retained results with mouse focus");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await expectTabThroughResults();
+});
+
+add_task(async function tabRetainedResultsKeyboardFocus() {
+ info("Tab after retained results with keyboard focus");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeKey("l", { accelKey: true });
+ });
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await expectTabThroughResults();
+});
+
+add_task(async function tabRetainedResults() {
+ info("Tab with a search string after mouse focus.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ await expectTabThroughResults();
+});
+
+add_task(async function tabSearchModePreview() {
+ info(
+ "Tab past a search mode preview keywordoffer after focusing with the keyboard."
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeKey("l", { accelKey: true });
+ });
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(
+ result.searchParams.keyword,
+ "The first result is a keyword offer."
+ );
+
+ // Sanity check: the Urlbar value is cleared when keywordoffer results are
+ // selected.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.ok(!gURLBar.value, "The Urlbar should have no value.");
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+
+ await expectTabThroughResults();
+
+ await UrlbarTestUtils.promisePopupClose(window, async () => {
+ gURLBar.blur();
+ // Verify that blur closes search mode preview.
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ });
+});
+
+add_task(async function tabTabToSearch() {
+ info("Tab past a tab-to-search result after focusing with the keyboard.");
+ await SearchTestUtils.installSearchExtension();
+
+ for (let i = 0; i < 3; i++) {
+ await PlacesTestUtils.addVisits(["https://example.com/"]);
+ }
+
+ // Search for a tab-to-search result.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "exam",
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeKey("l", { accelKey: true });
+ });
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ let tabToSearchResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1)
+ ).result;
+ Assert.equal(
+ tabToSearchResult.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+
+ await expectTabThroughResults();
+
+ await UrlbarTestUtils.promisePopupClose(window, async () => {
+ gURLBar.blur();
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ });
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function tabNoSearchStringSearchMode() {
+ info(
+ "Tab through the toolbar when refocusing a Urlbar in search mode with the keyboard."
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ fireInputEvent: true,
+ });
+ // Enter history search mode to avoid hitting the network.
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ });
+ await UrlbarTestUtils.promisePopupClose(window);
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeKey("l", { accelKey: true });
+ });
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ await expectTabThroughToolbar();
+
+ // We have to reopen the view to exit search mode.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+add_task(async function tabOnTopSites() {
+ info("Tab through the toolbar when focusing the Address Bar on top sites.");
+ for (let val of [true, false]) {
+ info(`Test with keyboard_navigation set to "${val}"`);
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.toolbars.keyboard_navigation", val]],
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ fireInputEvent: true,
+ });
+ Assert.ok(
+ UrlbarTestUtils.getResultCount(window) > 0,
+ "There should be some results"
+ );
+ Assert.deepEqual(
+ UrlbarTestUtils.getSelectedElement(window),
+ null,
+ "There should be no selection"
+ );
+
+ await expectTabThroughToolbar();
+ await UrlbarTestUtils.promisePopupClose(window);
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+async function expectTabThroughResults(options = { reverse: false }) {
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+ Assert.ok(resultCount > 0, "There should be results");
+
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+
+ let initiallySelectedIndex = result.heuristic ? 0 : -1;
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ initiallySelectedIndex,
+ "Check the initial selection."
+ );
+
+ for (let i = initiallySelectedIndex + 1; i < resultCount; i++) {
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: options.reverse });
+ if (
+ UrlbarTestUtils.getButtonForResultIndex(
+ window,
+ "menu",
+ UrlbarTestUtils.getSelectedRowIndex(window)
+ )
+ ) {
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: options.reverse });
+ }
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ options.reverse ? resultCount - i : i
+ );
+ }
+
+ EventUtils.synthesizeKey("KEY_Tab");
+
+ if (!options.reverse) {
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ initiallySelectedIndex,
+ "Should be back at the initial selection."
+ );
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ gURLBar.blur();
+ });
+}
+
+async function expectTabThroughToolbar(options = { reverse: false }) {
+ if (gURLBar.getAttribute("pageproxystate") == "valid") {
+ Assert.equal(document.activeElement, gURLBar.inputField);
+ EventUtils.synthesizeKey("KEY_Tab");
+ Assert.notEqual(document.activeElement, gURLBar.inputField);
+ } else {
+ let focusPromise = waitForFocusOnNextFocusableElement(options.reverse);
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: options.reverse });
+ await focusPromise;
+ }
+ Assert.ok(!gURLBar.view.isOpen, "The urlbar view should be closed.");
+}
+
+async function waitForFocusOnNextFocusableElement(reverse = false) {
+ if (
+ !Services.prefs.getBoolPref("browser.toolbars.keyboard_navigation", true)
+ ) {
+ return BrowserTestUtils.waitForCondition(
+ () => document.activeElement == gBrowser.selectedBrowser
+ );
+ }
+ let urlbar = document.getElementById("urlbar-container");
+ let nextFocusableElement = reverse
+ ? urlbar.previousElementSibling
+ : urlbar.nextElementSibling;
+ while (
+ nextFocusableElement &&
+ (!nextFocusableElement.classList.contains("toolbarbutton-1") ||
+ nextFocusableElement.hasAttribute("hidden") ||
+ nextFocusableElement.hasAttribute("disabled") ||
+ BrowserTestUtils.is_hidden(nextFocusableElement))
+ ) {
+ nextFocusableElement = reverse
+ ? nextFocusableElement.previousElementSibling
+ : nextFocusableElement.nextElementSibling;
+ }
+ info(
+ `Next focusable element: ${nextFocusableElement.localName}.#${nextFocusableElement.id}`
+ );
+
+ Assert.ok(
+ nextFocusableElement.classList.contains("toolbarbutton-1"),
+ "We should have a reference to the next focusable element after the Urlbar."
+ );
+
+ return BrowserTestUtils.waitForCondition(
+ () => nextFocusableElement.tabIndex == -1
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/browser_tabMatchesInAwesomebar.js b/browser/components/urlbar/tests/browser/browser_tabMatchesInAwesomebar.js
new file mode 100644
index 0000000000..e186681907
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_tabMatchesInAwesomebar.js
@@ -0,0 +1,224 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Tests for ensuring that the tab switch results correctly match what is
+ * currently available.
+ */
+
+requestLongerTimeout(2);
+
+const TEST_URL_BASES = [
+ `${TEST_BASE_URL}dummy_page.html#tabmatch`,
+ `${TEST_BASE_URL}moz.png#tabmatch`,
+];
+
+const RESTRICT_TOKEN_OPENPAGE = "%";
+
+var gTabCounter = 0;
+
+add_task(async function step_1() {
+ info("Running step 1");
+ let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+ let promises = [];
+ for (let i = 0; i < maxResults - 1; i++) {
+ let tab = BrowserTestUtils.addTab(gBrowser);
+ promises.push(loadTab(tab, TEST_URL_BASES[0] + ++gTabCounter));
+ }
+
+ await Promise.all(promises);
+ await ensure_opentabs_match_db();
+});
+
+add_task(async function step_2() {
+ info("Running step 2");
+ gBrowser.selectTabAtIndex(1);
+ gBrowser.removeCurrentTab();
+ gBrowser.selectTabAtIndex(1);
+ gBrowser.removeCurrentTab();
+ gBrowser.selectTabAtIndex(0);
+
+ let promises = [];
+ for (let i = 1; i < gBrowser.tabs.length; i++) {
+ promises.push(loadTab(gBrowser.tabs[i], TEST_URL_BASES[1] + ++gTabCounter));
+ }
+
+ await Promise.all(promises);
+ await ensure_opentabs_match_db();
+});
+
+add_task(async function step_3() {
+ info("Running step 3");
+ let promises = [];
+ for (let i = 1; i < gBrowser.tabs.length; i++) {
+ promises.push(loadTab(gBrowser.tabs[i], TEST_URL_BASES[0] + gTabCounter));
+ }
+
+ await Promise.all(promises);
+ await ensure_opentabs_match_db();
+});
+
+add_task(async function step_4() {
+ info("Running step 4 - ensure we don't register subframes as open pages");
+ let tab = BrowserTestUtils.addTab(
+ gBrowser,
+ 'data:text/html,<body><iframe src=""></iframe></body>'
+ );
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let iframe_loaded = ContentTaskUtils.waitForEvent(
+ content.document,
+ "load",
+ true
+ );
+ content.document.querySelector("iframe").src = "http://test2.example.org/";
+ await iframe_loaded;
+ });
+
+ await ensure_opentabs_match_db();
+});
+
+add_task(async function step_5() {
+ info("Running step 5 - remove tab immediately");
+ let tab = BrowserTestUtils.addTab(gBrowser, "about:logo");
+ BrowserTestUtils.removeTab(tab);
+ await ensure_opentabs_match_db();
+});
+
+add_task(async function step_6() {
+ info(
+ "Running step 6 - check swapBrowsersAndCloseOther preserves registered switch-to-tab result"
+ );
+ let tabToKeep = BrowserTestUtils.addTab(gBrowser);
+ let tab = BrowserTestUtils.addTab(gBrowser, "about:mozilla");
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ gBrowser.updateBrowserRemoteness(tabToKeep.linkedBrowser, {
+ remoteType: tab.linkedBrowser.isRemoteBrowser
+ ? E10SUtils.DEFAULT_REMOTE_TYPE
+ : E10SUtils.NOT_REMOTE,
+ });
+ gBrowser.swapBrowsersAndCloseOther(tabToKeep, tab);
+
+ await ensure_opentabs_match_db();
+
+ BrowserTestUtils.removeTab(tabToKeep);
+
+ await ensure_opentabs_match_db();
+});
+
+add_task(async function step_7() {
+ info("Running step 7 - close all tabs");
+
+ Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+
+ BrowserTestUtils.addTab(gBrowser, "about:blank", { skipAnimation: true });
+ while (gBrowser.tabs.length > 1) {
+ info("Removing tab: " + gBrowser.tabs[0].linkedBrowser.currentURI.spec);
+ gBrowser.selectTabAtIndex(0);
+ gBrowser.removeCurrentTab();
+ }
+
+ await ensure_opentabs_match_db();
+});
+
+add_task(async function cleanup() {
+ info("Cleaning up");
+
+ await PlacesUtils.history.clear();
+});
+
+function loadTab(tab, url) {
+ // Because adding visits is async, we will not be notified immediately.
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ let visited = new Promise(resolve => {
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ if (url != aSubject.QueryInterface(Ci.nsIURI).spec) {
+ return;
+ }
+ Services.obs.removeObserver(observer, aTopic);
+ resolve();
+ }, "uri-visit-saved");
+ });
+
+ info("Loading page: " + url);
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, url);
+ return Promise.all([loaded, visited]);
+}
+
+function ensure_opentabs_match_db() {
+ let tabs = {};
+
+ for (let browserWin of Services.wm.getEnumerator("navigator:browser")) {
+ // skip closed-but-not-destroyed windows
+ if (browserWin.closed) {
+ continue;
+ }
+
+ for (let i = 0; i < browserWin.gBrowser.tabs.length; i++) {
+ let browser = browserWin.gBrowser.getBrowserAtIndex(i);
+ let url = browser.currentURI.spec;
+ if (browserWin.isBlankPageURL(url)) {
+ continue;
+ }
+ if (!(url in tabs)) {
+ tabs[url] = 1;
+ } else {
+ tabs[url]++;
+ }
+ }
+ }
+
+ return checkAutocompleteResults(tabs);
+}
+
+async function checkAutocompleteResults(expected) {
+ info("Searching open pages.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: RESTRICT_TOKEN_OPENPAGE,
+ });
+
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+ for (let i = 0; i < resultCount; i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ if (result.heuristic) {
+ info("Skip heuristic match");
+ continue;
+ }
+
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
+ "Should have a tab switch result"
+ );
+
+ let url = result.url;
+
+ info(`Search for ${url} in open tabs.`);
+ let inExpected = url in expected;
+ Assert.ok(
+ inExpected,
+ `${url} was found in autocomplete, was ${
+ inExpected ? "" : "not "
+ } expected`
+ );
+ // Remove the found entry from expected results.
+ delete expected[url];
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+
+ // Make sure there is no reported open page that is not open.
+ for (let entry in expected) {
+ Assert.ok(!entry, `Should have been found in autocomplete`);
+ }
+}
diff --git a/browser/components/urlbar/tests/browser/browser_tabMatchesInAwesomebar_perwindowpb.js b/browser/components/urlbar/tests/browser/browser_tabMatchesInAwesomebar_perwindowpb.js
new file mode 100644
index 0000000000..b7b13eecf8
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_tabMatchesInAwesomebar_perwindowpb.js
@@ -0,0 +1,120 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test ensures that we don't switch between tabs from normal window to
+ * private browsing window or opposite.
+ */
+
+const TEST_URL = `${TEST_BASE_URL}dummy_page.html`;
+
+add_task(async function () {
+ let normalWindow = await BrowserTestUtils.openNewBrowserWindow();
+ let privateWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ await runTest(normalWindow, privateWindow, false);
+ await BrowserTestUtils.closeWindow(normalWindow);
+ await BrowserTestUtils.closeWindow(privateWindow);
+
+ normalWindow = await BrowserTestUtils.openNewBrowserWindow();
+ privateWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ await runTest(privateWindow, normalWindow, false);
+ await BrowserTestUtils.closeWindow(normalWindow);
+ await BrowserTestUtils.closeWindow(privateWindow);
+
+ privateWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ await runTest(privateWindow, privateWindow, true);
+ await BrowserTestUtils.closeWindow(privateWindow);
+
+ normalWindow = await BrowserTestUtils.openNewBrowserWindow();
+ await runTest(normalWindow, normalWindow, true);
+ await BrowserTestUtils.closeWindow(normalWindow);
+});
+
+async function runTest(aSourceWindow, aDestWindow, aExpectSwitch, aCallback) {
+ await BrowserTestUtils.openNewForegroundTab(aSourceWindow.gBrowser, TEST_URL);
+ let testTab = await BrowserTestUtils.openNewForegroundTab(
+ aDestWindow.gBrowser
+ );
+
+ info("waiting for focus on the window");
+ await SimpleTest.promiseFocus(aDestWindow);
+ info("got focus on the window");
+
+ // Select the testTab
+ aDestWindow.gBrowser.selectedTab = testTab;
+
+ // Ensure that this tab has no history entries
+ let sessionHistoryCount = await new Promise(resolve => {
+ SessionStore.getSessionHistory(
+ gBrowser.selectedTab,
+ function (sessionHistory) {
+ resolve(sessionHistory.entries.length);
+ }
+ );
+ });
+
+ ok(
+ sessionHistoryCount < 2,
+ `The test tab has 1 or fewer history entries. sessionHistoryCount=${sessionHistoryCount}`
+ );
+ // Ensure that this tab is on about:blank
+ is(
+ testTab.linkedBrowser.currentURI.spec,
+ "about:blank",
+ "The test tab is on about:blank"
+ );
+ // Ensure that this tab's document has no child nodes
+ await SpecialPowers.spawn(testTab.linkedBrowser, [], async function () {
+ ok(
+ !content.document.body.hasChildNodes(),
+ "The test tab has no child nodes"
+ );
+ });
+ ok(
+ !testTab.hasAttribute("busy"),
+ "The test tab doesn't have the busy attribute"
+ );
+
+ // Wait for the Awesomebar popup to appear.
+ let searchString = TEST_URL;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: aDestWindow,
+ value: searchString,
+ });
+
+ info(`awesomebar popup appeared. aExpectSwitch: ${aExpectSwitch}`);
+ // Make sure the last match is selected.
+ while (
+ UrlbarTestUtils.getSelectedRowIndex(aDestWindow) <
+ UrlbarTestUtils.getResultCount(aDestWindow) - 1
+ ) {
+ info("handling key navigation for DOM_VK_DOWN key");
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, aDestWindow);
+ }
+
+ let awaitTabSwitch;
+ if (aExpectSwitch) {
+ awaitTabSwitch = BrowserTestUtils.waitForTabClosing(testTab);
+ }
+
+ // Execute the selected action.
+ EventUtils.synthesizeKey("KEY_Enter", {}, aDestWindow);
+ info("sent Enter command to the controller");
+
+ if (aExpectSwitch) {
+ // If we expect a tab switch then the current tab
+ // will be closed and we switch to the other tab.
+ await awaitTabSwitch;
+ } else {
+ // If we don't expect a tab switch then wait for the tab to load.
+ await BrowserTestUtils.browserLoaded(testTab.linkedBrowser);
+ }
+}
diff --git a/browser/components/urlbar/tests/browser/browser_tabToSearch.js b/browser/components/urlbar/tests/browser/browser_tabToSearch.js
new file mode 100644
index 0000000000..b029682eda
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_tabToSearch.js
@@ -0,0 +1,641 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests tab-to-search results. See also
+ * browser/components/urlbar/tests/unit/test_providerTabToSearch.js.
+ */
+
+"use strict";
+
+const TEST_ENGINE_NAME = "Test";
+const TEST_ENGINE_DOMAIN = "example.com";
+
+const DYNAMIC_RESULT_TYPE = "onboardTabToSearch";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarProviderTabToSearch:
+ "resource:///modules/UrlbarProviderTabToSearch.sys.mjs",
+});
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Disable onboarding results for general tests. They are enabled in tests
+ // that specifically address onboarding.
+ ["browser.urlbar.tabToSearch.onboard.interactionsLeft", 0],
+ ],
+ });
+
+ await SearchTestUtils.installSearchExtension({
+ name: TEST_ENGINE_NAME,
+ search_url: `https://${TEST_ENGINE_DOMAIN}/`,
+ });
+
+ for (let i = 0; i < 3; i++) {
+ await PlacesTestUtils.addVisits([`https://${TEST_ENGINE_DOMAIN}/`]);
+ }
+
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ });
+});
+
+// Tests that tab-to-search results preview search mode when highlighted. These
+// results are worth testing separately since they do not set the
+// payload.keyword parameter.
+add_task(async function basic() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ fireInputEvent: true,
+ });
+ let autofillResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(autofillResult.autofill);
+ Assert.equal(
+ autofillResult.url,
+ `https://${TEST_ENGINE_DOMAIN}/`,
+ "The autofilled URL matches the engine domain."
+ );
+
+ let tabToSearchResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1)
+ ).result;
+ Assert.equal(
+ tabToSearchResult.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+ Assert.equal(
+ tabToSearchResult.payload.engine,
+ TEST_ENGINE_NAME,
+ "The tab-to-search result is for the correct engine."
+ );
+ let tabToSearchDetails = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ 1
+ );
+ let [actionTabToSearch] = await document.l10n.formatValues([
+ {
+ id: Services.search.getEngineByName(
+ tabToSearchDetails.searchParams.engine
+ ).isGeneralPurposeEngine
+ ? "urlbar-result-action-tabtosearch-web"
+ : "urlbar-result-action-tabtosearch-other-engine",
+ args: { engine: tabToSearchDetails.searchParams.engine },
+ },
+ ]);
+ Assert.equal(
+ tabToSearchDetails.displayed.title,
+ `Search with ${tabToSearchDetails.searchParams.engine}`,
+ "The result's title is set correctly."
+ );
+ Assert.equal(
+ tabToSearchDetails.displayed.action,
+ actionTabToSearch,
+ "The correct action text is displayed in the tab-to-search result."
+ );
+
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 1,
+ "Sanity check: The second result is selected."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "tabtosearch",
+ isPreview: true,
+ });
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+});
+
+// Tests that we do not set aria-activedescendant after tabbing to a
+// tab-to-search result when the pref
+// browser.urlbar.accessibility.tabToSearch.announceResults is true. If that
+// pref is true, the result was already announced while the user was typing.
+add_task(async function activedescendant_tab() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.accessibility.tabToSearch.announceResults", true]],
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ fireInputEvent: true,
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "There should be two results."
+ );
+ let tabToSearchRow = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ 1
+ );
+ Assert.equal(
+ tabToSearchRow.result.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+
+ EventUtils.synthesizeKey("KEY_Tab");
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "tabtosearch",
+ isPreview: true,
+ });
+ let aadID = gURLBar.inputField.getAttribute("aria-activedescendant");
+ Assert.equal(aadID, null, "aria-activedescendant was not set.");
+
+ // Cycle through all the results then return to the tab-to-search result. It
+ // should be announced.
+ EventUtils.synthesizeKey("KEY_Tab");
+ aadID = gURLBar.inputField.getAttribute("aria-activedescendant");
+ let firstRow = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+ Assert.equal(
+ aadID,
+ firstRow._content.id,
+ "aria-activedescendant was set to the row after the tab-to-search result."
+ );
+ EventUtils.synthesizeKey("KEY_Tab");
+ aadID = gURLBar.inputField.getAttribute("aria-activedescendant");
+ Assert.equal(
+ aadID,
+ tabToSearchRow._content.id,
+ "aria-activedescendant was set to the tab-to-search result."
+ );
+
+ // Now close and reopen the view, then do another search that yields a
+ // tab-to-search result. aria-activedescendant should not be set when it is
+ // selected.
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ fireInputEvent: true,
+ });
+ tabToSearchRow = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ Assert.equal(
+ tabToSearchRow.result.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+
+ EventUtils.synthesizeKey("KEY_Tab");
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "tabtosearch",
+ isPreview: true,
+ });
+ aadID = gURLBar.inputField.getAttribute("aria-activedescendant");
+ Assert.equal(aadID, null, "aria-activedescendant was not set.");
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ await SpecialPowers.popPrefEnv();
+});
+
+// Tests that we set aria-activedescendant after accessing a tab-to-search
+// result with the arrow keys.
+add_task(async function activedescendant_arrow() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ fireInputEvent: true,
+ });
+ let tabToSearchRow = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ 1
+ );
+ Assert.equal(
+ tabToSearchRow.result.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "tabtosearch",
+ isPreview: true,
+ });
+ let aadID = gURLBar.inputField.getAttribute("aria-activedescendant");
+ Assert.equal(
+ aadID,
+ tabToSearchRow._content.id,
+ "aria-activedescendant was set to the tab-to-search result."
+ );
+
+ // Move selection away from the tab-to-search result then return. It should
+ // be announced.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ aadID = gURLBar.inputField.getAttribute("aria-activedescendant");
+ Assert.equal(
+ aadID,
+ UrlbarTestUtils.getOneOffSearchButtons(window).selectedButton.id,
+ "aria-activedescendant was moved to the first one-off."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ aadID = gURLBar.inputField.getAttribute("aria-activedescendant");
+ Assert.equal(
+ aadID,
+ tabToSearchRow._content.id,
+ "aria-activedescendant was set to the tab-to-search result."
+ );
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+});
+
+add_task(async function tab_key_race() {
+ // Mac Debug tinderboxes are just too slow and fail intermittently
+ // even if the EventBufferer timeout is set to an high value.
+ if (AppConstants.platform == "macosx" && AppConstants.DEBUG) {
+ return;
+ }
+ info(
+ "Test typing a letter followed shortly by down arrow consistently selects a tab-to-search result"
+ );
+ Assert.equal(gURLBar.value, "", "Sanity check urlbar is empty");
+ let promiseQueryStarted = new Promise(resolve => {
+ /**
+ * A no-op test provider.
+ * We use this to wait for the query to start, because otherwise TAB will
+ * move to the next widget since the panel is closed and there's no running
+ * query. This means waiting for the UrlbarProvidersManager to at least
+ * evaluate the isActive status of providers.
+ * In the future we should try to reduce this latency, to defer user events
+ * even more efficiently.
+ */
+ class ListeningTestProvider extends UrlbarProvider {
+ constructor() {
+ super();
+ }
+ get name() {
+ return "ListeningTestProvider";
+ }
+ get type() {
+ return UrlbarUtils.PROVIDER_TYPE.PROFILE;
+ }
+ isActive(context) {
+ executeSoon(resolve);
+ return false;
+ }
+ isRestricting(context) {
+ return false;
+ }
+ async startQuery(context, addCallback) {
+ // Nothing to do.
+ }
+ }
+ let provider = new ListeningTestProvider();
+ UrlbarProvidersManager.registerProvider(provider);
+ registerCleanupFunction(async function () {
+ UrlbarProvidersManager.unregisterProvider(provider);
+ });
+ });
+ gURLBar.focus();
+ info("Type the beginning of the search string to get tab-to-search");
+ EventUtils.synthesizeKey(TEST_ENGINE_DOMAIN.slice(0, 1));
+ info("Awaiting for the query to start");
+ await promiseQueryStarted;
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await TestUtils.waitForCondition(
+ () => UrlbarTestUtils.getSelectedRowIndex(window) == 1,
+ "Wait for down arrow key to be handled"
+ );
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "tabtosearch",
+ isPreview: true,
+ });
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+});
+
+// Test that large-style onboarding results appear and have the correct
+// properties.
+add_task(async function onboard() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.tabToSearch.onboard.interactionsLeft", 3]],
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ fireInputEvent: true,
+ });
+ let autofillResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(autofillResult.autofill);
+ Assert.equal(
+ autofillResult.url,
+ `https://${TEST_ENGINE_DOMAIN}/`,
+ "The autofilled URL matches the engine domain."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 1,
+ "Sanity check: The second result is selected."
+ );
+
+ // Now check the properties of the onboarding result.
+ let onboardingElement = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ 1
+ );
+ Assert.equal(
+ onboardingElement.result.payload.dynamicType,
+ DYNAMIC_RESULT_TYPE,
+ "The tab-to-search result is an onboarding result."
+ );
+ Assert.equal(
+ onboardingElement.result.resultSpan,
+ 2,
+ "The correct resultSpan was set."
+ );
+ Assert.ok(
+ onboardingElement
+ .querySelector(".urlbarView-row-inner")
+ .hasAttribute("selected"),
+ "The onboarding element set the selected attribute."
+ );
+
+ let [titleOnboarding, actionOnboarding, descriptionOnboarding] =
+ await document.l10n.formatValues([
+ {
+ id: "urlbar-result-action-search-w-engine",
+ args: {
+ engine: onboardingElement.result.payload.engine,
+ },
+ },
+ {
+ id: Services.search.getEngineByName(
+ onboardingElement.result.payload.engine
+ ).isGeneralPurposeEngine
+ ? "urlbar-result-action-tabtosearch-web"
+ : "urlbar-result-action-tabtosearch-other-engine",
+ args: { engine: onboardingElement.result.payload.engine },
+ },
+ {
+ id: "urlbar-tabtosearch-onboard",
+ },
+ ]);
+ let onboardingDetails = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ onboardingDetails.displayed.title,
+ titleOnboarding,
+ "The correct title was set."
+ );
+ Assert.equal(
+ onboardingDetails.displayed.action,
+ actionOnboarding,
+ "The correct action text was set."
+ );
+ Assert.equal(
+ onboardingDetails.element.row.querySelector(
+ ".urlbarView-dynamic-onboardTabToSearch-description"
+ ).textContent,
+ descriptionOnboarding,
+ "The correct description was set."
+ );
+
+ // Check that the onboarding result enters search mode.
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "tabtosearch_onboard",
+ isPreview: true,
+ });
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "tabtosearch_onboard",
+ });
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ UrlbarPrefs.set("tabToSearch.onboard.interactionsLeft", 3);
+ delete UrlbarProviderTabToSearch.onboardingInteractionAtTime;
+ await SpecialPowers.popPrefEnv();
+});
+
+// Tests that we show the onboarding result until the user interacts with it
+// `browser.urlbar.tabToSearch.onboard.interactionsLeft` times.
+add_task(async function onboard_limit() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.tabToSearch.onboard.interactionsLeft", 3]],
+ });
+
+ Assert.equal(
+ UrlbarPrefs.get("tabToSearch.onboard.interactionsLeft"),
+ 3,
+ "Sanity check: interactionsLeft is 3."
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ fireInputEvent: true,
+ });
+ let onboardingResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1)
+ ).result;
+ Assert.equal(
+ onboardingResult.payload.dynamicType,
+ DYNAMIC_RESULT_TYPE,
+ "The second result is an onboarding result."
+ );
+ await EventUtils.synthesizeKey("KEY_ArrowDown");
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "tabtosearch_onboard",
+ isPreview: true,
+ });
+ Assert.equal(UrlbarPrefs.get("tabToSearch.onboard.interactionsLeft"), 2);
+ await UrlbarTestUtils.exitSearchMode(window);
+
+ // We don't increment the counter if we showed the onboarding result less than
+ // 5 minutes ago.
+ for (let i = 0; i < 5; i++) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ fireInputEvent: true,
+ });
+ onboardingResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1)
+ ).result;
+ Assert.equal(
+ onboardingResult.payload.dynamicType,
+ DYNAMIC_RESULT_TYPE,
+ "The second result is an onboarding result."
+ );
+ await EventUtils.synthesizeKey("KEY_ArrowDown");
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "tabtosearch_onboard",
+ isPreview: true,
+ });
+ Assert.equal(
+ UrlbarPrefs.get("tabToSearch.onboard.interactionsLeft"),
+ 2,
+ "We shouldn't decrement interactionsLeft if an onboarding result was just shown."
+ );
+ await UrlbarTestUtils.exitSearchMode(window);
+ }
+
+ // If the user doesn't interact with the result, we don't increment the
+ // counter.
+ for (let i = 0; i < 5; i++) {
+ delete UrlbarProviderTabToSearch.onboardingInteractionAtTime;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ fireInputEvent: true,
+ });
+ let tabToSearchResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1)
+ ).result;
+ Assert.equal(
+ tabToSearchResult.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+ Assert.equal(
+ tabToSearchResult.type,
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ "The tab-to-search result is an onboarding result."
+ );
+ Assert.equal(
+ UrlbarPrefs.get("tabToSearch.onboard.interactionsLeft"),
+ 2,
+ "We shouldn't decrement interactionsLeft if the user doesn't interact with onboarding."
+ );
+ }
+
+ // Test that we increment the counter if the user interacts with the result
+ // and it's been 5+ minutes since they last interacted with it.
+ for (let i = 1; i >= 0; i--) {
+ delete UrlbarProviderTabToSearch.onboardingInteractionAtTime;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ fireInputEvent: true,
+ });
+ let tabToSearchResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1)
+ ).result;
+ Assert.equal(
+ tabToSearchResult.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+ Assert.equal(
+ onboardingResult.payload.dynamicType,
+ DYNAMIC_RESULT_TYPE,
+ "The second result is an onboarding result."
+ );
+ await EventUtils.synthesizeKey("KEY_ArrowDown");
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "tabtosearch_onboard",
+ isPreview: true,
+ });
+ Assert.equal(
+ UrlbarPrefs.get("tabToSearch.onboard.interactionsLeft"),
+ i,
+ "We decremented interactionsLeft."
+ );
+ await UrlbarTestUtils.exitSearchMode(window);
+ }
+
+ delete UrlbarProviderTabToSearch.onboardingInteractionAtTime;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ fireInputEvent: true,
+ });
+ let tabToSearchResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1)
+ ).result;
+ Assert.equal(
+ tabToSearchResult.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+ Assert.notEqual(
+ tabToSearchResult.type,
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ "Now that interactionsLeft is 0, we don't show onboarding results."
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ UrlbarPrefs.set("tabToSearch.onboard.interactionsLeft", 3);
+ delete UrlbarProviderTabToSearch.onboardingInteractionAtTime;
+ await SpecialPowers.popPrefEnv();
+});
+
+// Tests that we show at most one onboarding result at a time. See
+// tests/unit/test_providerTabToSearch.js:multipleEnginesForHostname for a test
+// that ensures only one normal tab-to-search result is shown in this scenario.
+add_task(async function onboard_multipleEnginesForHostname() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.tabToSearch.onboard.interactionsLeft", 3]],
+ });
+
+ let extension = await SearchTestUtils.installSearchExtension(
+ {
+ name: `${TEST_ENGINE_NAME}Maps`,
+ search_url: `https://${TEST_ENGINE_DOMAIN}/maps/`,
+ },
+ { skipUnload: true }
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ fireInputEvent: true,
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "Only two results are shown."
+ );
+ let firstResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0)
+ ).result;
+ Assert.notEqual(
+ firstResult.providerName,
+ "TabToSearch",
+ "The first result is not from TabToSearch."
+ );
+ let secondResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1)
+ ).result;
+ Assert.equal(
+ secondResult.providerName,
+ "TabToSearch",
+ "The second result is from TabToSearch."
+ );
+ Assert.equal(
+ secondResult.type,
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ "The tab-to-search result is the only onboarding result."
+ );
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ await extension.unload();
+ UrlbarPrefs.set("tabToSearch.onboard.interactionsLeft", 3);
+ delete UrlbarProviderTabToSearch.onboardingInteractionAtTime;
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_textruns.js b/browser/components/urlbar/tests/browser/browser_textruns.js
new file mode 100644
index 0000000000..ed7a61e6b0
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_textruns.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test ensures that we limit textruns in case of very long urls or titles.
+ */
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", true]],
+ });
+ await SearchTestUtils.installSearchExtension(
+ { name: "Test" },
+ { setAsDefault: true }
+ );
+
+ let lotsOfSpaces = "%20".repeat(300);
+ await PlacesTestUtils.addVisits({
+ uri: `https://textruns.mozilla.org/${lotsOfSpaces}/test/`,
+ title: `A long ${lotsOfSpaces} title`,
+ });
+ await UrlbarTestUtils.formHistory.add([
+ { value: `A long ${lotsOfSpaces} textruns suggestion` },
+ ]);
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "textruns",
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(result.searchParams.engine, "Test", "Sanity check engine");
+ Assert.equal(
+ result.displayed.title.length,
+ UrlbarUtils.MAX_TEXT_LENGTH,
+ "Result title should be limited"
+ );
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
+ Assert.equal(
+ result.displayed.title.length,
+ UrlbarUtils.MAX_TEXT_LENGTH,
+ "Result title should be limited"
+ );
+ Assert.equal(
+ result.displayed.url.length,
+ UrlbarUtils.MAX_TEXT_LENGTH,
+ "Result url should be limited"
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_tokenAlias.js b/browser/components/urlbar/tests/browser/browser_tokenAlias.js
new file mode 100644
index 0000000000..d215c2536f
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_tokenAlias.js
@@ -0,0 +1,861 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test checks "@" search engine aliases ("token aliases") in the urlbar.
+
+"use strict";
+
+const TEST_ALIAS_ENGINE_NAME = "Test";
+const ALIAS = "@test";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+// We make sure that aliases and search terms are correctly recognized when they
+// are separated by each of these different types of spaces and combinations of
+// spaces. U+3000 is the ideographic space in CJK and is commonly used by CJK
+// speakers.
+const TEST_SPACES = [" ", "\u3000", " \u3000", "\u3000 "];
+
+// Allow more time for Mac machines so they don't time out in verify mode. See
+// bug 1673062.
+if (AppConstants.platform == "macosx") {
+ requestLongerTimeout(5);
+}
+
+add_setup(async function () {
+ // Add a default engine with suggestions, to avoid hitting the network when
+ // fetching them.
+ let defaultEngine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ setAsDefault: true,
+ });
+ defaultEngine.alias = "@default";
+ await SearchTestUtils.installSearchExtension({
+ name: TEST_ALIAS_ENGINE_NAME,
+ keyword: ALIAS,
+ });
+
+ // Search results aren't shown in quantumbar unless search suggestions are
+ // enabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", true]],
+ });
+});
+
+// Simple test that tries different variations of an alias, without reverting
+// the urlbar value in between.
+add_task(async function testNoRevert() {
+ await doSimpleTest(false);
+});
+
+// Simple test that tries different variations of an alias, reverting the urlbar
+// value in between.
+add_task(async function testRevert() {
+ await doSimpleTest(true);
+});
+
+async function doSimpleTest(revertBetweenSteps) {
+ // When autofill is enabled, searching for "@tes" will autofill to "@test",
+ // which gets in the way of this test task, so temporarily disable it.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.autoFill", false]],
+ });
+
+ // "@tes" -- not an alias, no search mode
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS.substr(0, ALIAS.length - 1),
+ });
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ Assert.equal(
+ gURLBar.value,
+ ALIAS.substr(0, ALIAS.length - 1),
+ "value should be alias substring"
+ );
+
+ if (revertBetweenSteps) {
+ gURLBar.handleRevert();
+ gURLBar.blur();
+ }
+
+ // "@test" -- alias but no trailing space, no search mode
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS,
+ });
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ Assert.equal(gURLBar.value, ALIAS, "value should be alias");
+
+ if (revertBetweenSteps) {
+ gURLBar.handleRevert();
+ gURLBar.blur();
+ }
+
+ // "@test " -- alias with trailing space, search mode
+ for (let spaces of TEST_SPACES) {
+ info("Testing: " + JSON.stringify({ spaces: codePoints(spaces) }));
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS + spaces,
+ });
+ // Wait for the second new search that starts when search mode is entered.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ALIAS_ENGINE_NAME,
+ entry: "typed",
+ });
+ Assert.equal(gURLBar.value, "", "value should be empty");
+ await UrlbarTestUtils.exitSearchMode(window);
+
+ if (revertBetweenSteps) {
+ gURLBar.handleRevert();
+ gURLBar.blur();
+ }
+ }
+
+ // "@test foo" -- alias, search mode
+ for (let spaces of TEST_SPACES) {
+ info("Testing: " + JSON.stringify({ spaces: codePoints(spaces) }));
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS + spaces + "foo",
+ });
+ // Wait for the second new search that starts when search mode is entered.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ALIAS_ENGINE_NAME,
+ entry: "typed",
+ });
+ Assert.equal(gURLBar.value, "foo", "value should be query");
+ await UrlbarTestUtils.exitSearchMode(window);
+
+ if (revertBetweenSteps) {
+ gURLBar.handleRevert();
+ gURLBar.blur();
+ }
+ }
+
+ // "@test " -- alias with trailing space, search mode
+ for (let spaces of TEST_SPACES) {
+ info("Testing: " + JSON.stringify({ spaces: codePoints(spaces) }));
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS + spaces,
+ });
+ // Wait for the second new search that starts when search mode is entered.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ALIAS_ENGINE_NAME,
+ entry: "typed",
+ });
+ Assert.equal(gURLBar.value, "", "value should be empty");
+ await UrlbarTestUtils.exitSearchMode(window);
+
+ if (revertBetweenSteps) {
+ gURLBar.handleRevert();
+ gURLBar.blur();
+ }
+ }
+
+ // "@test" -- alias but no trailing space, no highlight
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS,
+ });
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ Assert.equal(gURLBar.value, ALIAS, "value should be alias");
+
+ if (revertBetweenSteps) {
+ gURLBar.handleRevert();
+ gURLBar.blur();
+ }
+
+ // "@tes" -- not an alias, no highlight
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS.substr(0, ALIAS.length - 1),
+ });
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ Assert.equal(
+ gURLBar.value,
+ ALIAS.substr(0, ALIAS.length - 1),
+ "value should be alias substring"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+
+ await SpecialPowers.popPrefEnv();
+}
+
+// An alias should be recognized even when there are spaces before it, and
+// search mode should be entered.
+add_task(async function spacesBeforeAlias() {
+ for (let spaces of TEST_SPACES) {
+ info("Testing: " + JSON.stringify({ spaces: codePoints(spaces) }));
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: spaces + ALIAS + spaces,
+ });
+ // Wait for the second new search that starts when search mode is entered.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ALIAS_ENGINE_NAME,
+ entry: "typed",
+ });
+ Assert.equal(gURLBar.value, "", "value should be empty");
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+ }
+});
+
+// An alias in the middle of a string should not be recognized and search mode
+// should not be entered.
+add_task(async function charsBeforeAlias() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "not an alias " + ALIAS + " ",
+ });
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ Assert.equal(
+ gURLBar.value,
+ "not an alias " + ALIAS + " ",
+ "value should be unchanged"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// While already in search mode, an alias should not be recognized.
+add_task(async function alreadyInSearchMode() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ALIAS + " ",
+ });
+
+ // Search mode source should still be bookmarks.
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "oneoff",
+ });
+ Assert.equal(gURLBar.value, ALIAS + " ", "value should be unchanged");
+
+ // Exit search mode, but first remove the value in the input. Since the value
+ // is "alias ", we'd actually immediately re-enter search mode otherwise.
+ gURLBar.value = "";
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// Types a space while typing an alias to ensure we stop autofilling.
+add_task(async function spaceWhileTypingAlias() {
+ for (let spaces of TEST_SPACES) {
+ if (spaces.length != 1) {
+ continue;
+ }
+ info("Testing: " + JSON.stringify({ spaces: codePoints(spaces) }));
+
+ let value = ALIAS.substring(0, ALIAS.length - 1);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ selectionStart: value.length,
+ selectionEnd: value.length,
+ });
+ Assert.equal(gURLBar.value, ALIAS + " ", "Alias should be autofilled");
+
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey(spaces);
+ await searchPromise;
+
+ Assert.equal(
+ gURLBar.value,
+ value + spaces,
+ "Alias should not be autofilled"
+ );
+ await UrlbarTestUtils.assertSearchMode(window, null);
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ }
+});
+
+// Aliases are case insensitive. Make sure that searching with an alias using a
+// weird case still causes the alias to be recognized and search mode entered.
+add_task(async function aliasCase() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@TeSt ",
+ });
+ // Wait for the second new search that starts when search mode is entered.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ALIAS_ENGINE_NAME,
+ entry: "typed",
+ });
+ Assert.equal(gURLBar.value, "", "value should be empty");
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// Same as previous but with a query.
+add_task(async function aliasCase_query() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@tEsT query",
+ });
+ // Wait for the second new search that starts when search mode is entered.
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ALIAS_ENGINE_NAME,
+ entry: "typed",
+ });
+ Assert.equal(gURLBar.value, "query", "value should be query");
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// Selecting a non-heuristic (non-first) search engine result with an alias and
+// empty query should put the alias in the urlbar and highlight it.
+// Also checks that internal aliases appear with the "@" keyword.
+add_task(async function nonHeuristicAliases() {
+ // Get the list of token alias engines (those with aliases that start with
+ // "@").
+ let tokenEngines = [];
+ for (let engine of await Services.search.getEngines()) {
+ let aliases = [];
+ if (engine.alias) {
+ aliases.push(engine.alias);
+ }
+ aliases.push(...engine.aliases);
+ let tokenAliases = aliases.filter(a => a.startsWith("@"));
+ if (tokenAliases.length) {
+ tokenEngines.push({ engine, tokenAliases });
+ }
+ }
+ if (!tokenEngines.length) {
+ Assert.ok(true, "No token alias engines, skipping task.");
+ return;
+ }
+ info(
+ "Got token alias engines: " + tokenEngines.map(({ engine }) => engine.name)
+ );
+
+ // Populate the results with the list of token alias engines by searching for
+ // "@".
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ });
+ await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ tokenEngines.length - 1
+ );
+ // Key down to select each result in turn. The urlbar should preview search
+ // mode for each engine.
+ for (let { tokenAliases } of tokenEngines) {
+ let alias = tokenAliases[0];
+ let engineName = (await UrlbarSearchUtils.engineForAlias(alias)).name;
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ let expectedSearchMode = {
+ engineName,
+ entry: "keywordoffer",
+ isPreview: true,
+ };
+ if (Services.search.getEngineByName(engineName).isGeneralPurposeEngine) {
+ expectedSearchMode.source = UrlbarUtils.RESULT_SOURCE.SEARCH;
+ }
+ await UrlbarTestUtils.assertSearchMode(window, expectedSearchMode);
+ Assert.ok(!gURLBar.value, "The Urlbar should be empty.");
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// Clicking on an @ alias offer (an @ alias with an empty search string) in the
+// view should enter search mode.
+add_task(async function clickAndFillAlias() {
+ // Do a search for "@" to show all the @ aliases.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ });
+
+ // Find our test engine in the results. It's probably last, but for
+ // robustness don't assume it is.
+ let testEngineItem;
+ for (let i = 0; !testEngineItem; i++) {
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.equal(
+ details.displayed.title,
+ `Search with ${details.searchParams.engine}`,
+ "The result's title is set correctly."
+ );
+ Assert.ok(!details.action, "The result should have no action text.");
+ if (details.searchParams && details.searchParams.keyword == ALIAS) {
+ testEngineItem = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ i
+ );
+ }
+ }
+
+ // Click it.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(testEngineItem, {});
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: testEngineItem.result.payload.engine,
+ entry: "keywordoffer",
+ });
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// Pressing enter on an @ alias offer (an @ alias with an empty search string)
+// in the view should enter search mode.
+add_task(async function enterAndFillAlias() {
+ // Do a search for "@" to show all the @ aliases.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ });
+
+ // Find our test engine in the results. It's probably last, but for
+ // robustness don't assume it is.
+ let details;
+ let index = 0;
+ for (; ; index++) {
+ details = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ if (details.searchParams && details.searchParams.keyword == ALIAS) {
+ index++;
+ break;
+ }
+ }
+
+ // Key down to it and press enter.
+ EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: index });
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: details.searchParams.engine,
+ entry: "keywordoffer",
+ });
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// Pressing Enter on an @ alias autofill should enter search mode.
+add_task(async function enterAutofillsAlias() {
+ for (let value of [ALIAS.substring(0, ALIAS.length - 1), ALIAS]) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ selectionStart: value.length,
+ selectionEnd: value.length,
+ });
+
+ // Press Enter.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ALIAS_ENGINE_NAME,
+ entry: "keywordoffer",
+ });
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ }
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// Pressing Right on an @ alias autofill should enter search mode.
+add_task(async function rightEntersSearchMode() {
+ for (let value of [ALIAS.substring(0, ALIAS.length - 1), ALIAS]) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ selectionStart: value.length,
+ selectionEnd: value.length,
+ });
+
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ALIAS_ENGINE_NAME,
+ entry: "typed",
+ });
+ Assert.equal(gURLBar.value, "", "value should be empty");
+ await UrlbarTestUtils.exitSearchMode(window);
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+// Pressing Tab when an @ alias is autofilled should enter search mode preview.
+add_task(async function rightEntersSearchMode() {
+ for (let value of [ALIAS.substring(0, ALIAS.length - 1), ALIAS]) {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value,
+ selectionStart: value.length,
+ selectionEnd: value.length,
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ -1,
+ "There is no selected result."
+ );
+
+ EventUtils.synthesizeKey("KEY_Tab");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "The first result is selected."
+ );
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ALIAS_ENGINE_NAME,
+ entry: "keywordoffer",
+ isPreview: true,
+ });
+ Assert.equal(gURLBar.value, "", "value should be empty");
+
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: TEST_ALIAS_ENGINE_NAME,
+ entry: "keywordoffer",
+ isPreview: false,
+ });
+ await UrlbarTestUtils.exitSearchMode(window);
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+});
+
+/**
+ * This test checks that if an engine is marked as hidden then
+ * it should not appear in the popup when using the "@" token alias in the search bar.
+ */
+add_task(async function hiddenEngine() {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ fireInputEvent: true,
+ });
+
+ const defaultEngine = await Services.search.getDefault();
+
+ let foundDefaultEngineInPopup = false;
+
+ // Checks that the default engine appears in the urlbar's popup.
+ for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ if (defaultEngine.name == details.searchParams.engine) {
+ foundDefaultEngineInPopup = true;
+ break;
+ }
+ }
+ Assert.ok(foundDefaultEngineInPopup, "Default engine appears in the popup.");
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+
+ // Checks that a hidden default engine (i.e. an engine removed from
+ // a user's search settings) does not appear in the urlbar's popup.
+ defaultEngine.hidden = true;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ fireInputEvent: true,
+ });
+ foundDefaultEngineInPopup = false;
+ for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ if (defaultEngine.name == details.searchParams.engine) {
+ foundDefaultEngineInPopup = true;
+ break;
+ }
+ }
+ Assert.ok(
+ !foundDefaultEngineInPopup,
+ "Hidden default engine does not appear in the popup"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Escape")
+ );
+
+ defaultEngine.hidden = false;
+});
+
+/**
+ * This test checks that if an engines alias is not prefixed with
+ * @ it still appears in the popup when using the "@" token
+ * alias in the search bar.
+ */
+add_task(async function nonPrefixedKeyword() {
+ let name = "Custom";
+ let alias = "customkeyword";
+ let extension = await SearchTestUtils.installSearchExtension(
+ {
+ name,
+ keyword: alias,
+ },
+ { skipUnload: true }
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ });
+
+ let foundEngineInPopup = false;
+
+ // Checks that the default engine appears in the urlbar's popup.
+ for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ if (details.searchParams.engine === name) {
+ foundEngineInPopup = true;
+ break;
+ }
+ }
+ Assert.ok(foundEngineInPopup, "Custom engine appears in the popup.");
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@" + alias,
+ });
+
+ let keywordOfferResult = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ 0
+ );
+
+ Assert.equal(
+ keywordOfferResult.searchParams.engine,
+ name,
+ "The first result should be a keyword search result with the correct engine."
+ );
+
+ await extension.unload();
+});
+
+// Tests that we show all engines with a token alias that match the search
+// string.
+add_task(async function multipleMatchingEngines() {
+ let extension = await SearchTestUtils.installSearchExtension(
+ {
+ name: "TestFoo",
+ keyword: `${ALIAS}foo`,
+ },
+ { skipUnload: true }
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@te",
+ fireInputEvent: true,
+ });
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "Two results are shown."
+ );
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ -1,
+ "Neither result is selected."
+ );
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(result.autofill, "The first result is autofilling.");
+ Assert.equal(
+ result.searchParams.keyword,
+ ALIAS,
+ "The autofilled engine is shown first."
+ );
+
+ result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ result.searchParams.keyword,
+ `${ALIAS}foo`,
+ "The other engine is shown second."
+ );
+
+ EventUtils.synthesizeKey("KEY_Tab");
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), 0);
+ Assert.equal(gURLBar.value, "", "Urlbar should be empty.");
+ EventUtils.synthesizeKey("KEY_Tab");
+ Assert.equal(UrlbarTestUtils.getSelectedRowIndex(window), 1);
+ Assert.equal(gURLBar.value, "", "Urlbar should be empty.");
+ EventUtils.synthesizeKey("KEY_Tab");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ -1,
+ "Tabbing all the way through the matching engines should return to the input."
+ );
+ Assert.equal(
+ gURLBar.value,
+ "@te",
+ "Urlbar should contain the search string."
+ );
+
+ await extension.unload();
+});
+
+// Tests that UrlbarProviderTokenAliasEngines is disabled in search mode.
+add_task(async function doNotShowInSearchMode() {
+ // Do a search for "@" to show all the @ aliases.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ });
+
+ // Find our test engine in the results. It's probably last, but for
+ // robustness don't assume it is.
+ let testEngineItem;
+ for (let i = 0; !testEngineItem; i++) {
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ if (details.searchParams && details.searchParams.keyword == ALIAS) {
+ testEngineItem = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ i
+ );
+ }
+ }
+
+ Assert.equal(
+ testEngineItem.result.payload.keyword,
+ ALIAS,
+ "Sanity check: we found our engine."
+ );
+
+ // Click it.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(testEngineItem, {});
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: testEngineItem.result.payload.engine,
+ entry: "keywordoffer",
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "@",
+ fireInputEvent: true,
+ });
+
+ let resultCount = UrlbarTestUtils.getResultCount(window);
+ for (let i = 0; i < resultCount; i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ Assert.ok(
+ !result.searchParams.keyword,
+ `Result at index ${i} is not a keywordoffer.`
+ );
+ }
+});
+
+async function assertFirstResultIsAlias(isAlias, expectedAlias) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "Should have the correct type"
+ );
+
+ if (isAlias) {
+ Assert.equal(
+ result.searchParams.keyword,
+ expectedAlias,
+ "Payload keyword should be the alias"
+ );
+ } else {
+ Assert.notEqual(
+ result.searchParams.keyword,
+ expectedAlias,
+ "Payload keyword should be absent or not the alias"
+ );
+ }
+}
+
+function assertHighlighted(highlighted, expectedAlias) {
+ let selection = gURLBar.editor.selectionController.getSelection(
+ Ci.nsISelectionController.SELECTION_FIND
+ );
+ Assert.ok(selection);
+ if (!highlighted) {
+ Assert.equal(selection.rangeCount, 0);
+ return;
+ }
+ Assert.equal(selection.rangeCount, 1);
+ let index = gURLBar.value.indexOf(expectedAlias);
+ Assert.ok(
+ index >= 0,
+ `gURLBar.value="${gURLBar.value}" expectedAlias="${expectedAlias}"`
+ );
+ let range = selection.getRangeAt(0);
+ Assert.ok(range);
+ Assert.equal(range.startOffset, index);
+ Assert.equal(range.endOffset, index + expectedAlias.length);
+}
+
+/**
+ * Returns an array of code points in the given string. Each code point is
+ * returned as a hexidecimal string.
+ *
+ * @param {string} str
+ * The code points of this string will be returned.
+ * @returns {Array}
+ * Array of code points in the string, where each is a hexidecimal string.
+ */
+function codePoints(str) {
+ return str.split("").map(s => s.charCodeAt(0).toString(16));
+}
diff --git a/browser/components/urlbar/tests/browser/browser_top_sites.js b/browser/components/urlbar/tests/browser/browser_top_sites.js
new file mode 100644
index 0000000000..cb6502d2b2
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_top_sites.js
@@ -0,0 +1,481 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AboutNewTab: "resource:///modules/AboutNewTab.jsm",
+});
+
+const EN_US_TOPSITES =
+ "https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://twitter.com/";
+
+async function addTestVisits() {
+ // Add some visits to a URL.
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits("http://example.com/");
+ }
+
+ // Wait for example.com to be listed first.
+ await updateTopSites(sites => {
+ return sites && sites[0] && sites[0].url == "http://example.com/";
+ });
+
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "https://www.youtube.com/",
+ title: "YouTube",
+ });
+}
+
+async function checkDoesNotOpenOnFocus(win = window) {
+ // The view should not open when the input is focused programmatically.
+ win.gURLBar.blur();
+ win.gURLBar.focus();
+ Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open");
+ win.gURLBar.blur();
+
+ // Check the keyboard shortcut.
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ // Because the panel opening may not be immediate, we must wait a bit.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 500));
+ Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open");
+ win.gURLBar.blur();
+
+ // Focus with the mouse.
+ EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {}, win);
+ // Because the panel opening may not be immediate, we must wait a bit.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 500));
+ Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open");
+ win.gURLBar.blur();
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.topsites", true],
+ ["browser.urlbar.suggest.quickactions", false],
+ ["browser.newtabpage.activity-stream.default.sites", EN_US_TOPSITES],
+ ],
+ });
+
+ await updateTopSites(
+ sites => sites && sites.length == EN_US_TOPSITES.split(",").length
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://example.com/"
+ );
+
+ registerCleanupFunction(() => {
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(async function topSitesShown() {
+ let sites = AboutNewTab.getTopSites();
+
+ for (let prefVal of [true, false]) {
+ // This test should work regardless of whether Top Sites are enabled on
+ // about:newtab.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.newtabpage.activity-stream.feeds.topsites", prefVal]],
+ });
+ // We don't expect this to change, but we run updateTopSites just in case
+ // feeds.topsites were to have an effect on the composition of Top Sites.
+ await updateTopSites(siteList => siteList.length == 6);
+
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ if (gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ Assert.ok(gURLBar.view.isOpen, "UrlbarView should be open.");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ sites.length,
+ "The number of results should be the same as the number of Top Sites (6)."
+ );
+
+ for (let i = 0; i < sites.length; i++) {
+ let site = sites[i];
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ if (site.searchTopSite) {
+ Assert.equal(
+ result.searchParams.keyword,
+ site.label,
+ "The search Top Site should have an alias."
+ );
+ continue;
+ }
+
+ Assert.equal(
+ site.url,
+ result.url,
+ "The Top Site URL and the result URL shoud match."
+ );
+ Assert.equal(
+ site.label || site.title || site.hostname,
+ result.title,
+ "The Top Site title and the result title shoud match."
+ );
+ }
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ gURLBar.blur();
+ });
+ // This pops updateTopSites changes.
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.popPrefEnv();
+ }
+});
+
+add_task(async function selectSearchTopSite() {
+ await updateTopSites(
+ sites => sites && sites[0] && sites[0].searchTopSite,
+ true
+ );
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ if (gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ let amazonSearch = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ 0
+ );
+
+ Assert.equal(
+ amazonSearch.result.type,
+ UrlbarUtils.RESULT_TYPE.SEARCH,
+ "First result should have SEARCH type."
+ );
+
+ Assert.equal(
+ amazonSearch.result.payload.keyword,
+ "@amazon",
+ "First result should have the Amazon keyword."
+ );
+
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(amazonSearch, {});
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: amazonSearch.result.payload.engine,
+ entry: "topsites_urlbar",
+ });
+ await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ gURLBar.blur();
+ });
+});
+
+add_task(async function topSitesBookmarksAndTabs() {
+ await addTestVisits();
+ let sites = AboutNewTab.getTopSites();
+ Assert.equal(
+ sites.length,
+ 7,
+ "The test suite browser should have 7 Top Sites."
+ );
+
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ if (gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ Assert.ok(gURLBar.view.isOpen, "UrlbarView should be open.");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 7,
+ "The number of results should be the same as the number of Top Sites (7)."
+ );
+
+ let exampleResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ exampleResult.url,
+ "http://example.com/",
+ "The example.com Top Site should be the first result."
+ );
+ Assert.equal(
+ exampleResult.source,
+ UrlbarUtils.RESULT_SOURCE.TABS,
+ "The example.com Top Site should appear in the view as an open tab result."
+ );
+
+ let youtubeResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ youtubeResult.url,
+ "https://www.youtube.com/",
+ "The YouTube Top Site should be the second result."
+ );
+ Assert.equal(
+ youtubeResult.source,
+ UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ "The YouTube Top Site should appear in the view as a bookmark result."
+ );
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ gURLBar.blur();
+ });
+
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function topSitesKeywordNavigationPageproxystate() {
+ await addTestVisits();
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "valid",
+ "Sanity check initial state"
+ );
+
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ if (gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ Assert.ok(gURLBar.view.isOpen, "UrlbarView should be open.");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ let count = UrlbarTestUtils.getResultCount(window);
+ Assert.equal(count, 7, "The number of results should be the expected one.");
+
+ for (let i = 0; i < count; ++i) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "invalid",
+ "Moving through results"
+ );
+ }
+ for (let i = 0; i < count; ++i) {
+ EventUtils.synthesizeKey("KEY_ArrowUp");
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "invalid",
+ "Moving through results"
+ );
+ }
+
+ // Double ESC should restore state.
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_Escape");
+ });
+ EventUtils.synthesizeKey("KEY_Escape");
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "valid",
+ "Double ESC should restore state"
+ );
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function topSitesPinned() {
+ await addTestVisits();
+ let info = { url: "http://example.com/" };
+ NewTabUtils.pinnedLinks.pin(info, 0);
+
+ await updateTopSites(sites => sites && sites[0] && sites[0].isPinned);
+
+ let sites = AboutNewTab.getTopSites();
+ Assert.equal(
+ sites.length,
+ 7,
+ "The test suite browser should have 7 Top Sites."
+ );
+
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ Assert.ok(gURLBar.view.isOpen, "UrlbarView should be open.");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 7,
+ "The number of results should be the same as the number of Top Sites (7)."
+ );
+
+ let exampleResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ exampleResult.url,
+ "http://example.com/",
+ "The example.com Top Site should be the first result."
+ );
+
+ Assert.equal(
+ exampleResult.source,
+ UrlbarUtils.RESULT_SOURCE.TABS,
+ "The example.com Top Site should be an open tab result."
+ );
+
+ Assert.ok(
+ exampleResult.element.row.hasAttribute("pinned"),
+ "The example.com Top Site should have the pinned property."
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ gURLBar.blur();
+ });
+ NewTabUtils.pinnedLinks.unpin(info);
+
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function topSitesBookmarksAndTabsDisabled() {
+ await addTestVisits();
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.openpage", false],
+ ["browser.urlbar.suggest.bookmark", false],
+ ],
+ });
+
+ let sites = AboutNewTab.getTopSites();
+ Assert.equal(
+ sites.length,
+ 7,
+ "The test suite browser should have 7 Top Sites."
+ );
+
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ if (gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ Assert.ok(gURLBar.view.isOpen, "UrlbarView should be open.");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 7,
+ "The number of results should be the same as the number of Top Sites (7)."
+ );
+
+ let exampleResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(
+ exampleResult.url,
+ "http://example.com/",
+ "The example.com Top Site should be the second result."
+ );
+ Assert.equal(
+ exampleResult.source,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ "The example.com Top Site should appear as a normal result even though it's open in a tab."
+ );
+
+ let youtubeResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ Assert.equal(
+ youtubeResult.url,
+ "https://www.youtube.com/",
+ "The YouTube Top Site should be the third result."
+ );
+ Assert.equal(
+ youtubeResult.source,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ "The YouTube Top Site should appear as a normal result even though it's bookmarked."
+ );
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ gURLBar.blur();
+ });
+
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function topSitesDisabled() {
+ // Disable Top Sites feed.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.newtabpage.activity-stream.feeds.system.topsites", false]],
+ });
+ await checkDoesNotOpenOnFocus();
+ await SpecialPowers.popPrefEnv();
+
+ // Top Sites should also not be shown when Urlbar Top Sites are disabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.topsites", false]],
+ });
+ await checkDoesNotOpenOnFocus();
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function topSitesNumber() {
+ // Add some visits
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits([
+ "http://example-a.com/",
+ "http://example-b.com/",
+ "http://example-c.com/",
+ "http://example-d.com/",
+ "http://example-e.com/",
+ ]);
+ }
+
+ // Wait for the expected number of Top sites.
+ await updateTopSites(sites => sites && sites.length == 8);
+ Assert.equal(
+ AboutNewTab.getTopSites().length,
+ 8,
+ "The test suite browser should have 8 Top Sites."
+ );
+
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ Assert.ok(gURLBar.view.isOpen, "UrlbarView should be open.");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 8,
+ "The number of results should be the default (8)."
+ );
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.newtabpage.activity-stream.topSitesRows", 2]],
+ });
+ // Wait for the expected number of Top sites.
+ await updateTopSites(sites => sites && sites.length == 11);
+ Assert.equal(
+ AboutNewTab.getTopSites().length,
+ 11,
+ "The test suite browser should have 11 Top Sites."
+ );
+
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ Assert.ok(gURLBar.view.isOpen, "UrlbarView should be open.");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 10,
+ "The number of results should be maxRichResults (10)."
+ );
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ await SpecialPowers.popPrefEnv();
+ await PlacesUtils.history.clear();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_top_sites_private.js b/browser/components/urlbar/tests/browser/browser_top_sites_private.js
new file mode 100644
index 0000000000..bcc6a70d88
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_top_sites_private.js
@@ -0,0 +1,174 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AboutNewTab: "resource:///modules/AboutNewTab.jsm",
+});
+
+const EN_US_TOPSITES =
+ "https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://twitter.com/";
+
+async function addTestVisits() {
+ // Add some visits to a URL.
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits("http://example.com/");
+ }
+
+ // Wait for example.com to be listed first.
+ await updateTopSites(sites => {
+ return sites && sites[0] && sites[0].url == "http://example.com/";
+ });
+
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "https://www.youtube.com/",
+ title: "YouTube",
+ });
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.topsites", true],
+ ["browser.urlbar.suggest.quickactions", false],
+ ["browser.newtabpage.activity-stream.default.sites", EN_US_TOPSITES],
+ ],
+ });
+
+ await updateTopSites(
+ sites => sites && sites.length == EN_US_TOPSITES.split(",").length
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://example.com/"
+ );
+
+ registerCleanupFunction(() => {
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(async function topSitesPrivateWindow() {
+ // Top Sites should also be shown in private windows.
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ await addTestVisits();
+ let sites = AboutNewTab.getTopSites();
+ Assert.equal(
+ sites.length,
+ 7,
+ "The test suite browser should have 7 Top Sites."
+ );
+ let urlbar = privateWin.gURLBar;
+ await UrlbarTestUtils.promisePopupOpen(privateWin, () => {
+ if (urlbar.getAttribute("pageproxystate") == "invalid") {
+ urlbar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(urlbar.inputField, {}, privateWin);
+ });
+ Assert.ok(urlbar.view.isOpen, "UrlbarView should be open.");
+ await UrlbarTestUtils.promiseSearchComplete(privateWin);
+
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(privateWin),
+ 7,
+ "The number of results should be the same as the number of Top Sites (7)."
+ );
+
+ // Top sites should also be shown in a private window if the search string
+ // gets cleared.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: privateWin,
+ value: "example",
+ });
+ urlbar.select();
+ EventUtils.synthesizeKey("KEY_Backspace", {}, privateWin);
+ Assert.ok(urlbar.view.isOpen, "UrlbarView should be open.");
+ await UrlbarTestUtils.promiseSearchComplete(privateWin);
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(privateWin),
+ 7,
+ "The number of results should be the same as the number of Top Sites (7)."
+ );
+
+ await BrowserTestUtils.closeWindow(privateWin);
+
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ gURLBar.blur();
+ });
+});
+
+add_task(async function topSitesTabSwitch() {
+ // Add some visits
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits(["http://example.com/"]);
+ }
+
+ // Switch to the originating tab, to check for switch to the current tab.
+ gBrowser.selectedTab = gBrowser.tabs[0];
+
+ // Wait for the expected number of Top sites.
+ await updateTopSites(sites => sites?.length == 7);
+ Assert.equal(
+ AboutNewTab.getTopSites().length,
+ 7,
+ "The test suite browser should have 7 Top Sites."
+ );
+
+ async function checkResults(win, expectedResultType) {
+ let resultCount = UrlbarTestUtils.getResultCount(win);
+ let result;
+ for (let i = 0; i < resultCount; ++i) {
+ result = await UrlbarTestUtils.getDetailsOfResultAt(win, i);
+ if (result.url == "http://example.com/") {
+ break;
+ }
+ }
+ Assert.equal(
+ result.type,
+ expectedResultType,
+ `Should provide a result of type ${expectedResultType}.`
+ );
+ }
+
+ info("Test in a non-private window");
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ Assert.ok(gURLBar.view.isOpen, "UrlbarView should be open.");
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ await checkResults(window, UrlbarUtils.RESULT_TYPE.TAB_SWITCH);
+ await UrlbarTestUtils.promisePopupClose(window);
+
+ info("Test in a private window, switch to tab should not be offered");
+ // Top Sites should also be shown in private windows.
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+
+ let urlbar = privateWin.gURLBar;
+ await UrlbarTestUtils.promisePopupOpen(privateWin, () => {
+ if (urlbar.getAttribute("pageproxystate") == "invalid") {
+ urlbar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(urlbar.inputField, {}, privateWin);
+ });
+
+ Assert.ok(urlbar.view.isOpen, "UrlbarView should be open.");
+ await UrlbarTestUtils.promiseSearchComplete(privateWin);
+ await checkResults(privateWin, UrlbarUtils.RESULT_TYPE.URL);
+ await UrlbarTestUtils.promisePopupClose(privateWin);
+ await BrowserTestUtils.closeWindow(privateWin);
+
+ await PlacesUtils.history.clear();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_typed_value.js b/browser/components/urlbar/tests/browser/browser_typed_value.js
new file mode 100644
index 0000000000..ca4566d172
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_typed_value.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test ensures that the urlbar is restored to the typed value on blur.
+
+"use strict";
+
+add_setup(async function () {
+ registerCleanupFunction(async function () {
+ Services.prefs.clearUserPref("browser.urlbar.autoFill");
+ Services.prefs.clearUserPref("browser.urlbar.suggest.quickactions");
+ gURLBar.handleRevert();
+ await PlacesUtils.history.clear();
+ });
+ Services.prefs.setBoolPref("browser.urlbar.autoFill", true);
+ Services.prefs.setBoolPref("browser.urlbar.suggest.quickactions", false);
+
+ await PlacesTestUtils.addVisits([
+ "http://example.com/",
+ "http://example.com/foo",
+ ]);
+});
+
+add_task(async function test_autofill() {
+ let typed = "ex";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typed,
+ fireInputEvent: true,
+ });
+ Assert.equal(gURLBar.value, "example.com/", "autofilled value as expected");
+ Assert.equal(gURLBar.selectionStart, typed.length);
+ Assert.equal(gURLBar.selectionEnd, gURLBar.value.length);
+
+ gURLBar.blur();
+ Assert.equal(gURLBar.value, typed, "Value should have been restored");
+ gURLBar.focus();
+ Assert.equal(gURLBar.value, typed, "Value should not have changed");
+ Assert.equal(gURLBar.selectionStart, typed.length);
+ Assert.equal(gURLBar.selectionEnd, typed.length);
+});
+
+add_task(async function test_complete_selection() {
+ let typed = "ex";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: typed,
+ fireInputEvent: true,
+ });
+ Assert.equal(gURLBar.value, "example.com/", "autofilled value as expected");
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 2,
+ "Should have the correct number of matches"
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(
+ gURLBar.value,
+ "example.com/foo",
+ "Value should have been completed"
+ );
+
+ gURLBar.blur();
+ Assert.equal(gURLBar.value, typed, "Value should have been restored");
+ gURLBar.focus();
+ Assert.equal(gURLBar.value, typed, "Value should not have changed");
+ Assert.equal(gURLBar.selectionStart, typed.length);
+ Assert.equal(gURLBar.selectionEnd, typed.length);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_unitConversion.js b/browser/components/urlbar/tests/browser/browser_unitConversion.js
new file mode 100644
index 0000000000..566300b7d4
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_unitConversion.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests unit conversion on browser.
+ */
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.unitConversion.enabled", true]],
+ });
+
+ registerCleanupFunction(function () {
+ SpecialPowers.clipboardCopyString("");
+ });
+});
+
+add_task(async function test_selectByMouse() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ // Clear clipboard content.
+ SpecialPowers.clipboardCopyString("");
+
+ const row = await doUnitConversion(win);
+
+ info("Check if the result is copied to clipboard when selecting by mouse");
+ EventUtils.synthesizeMouseAtCenter(
+ row.querySelector(".urlbarView-no-wrap"),
+ {},
+ win
+ );
+ assertClipboard();
+
+ await UrlbarTestUtils.promisePopupClose(win);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function test_selectByKey() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ // Clear clipboard content.
+ SpecialPowers.clipboardCopyString("");
+
+ await doUnitConversion(win);
+
+ // As gURLBar might lost focus,
+ // give focus again in order to enable key event on the result.
+ win.gURLBar.focus();
+
+ info("Check if the result is copied to clipboard when selecting by key");
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+ assertClipboard();
+
+ await UrlbarTestUtils.promisePopupClose(win);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+function assertClipboard() {
+ Assert.equal(
+ SpecialPowers.getClipboardData("text/plain"),
+ "100 cm",
+ "The result of conversion is copied to clipboard"
+ );
+}
+
+async function doUnitConversion(win) {
+ info("Do unit conversion then wait the result");
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "1m to cm",
+ waitForFocus: SimpleTest.waitForFocus,
+ });
+
+ const row = await UrlbarTestUtils.waitForAutocompleteResultAt(win, 1);
+
+ Assert.ok(row.querySelector(".urlbarView-favicon"), "The icon is displayed");
+ Assert.equal(
+ row.querySelector(".urlbarView-dynamic-unitConversion-output").textContent,
+ "100 cm",
+ "The unit is converted"
+ );
+
+ return row;
+}
diff --git a/browser/components/urlbar/tests/browser/browser_updateForDomainCompletion.js b/browser/components/urlbar/tests/browser/browser_updateForDomainCompletion.js
new file mode 100644
index 0000000000..4f34b5d52a
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_updateForDomainCompletion.js
@@ -0,0 +1,51 @@
+"use strict";
+
+/**
+ * Disable keyword.enabled (so no keyword search), and check that when
+ * you type in "example" and hit enter, the browser shows an error page.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["keyword.enabled", false]],
+ });
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ gURLBar.value = "example";
+ gURLBar.select();
+ const loadPromise = BrowserTestUtils.waitForErrorPage(browser);
+ EventUtils.sendKey("return");
+ await loadPromise;
+ ok(true, "error page is loaded correctly");
+ }
+ );
+});
+
+/**
+ * Disable keyword.enabled (so no keyword search) and enable fixup.alternate, and check
+ * that when you type in "example" and hit enter, the browser loads and the URL bar
+ * is updated accordingly.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["keyword.enabled", false],
+ ["browser.fixup.alternate.enabled", true],
+ ],
+ });
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ gURLBar.value = "example";
+ gURLBar.select();
+ const loadPromise = BrowserTestUtils.waitForDocLoadAndStopIt(
+ "https://www.example.com/",
+ gBrowser.selectedBrowser
+ );
+
+ EventUtils.sendKey("return");
+ await loadPromise;
+ ok(true, "https://www.example.com is loaded correctly");
+ }
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_annotation.js b/browser/components/urlbar/tests/browser/browser_urlbar_annotation.js
new file mode 100644
index 0000000000..9f736ea6af
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_annotation.js
@@ -0,0 +1,333 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test whether a visit information is annotated correctly when picking a result.
+
+if (AppConstants.platform === "macosx") {
+ requestLongerTimeout(2);
+}
+
+const FRECENCY = {
+ ORGANIC: 2000,
+ SPONSORED: -1,
+ BOOKMARKED: 2075,
+ SEARCHED: 100,
+};
+
+const {
+ VISIT_SOURCE_ORGANIC,
+ VISIT_SOURCE_SPONSORED,
+ VISIT_SOURCE_BOOKMARKED,
+ VISIT_SOURCE_SEARCHED,
+} = PlacesUtils.history;
+
+/**
+ * To be used before checking database contents when they depend on a visit
+ * being added to History.
+ *
+ * @param {string} href the page to await notifications for.
+ */
+async function waitForVisitNotification(href) {
+ await PlacesTestUtils.waitForNotification("page-visited", events =>
+ events.some(e => e.url === href)
+ );
+}
+
+async function assertDatabase({ targetURL, expected }) {
+ const frecency = await PlacesTestUtils.getDatabaseValue(
+ "moz_places",
+ "frecency",
+ { url: targetURL }
+ );
+ Assert.equal(frecency, expected.frecency, "Frecency is correct");
+
+ const placesId = await PlacesTestUtils.getDatabaseValue("moz_places", "id", {
+ url: targetURL,
+ });
+ const expectedTriggeringPlaceId = expected.triggerURL
+ ? await PlacesTestUtils.getDatabaseValue("moz_places", "id", {
+ url: expected.triggerURL,
+ })
+ : null;
+ const db = await PlacesUtils.promiseDBConnection();
+ const rows = await db.execute(
+ "SELECT source, triggeringPlaceId FROM moz_historyvisits WHERE place_id = :place_id AND source = :source",
+ {
+ place_id: placesId,
+ source: expected.source,
+ }
+ );
+ Assert.equal(rows.length, 1);
+ Assert.equal(
+ rows[0].getResultByName("triggeringPlaceId"),
+ expectedTriggeringPlaceId,
+ `The triggeringPlaceId in database is correct for ${targetURL}`
+ );
+}
+
+function registerProvider(payload) {
+ const provider = new UrlbarTestUtils.TestProvider({
+ results: [
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.SEARCH,
+ ...UrlbarResult.payloadAndSimpleHighlights([], {
+ ...payload,
+ })
+ ),
+ ],
+ priority: Infinity,
+ });
+ UrlbarProvidersManager.registerProvider(provider);
+ return provider;
+}
+
+async function pickResult({ input, payloadURL, redirectTo }) {
+ const destinationURL = redirectTo || payloadURL;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: input,
+ fireInputEvent: true,
+ });
+
+ const result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.equal(result.url, payloadURL);
+ UrlbarTestUtils.setSelectedRowIndex(window, 0);
+
+ info("Show result and wait for loading");
+ const onLoad = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ destinationURL
+ );
+ EventUtils.synthesizeKey("KEY_Enter");
+ await onLoad;
+}
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ registerCleanupFunction(async () => {
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ });
+});
+
+add_task(async function basic() {
+ const testData = [
+ {
+ description: "Sponsored result",
+ input: "exa",
+ payload: {
+ url: "http://example.com/",
+ isSponsored: true,
+ },
+ expected: {
+ source: VISIT_SOURCE_SPONSORED,
+ frecency: FRECENCY.SPONSORED,
+ },
+ },
+ {
+ description: "Bookmarked result",
+ input: "exa",
+ payload: {
+ url: "http://example.com/",
+ },
+ bookmarks: [
+ {
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: Services.io.newURI("http://example.com/"),
+ title: "test bookmark",
+ },
+ ],
+ expected: {
+ source: VISIT_SOURCE_BOOKMARKED,
+ frecency: FRECENCY.BOOKMARKED,
+ },
+ },
+ {
+ description: "Sponsored and bookmarked result",
+ input: "exa",
+ payload: {
+ url: "http://example.com/",
+ isSponsored: true,
+ },
+ bookmarks: [
+ {
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: Services.io.newURI("http://example.com/"),
+ title: "test bookmark",
+ },
+ ],
+ expected: {
+ source: VISIT_SOURCE_SPONSORED,
+ frecency: FRECENCY.BOOKMARKED,
+ },
+ },
+ {
+ description: "Organic result",
+ input: "exa",
+ payload: {
+ url: "http://example.com/",
+ },
+ expected: {
+ source: VISIT_SOURCE_ORGANIC,
+ frecency: FRECENCY.ORGANIC,
+ },
+ },
+ ];
+
+ for (const { description, input, payload, bookmarks, expected } of testData) {
+ info(description);
+ const provider = registerProvider(payload);
+
+ for (const bookmark of bookmarks || []) {
+ await PlacesUtils.bookmarks.insert(bookmark);
+ }
+
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ info("Pick result");
+ let promiseVisited = waitForVisitNotification(payload.url);
+ await pickResult({ input, payloadURL: payload.url });
+ await promiseVisited;
+ info("Check database");
+ await assertDatabase({ targetURL: payload.url, expected });
+ });
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ }
+});
+
+add_task(async function redirection() {
+ const redirectTo = "http://example.com/";
+ const payload = {
+ url: "http://example.com/browser/browser/components/urlbar/tests/browser/redirect_to.sjs?/",
+ isSponsored: true,
+ };
+ const input = "exa";
+ const provider = registerProvider(payload);
+
+ await BrowserTestUtils.withNewTab("about:home", async () => {
+ info("Pick result");
+ let promises = [
+ waitForVisitNotification(payload.url),
+ waitForVisitNotification(redirectTo),
+ ];
+ await pickResult({ input, payloadURL: payload.url, redirectTo });
+ await Promise.all(promises);
+
+ info("Check database");
+ await assertDatabase({
+ targetURL: payload.url,
+ expected: {
+ source: VISIT_SOURCE_SPONSORED,
+ frecency: FRECENCY.SPONSORED,
+ },
+ });
+ await assertDatabase({
+ targetURL: redirectTo,
+ expected: {
+ source: VISIT_SOURCE_SPONSORED,
+ triggerURL: payload.url,
+ frecency: FRECENCY.SPONSORED,
+ },
+ });
+ });
+
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
+
+add_task(async function search() {
+ const originalDefaultEngine = await Services.search.getDefault();
+ await SearchTestUtils.installSearchExtension({
+ name: "test engine",
+ keyword: "@test",
+ });
+
+ const testData = [
+ {
+ description: "Searched result",
+ input: "@test abc",
+ resultURL: "https://example.com/?q=abc",
+ expected: {
+ source: VISIT_SOURCE_SEARCHED,
+ frecency: FRECENCY.SEARCHED,
+ },
+ },
+ {
+ description: "Searched bookmarked result",
+ input: "@test abc",
+ resultURL: "https://example.com/?q=abc",
+ bookmarks: [
+ {
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: Services.io.newURI("https://example.com/?q=abc"),
+ title: "test bookmark",
+ },
+ ],
+ expected: {
+ source: VISIT_SOURCE_BOOKMARKED,
+ frecency: FRECENCY.BOOKMARKED,
+ },
+ },
+ ];
+
+ for (const {
+ description,
+ input,
+ resultURL,
+ bookmarks,
+ expected,
+ } of testData) {
+ info(description);
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ for (const bookmark of bookmarks || []) {
+ await PlacesUtils.bookmarks.insert(bookmark);
+ }
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: input,
+ });
+ const onLoad = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ resultURL
+ );
+ let promiseVisited = waitForVisitNotification(resultURL);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await onLoad;
+ await promiseVisited;
+ await assertDatabase({ targetURL: resultURL, expected });
+
+ // Open another URL to check whther the source is not inherited.
+ const payload = { url: "http://example.com/" };
+ const provider = registerProvider(payload);
+ promiseVisited = waitForVisitNotification(payload.url);
+ await pickResult({ input, payloadURL: payload.url });
+ await promiseVisited;
+ await assertDatabase({
+ targetURL: payload.url,
+ expected: {
+ source: VISIT_SOURCE_ORGANIC,
+ frecency: FRECENCY.ORGANIC,
+ },
+ });
+ UrlbarProvidersManager.unregisterProvider(provider);
+
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ });
+ }
+
+ await Services.search.setDefault(
+ originalDefaultEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry_abandonment.js b/browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry_abandonment.js
new file mode 100644
index 0000000000..6f30392e48
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry_abandonment.js
@@ -0,0 +1,357 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
+});
+
+const TEST_ENGINE_NAME = "Test";
+const TEST_ENGINE_ALIAS = "@test";
+const TEST_ENGINE_DOMAIN = "example.com";
+
+// Each test is a function that executes an urlbar action and returns the
+// expected event object.
+const tests = [
+ async function (win) {
+ info("Type something, blur.");
+ win.gURLBar.select();
+ EventUtils.synthesizeKey("x", {}, win);
+ win.gURLBar.blur();
+ return {
+ category: "urlbar",
+ method: "abandonment",
+ object: "blur",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "1",
+ numWords: "1",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Open the panel with DOWN, don't type, blur it.");
+ await addTopSite("http://example.org/");
+ win.gURLBar.value = "";
+ win.gURLBar.select();
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ });
+ win.gURLBar.blur();
+ return {
+ category: "urlbar",
+ method: "abandonment",
+ object: "blur",
+ value: "topsites",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "0",
+ numWords: "0",
+ },
+ };
+ },
+
+ async function (win) {
+ info("With pageproxystate=valid, autoopen the panel, don't type, blur it.");
+ win.gURLBar.value = "";
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ win.gURLBar.blur();
+ return {
+ category: "urlbar",
+ method: "abandonment",
+ object: "blur",
+ value: "topsites",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "0",
+ numWords: "0",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Enter search mode from Top Sites.");
+ await updateTopSites(sites => true, /* enableSearchShorcuts */ true);
+
+ win.gURLBar.value = "";
+ win.gURLBar.select();
+
+ await BrowserTestUtils.waitForCondition(async () => {
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ });
+
+ if (UrlbarTestUtils.getResultCount(win) > 1) {
+ return true;
+ }
+
+ win.gURLBar.view.close();
+ return false;
+ });
+
+ while (win.gURLBar.searchMode?.engineName != "Google") {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ }
+
+ let element = UrlbarTestUtils.getSelectedRow(win);
+ Assert.ok(
+ element.result.source == UrlbarUtils.RESULT_SOURCE.SEARCH,
+ "The selected result is a search Top Site."
+ );
+
+ let engine = element.result.payload.engine;
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+ EventUtils.synthesizeMouseAtCenter(element, {}, win);
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(win, {
+ engineName: engine,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ entry: "topsites_urlbar",
+ });
+
+ await UrlbarTestUtils.exitSearchMode(win);
+
+ // To avoid needing to add a custom search shortcut Top Site, we just
+ // abandon this interaction.
+ await UrlbarTestUtils.promisePopupClose(win, () => {
+ win.gURLBar.blur();
+ });
+
+ return [
+ // engagement on the top sites search engine to enter search mode
+ {
+ category: "urlbar",
+ method: "engagement",
+ object: "click",
+ value: "topsites",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "0",
+ numWords: "0",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "UrlbarProviderTopSites",
+ },
+ },
+ // abandonment
+ {
+ category: "urlbar",
+ method: "abandonment",
+ object: "blur",
+ value: "topsites",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "0",
+ numWords: "0",
+ },
+ },
+ ];
+ },
+
+ async function (win) {
+ info("Open search mode from a tab-to-search result.");
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.tabToSearch.onboard.interactionsLeft", 0]],
+ });
+
+ await PlacesUtils.history.clear();
+ for (let i = 0; i < 3; i++) {
+ await PlacesTestUtils.addVisits([`https://${TEST_ENGINE_DOMAIN}/`]);
+ }
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ });
+
+ let tabToSearchResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(win, 1)
+ ).result;
+ Assert.equal(
+ tabToSearchResult.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+
+ // Select the tab-to-search result.
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(win, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "tabtosearch",
+ });
+
+ // Abandon the interaction since simply entering search mode is not
+ // considered the end of an engagement.
+ await UrlbarTestUtils.promisePopupClose(win, () => {
+ win.gURLBar.blur();
+ });
+
+ await PlacesUtils.history.clear();
+ await SpecialPowers.popPrefEnv();
+
+ return [
+ // engagement on the tab-to-search to enter search mode
+ {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "4",
+ numWords: "1",
+ selIndex: "1",
+ selType: "tabtosearch",
+ provider: "TabToSearch",
+ },
+ },
+ // abandonment
+ {
+ category: "urlbar",
+ method: "abandonment",
+ object: "blur",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "0",
+ numWords: "0",
+ },
+ },
+ ];
+ },
+
+ async function (win) {
+ info(
+ "With pageproxystate=invalid, open retained results, don't type, blur it."
+ );
+ win.gURLBar.value = "mochi.test";
+ win.gURLBar.setPageProxyState("invalid");
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ win.gURLBar.blur();
+ return {
+ category: "urlbar",
+ method: "abandonment",
+ object: "blur",
+ value: "returned",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "10",
+ numWords: "1",
+ },
+ };
+ },
+];
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+
+ // Create a new search engine and mark it as default
+ let engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ setAsDefault: true,
+ });
+ await Services.search.moveEngine(engine, 0);
+
+ await SearchTestUtils.installSearchExtension({
+ name: TEST_ENGINE_NAME,
+ keyword: TEST_ENGINE_ALIAS,
+ search_url: `https://${TEST_ENGINE_DOMAIN}/`,
+ });
+
+ // This test used to rely on the initial timer of
+ // TestUtils.waitForCondition. See bug 1667216.
+ let originalWaitForCondition = TestUtils.waitForCondition;
+ TestUtils.waitForCondition = async function (
+ condition,
+ msg,
+ interval = 100,
+ maxTries = 50
+ ) {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ return originalWaitForCondition(condition, msg, interval, maxTries);
+ };
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ TestUtils.waitForCondition = originalWaitForCondition;
+ });
+});
+
+async function doTest(eventTelemetryEnabled) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.eventTelemetry.enabled", eventTelemetryEnabled]],
+ });
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ // This is not necessary after each loop, because assertEvents does it.
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+
+ for (let i = 0; i < tests.length; i++) {
+ info(`Running test at index ${i}`);
+ let events = await tests[i](win);
+ if (!Array.isArray(events)) {
+ events = [events];
+ }
+ // Always blur to ensure it's not accounted as an additional abandonment.
+ win.gURLBar.setSearchMode({});
+ win.gURLBar.blur();
+ TelemetryTestUtils.assertEvents(eventTelemetryEnabled ? events : [], {
+ category: "urlbar",
+ });
+
+ // Scalars should be recorded regardless of `eventTelemetry.enabled`.
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(
+ scalars,
+ "urlbar.engagement",
+ events.filter(e => e.method == "engagement").length || undefined
+ );
+ TelemetryTestUtils.assertScalar(
+ scalars,
+ "urlbar.abandonment",
+ events.filter(e => e.method == "abandonment").length || undefined
+ );
+
+ await UrlbarTestUtils.formHistory.clear(win);
+ }
+
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+}
+
+add_task(async function enabled() {
+ await doTest(true);
+});
+
+add_task(async function disabled() {
+ await doTest(false);
+});
+
+/**
+ * Replaces the contents of Top Sites with the specified site.
+ *
+ * @param {string} site
+ * A site to add to Top Sites.
+ */
+async function addTopSite(site) {
+ await PlacesUtils.history.clear();
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits(site);
+ }
+
+ await updateTopSites(sites => sites && sites[0] && sites[0].url == site);
+}
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry_engagement.js b/browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry_engagement.js
new file mode 100644
index 0000000000..c1fd36b452
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry_engagement.js
@@ -0,0 +1,1340 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarProvider: "resource:///modules/UrlbarUtils.sys.mjs",
+ UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.sys.mjs",
+ UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
+});
+
+const TEST_ENGINE_NAME = "Test";
+const TEST_ENGINE_ALIAS = "@test";
+const TEST_ENGINE_DOMAIN = "example.com";
+
+// This test has many subtests and can time out in verify mode.
+requestLongerTimeout(5);
+
+// Each test is a function that executes an urlbar action and returns the
+// expected event object.
+const tests = [
+ async function (win) {
+ info("Type something, press Enter.");
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "x",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "1",
+ numWords: "1",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "HeuristicFallback",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Type a multi-word query, press Enter.");
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "multi word query ",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "17",
+ numWords: "3",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "HeuristicFallback",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Paste something, press Enter.");
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await SimpleTest.promiseClipboardChange("test", () => {
+ clipboardHelper.copyString("test");
+ });
+ win.document.commandDispatcher
+ .getControllerForCommand("cmd_paste")
+ .doCommand("cmd_paste");
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "pasted",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "4",
+ numWords: "1",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "HeuristicFallback",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Type something, click one-off and press enter.");
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "test",
+ fireInputEvent: true,
+ });
+
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true }, win);
+ let selectedOneOff =
+ UrlbarTestUtils.getOneOffSearchButtons(win).selectedButton;
+ selectedOneOff.click();
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(win, {
+ engineName: selectedOneOff.engine.name,
+ entry: "oneoff",
+ });
+
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "4",
+ numWords: "1",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "HeuristicFallback",
+ },
+ };
+ },
+
+ async function (win) {
+ info(
+ "Type something, select one-off with enter, and select result with enter."
+ );
+ win.gURLBar.select();
+
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "test",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true }, win);
+ let selectedOneOff =
+ UrlbarTestUtils.getOneOffSearchButtons(win).selectedButton;
+ Assert.ok(selectedOneOff);
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await searchPromise;
+
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "4",
+ numWords: "1",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "HeuristicFallback",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Type something, ESC, type something else, press Enter.");
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("x", {}, win);
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+ EventUtils.synthesizeKey("y", {}, win);
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "1",
+ numWords: "1",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "HeuristicFallback",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Type a keyword, Enter.");
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "kw test",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "7",
+ numWords: "2",
+ selIndex: "0",
+ selType: "keyword",
+ provider: "BookmarkKeywords",
+ },
+ };
+ },
+
+ async function (win) {
+ let tipProvider = registerTipProvider();
+ info("Selecting a tip's main button, enter.");
+ win.gURLBar.search("x");
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ unregisterTipProvider(tipProvider);
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "1",
+ numWords: "1",
+ selIndex: "1",
+ selType: "tip",
+ provider: tipProvider.name,
+ },
+ };
+ },
+
+ async function (win) {
+ let tipProvider = registerTipProvider();
+ info("Selecting a tip's help option.");
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ win.gURLBar.search("x");
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ if (UrlbarPrefs.get("resultMenu")) {
+ await UrlbarTestUtils.openResultMenuAndPressAccesskey(win, "h");
+ } else {
+ EventUtils.synthesizeKey("KEY_Tab", {}, win);
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ }
+ await promise;
+ unregisterTipProvider(tipProvider);
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "1",
+ numWords: "1",
+ selIndex: "1",
+ selType: "tiphelp",
+ provider: tipProvider.name,
+ },
+ };
+ },
+
+ async function (win) {
+ info("Type something and canonize");
+ win.gURLBar.select();
+ const promise = BrowserTestUtils.waitForDocLoadAndStopIt(
+ "https://www.example.com/",
+ win.gBrowser.selectedBrowser
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "example",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("VK_RETURN", { ctrlKey: true }, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "7",
+ numWords: "1",
+ selIndex: "0",
+ selType: "canonized",
+ provider: "Autofill",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Type something, click on bookmark entry.");
+ // Add a clean bookmark.
+ const bookmark = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/bookmark",
+ title: "bookmark",
+ });
+
+ win.gURLBar.select();
+ let url = "http://example.com/bookmark";
+ let promise = BrowserTestUtils.browserLoaded(
+ win.gBrowser.selectedBrowser,
+ false,
+ url
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "boo",
+ fireInputEvent: true,
+ });
+ while (win.gURLBar.untrimmedValue != url) {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ }
+ let element = UrlbarTestUtils.getSelectedRow(win);
+ EventUtils.synthesizeMouseAtCenter(element, {}, win);
+ await promise;
+ await PlacesUtils.bookmarks.remove(bookmark);
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "click",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "3",
+ numWords: "1",
+ selIndex: val => parseInt(val) > 0,
+ selType: "bookmark",
+ provider: "Places",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Type an autofilled string, Enter.");
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "exa",
+ fireInputEvent: true,
+ });
+ // Check it's autofilled.
+ Assert.equal(win.gURLBar.selectionStart, 3);
+ Assert.equal(win.gURLBar.selectionEnd, 12);
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "3",
+ numWords: "1",
+ selIndex: "0",
+ selType: "autofill_origin",
+ provider: "Autofill",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Type something, select bookmark entry, Enter.");
+
+ // Add a clean bookmark and the input history in order to detect in InputHistory
+ // provider and to not show adaptive history autofill.
+ const bookmark = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/bookmark",
+ title: "bookmark",
+ });
+ await UrlbarUtils.addToInputHistory(
+ "http://example.com/bookmark",
+ "bookmark"
+ );
+
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "boo",
+ fireInputEvent: true,
+ });
+ while (win.gURLBar.untrimmedValue != "http://example.com/bookmark") {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ }
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ await PlacesUtils.bookmarks.remove(bookmark);
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "3",
+ numWords: "1",
+ selIndex: val => parseInt(val) > 0,
+ selType: "bookmark",
+ provider: "InputHistory",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Type something, select remote search suggestion, Enter.");
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "foo",
+ fireInputEvent: true,
+ });
+ while (win.gURLBar.untrimmedValue != "foofoo") {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ }
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "3",
+ numWords: "1",
+ selIndex: val => parseInt(val) > 0,
+ selType: "searchsuggestion",
+ provider: "SearchSuggestions",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Type something, select form history, Enter.");
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.maxHistoricalSearchSuggestions", 2]],
+ });
+ await UrlbarTestUtils.formHistory.add(["foofoo", "foobar"]);
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "foo",
+ fireInputEvent: true,
+ });
+ while (win.gURLBar.untrimmedValue != "foofoo") {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ }
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ await SpecialPowers.popPrefEnv();
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "3",
+ numWords: "1",
+ selIndex: val => parseInt(val) > 0,
+ selType: "formhistory",
+ provider: "SearchSuggestions",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Type @, enter on a keywordoffer, then search and press enter.");
+ win.gURLBar.select();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "@",
+ fireInputEvent: true,
+ });
+
+ while (win.gURLBar.searchMode?.engineName != TEST_ENGINE_NAME) {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ }
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await UrlbarTestUtils.promiseSearchComplete(win);
+
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "moz",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+
+ return [
+ // engagement on the keyword offer result to enter search mode
+ {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "1",
+ numWords: "1",
+ selIndex: "6",
+ selType: "searchengine",
+ provider: "TokenAliasEngines",
+ },
+ },
+ // engagement on the search heuristic
+ {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "3",
+ numWords: "1",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "HeuristicFallback",
+ },
+ },
+ ];
+ },
+
+ async function (win) {
+ info("Type an @alias, then space, then search and press enter.");
+ const alias = "testalias";
+ await SearchTestUtils.installSearchExtension({
+ name: "AliasTest",
+ keyword: alias,
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: `${alias} `,
+ });
+
+ await UrlbarTestUtils.assertSearchMode(win, {
+ engineName: "AliasTest",
+ entry: "typed",
+ });
+
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "moz",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "3",
+ numWords: "1",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "HeuristicFallback",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Drop something.");
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ EventUtils.synthesizeDrop(
+ win.document.getElementById("back-button"),
+ win.gURLBar.inputField,
+ [[{ type: "text/plain", data: "www.example.com" }]],
+ "copy",
+ win
+ );
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "drop_go",
+ value: "dropped",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "15",
+ numWords: "1",
+ selIndex: "-1",
+ selType: "none",
+ provider: "",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Paste and Go something.");
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await SimpleTest.promiseClipboardChange("www.example.com", () => {
+ clipboardHelper.copyString("www.example.com");
+ });
+ let inputBox = win.gURLBar.querySelector("moz-input-box");
+ let cxmenu = inputBox.menupopup;
+ let cxmenuPromise = BrowserTestUtils.waitForEvent(cxmenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(
+ win.gURLBar.inputField,
+ {
+ type: "contextmenu",
+ button: 2,
+ },
+ win
+ );
+ await cxmenuPromise;
+ let menuitem = inputBox.getMenuItem("paste-and-go");
+ cxmenu.activateItem(menuitem);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "paste_go",
+ value: "pasted",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "15",
+ numWords: "1",
+ selIndex: "-1",
+ selType: "none",
+ provider: "",
+ },
+ };
+ },
+
+ // The URLs in the down arrow/autoOpen tests must vary from test to test,
+ // else the first Top Site results will be a switch-to-tab result and a page
+ // load will not occur.
+ async function (win) {
+ info("Open the panel with DOWN, select with DOWN, Enter.");
+ await addTopSite("http://example.org/");
+ win.gURLBar.value = "";
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ });
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ while (win.gURLBar.untrimmedValue != "http://example.org/") {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ }
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "topsites",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "0",
+ numWords: "0",
+ selType: "history",
+ selIndex: val => parseInt(val) >= 0,
+ provider: "UrlbarProviderTopSites",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Open the panel with DOWN, click on entry.");
+ await addTopSite("http://example.com/");
+ win.gURLBar.value = "";
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ });
+ while (win.gURLBar.untrimmedValue != "http://example.com/") {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ }
+ let element = UrlbarTestUtils.getSelectedRow(win);
+ EventUtils.synthesizeMouseAtCenter(element, {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "click",
+ value: "topsites",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "0",
+ numWords: "0",
+ selType: "history",
+ selIndex: "0",
+ provider: "UrlbarProviderTopSites",
+ },
+ };
+ },
+
+ // The URLs in the autoOpen tests must vary from test to test, else
+ // the first Top Site results will be a switch-to-tab result and a page load
+ // will not occur.
+ async function (win) {
+ info(
+ "With pageproxystate=valid, autoopen the panel, select with DOWN, Enter."
+ );
+ await addTopSite("http://example.org/");
+ win.gURLBar.value = "";
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ while (win.gURLBar.untrimmedValue != "http://example.org/") {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ }
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "topsites",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "0",
+ numWords: "0",
+ selType: "history",
+ selIndex: val => parseInt(val) >= 0,
+ provider: "UrlbarProviderTopSites",
+ },
+ };
+ },
+
+ async function (win) {
+ info("With pageproxystate=valid, autoopen the panel, click on entry.");
+ await addTopSite("http://example.com/");
+ win.gURLBar.value = "";
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ while (win.gURLBar.untrimmedValue != "http://example.com/") {
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ }
+ let element = UrlbarTestUtils.getSelectedRow(win);
+ EventUtils.synthesizeMouseAtCenter(element, {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "click",
+ value: "topsites",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "0",
+ numWords: "0",
+ selType: "history",
+ selIndex: "0",
+ provider: "UrlbarProviderTopSites",
+ },
+ };
+ },
+
+ async function (win) {
+ info("With pageproxystate=invalid, open retained results, Enter.");
+ await addTopSite("http://example.org/");
+ win.gURLBar.value = "example.org";
+ win.gURLBar.setPageProxyState("invalid");
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "returned",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "11",
+ numWords: "1",
+ selType: "autofill_origin",
+ selIndex: "0",
+ provider: "Autofill",
+ },
+ };
+ },
+
+ async function (win) {
+ info("With pageproxystate=invalid, open retained results, click on entry.");
+ // This value must be different from the previous test, to avoid reopening
+ // the view.
+ win.gURLBar.value = "example.com";
+ win.gURLBar.setPageProxyState("invalid");
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ let element = UrlbarTestUtils.getSelectedRow(win);
+ EventUtils.synthesizeMouseAtCenter(element, {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "click",
+ value: "returned",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "11",
+ numWords: "1",
+ selType: "autofill_origin",
+ selIndex: "0",
+ provider: "Autofill",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Reopen the view: type, blur, focus, confirm.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "search",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.promisePopupClose(win, () => {
+ win.gURLBar.blur();
+ });
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return [
+ {
+ category: "urlbar",
+ method: "abandonment",
+ object: "blur",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "6",
+ numWords: "1",
+ },
+ },
+ {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "returned",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "6",
+ numWords: "1",
+ selType: "searchengine",
+ selIndex: "0",
+ provider: "HeuristicFallback",
+ },
+ },
+ ];
+ },
+
+ async function (win) {
+ info("Open search mode with a keyboard shortcut.");
+ // Bug 1797801: If the search mode used is the same as the default engine and
+ // showSearchTerms is enabled, the chiclet will remain in the urlbar on the search.
+ // Subsequent tests rely on search mode not already been selected.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", false]],
+ });
+ let defaultEngine = await Services.search.getDefault();
+ win.gURLBar.select();
+ EventUtils.synthesizeKey("k", { accelKey: true }, win);
+ await UrlbarTestUtils.assertSearchMode(win, {
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ engineName: defaultEngine.name,
+ entry: "shortcut",
+ });
+
+ // Execute a search to finish the engagement.
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "moz",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+
+ await SpecialPowers.popPrefEnv();
+
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "3",
+ numWords: "1",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "HeuristicFallback",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Open search mode from a tab-to-search result.");
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.tabToSearch.onboard.interactionsLeft", 0]],
+ });
+
+ await PlacesUtils.history.clear();
+ for (let i = 0; i < 3; i++) {
+ await PlacesTestUtils.addVisits([`https://${TEST_ENGINE_DOMAIN}/`]);
+ }
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: TEST_ENGINE_DOMAIN.slice(0, 4),
+ });
+
+ let tabToSearchResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(win, 1)
+ ).result;
+ Assert.equal(
+ tabToSearchResult.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+
+ // Select the tab-to-search result.
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(win, {
+ engineName: TEST_ENGINE_NAME,
+ entry: "tabtosearch",
+ });
+
+ // Execute a search to finish the engagement.
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "moz",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+
+ await PlacesUtils.history.clear();
+ await SpecialPowers.popPrefEnv();
+
+ return [
+ // engagement on the tab-to-search to enter search mode
+ {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "4",
+ numWords: "1",
+ selIndex: "1",
+ selType: "tabtosearch",
+ provider: "TabToSearch",
+ },
+ },
+ // engagement on the search heuristic
+ {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "3",
+ numWords: "1",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "HeuristicFallback",
+ },
+ },
+ ];
+ },
+
+ async function (win) {
+ info("Sanity check we are not stuck on 'returned'");
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "x",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "1",
+ numWords: "1",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "HeuristicFallback",
+ },
+ };
+ },
+
+ async function (win) {
+ info("Reopen the view: type, blur, focus, backspace, type, confirm.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "search",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.promisePopupClose(win, () => {
+ win.gURLBar.blur();
+ });
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ EventUtils.synthesizeKey("VK_RIGHT", {}, win);
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {}, win);
+ EventUtils.synthesizeKey("x", {}, win);
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return [
+ {
+ category: "urlbar",
+ method: "abandonment",
+ object: "blur",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "6",
+ numWords: "1",
+ },
+ },
+ {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "returned",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "6",
+ numWords: "1",
+ selType: "searchengine",
+ selIndex: "0",
+ provider: "HeuristicFallback",
+ },
+ },
+ ];
+ },
+
+ async function (win) {
+ info("Reopen the view: type, blur, focus, type (overwrite), confirm.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "search",
+ fireInputEvent: true,
+ });
+ await UrlbarTestUtils.promisePopupClose(win, () => {
+ win.gURLBar.blur();
+ });
+ await UrlbarTestUtils.promisePopupOpen(win, () => {
+ win.document.getElementById("Browser:OpenLocation").doCommand();
+ });
+ EventUtils.synthesizeKey("x", {}, win);
+ await UrlbarTestUtils.promiseSearchComplete(win);
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return [
+ {
+ category: "urlbar",
+ method: "abandonment",
+ object: "blur",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "6",
+ numWords: "1",
+ },
+ },
+ {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "restarted",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "1",
+ numWords: "1",
+ selType: "searchengine",
+ selIndex: "0",
+ provider: "HeuristicFallback",
+ },
+ },
+ ];
+ },
+
+ async function (win) {
+ info("Sanity check we are not stuck on 'restarted'");
+ win.gURLBar.select();
+ let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "x",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ return {
+ category: "urlbar",
+ method: "engagement",
+ object: "enter",
+ value: "typed",
+ extra: {
+ elapsed: val => parseInt(val) > 0,
+ numChars: "1",
+ numWords: "1",
+ selIndex: "0",
+ selType: "searchengine",
+ provider: "HeuristicFallback",
+ },
+ };
+ },
+];
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ // Create a new search engine and mark it as default
+ let engine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml",
+ setAsDefault: true,
+ });
+ await Services.search.moveEngine(engine, 0);
+
+ await SearchTestUtils.installSearchExtension({
+ name: TEST_ENGINE_NAME,
+ keyword: TEST_ENGINE_ALIAS,
+ search_url: `https://${TEST_ENGINE_DOMAIN}/`,
+ });
+
+ // Add a bookmark and a keyword.
+ let bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/?q=%s",
+ title: "test",
+ });
+ await PlacesUtils.keywords.insert({
+ keyword: "kw",
+ url: "http://example.com/?q=%s",
+ });
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.keywords.remove("kw");
+ await PlacesUtils.bookmarks.remove(bm);
+ await PlacesUtils.history.clear();
+ });
+});
+
+async function doTest(eventTelemetryEnabled) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.eventTelemetry.enabled", eventTelemetryEnabled],
+ ["browser.urlbar.suggest.searches", true],
+ ],
+ });
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ // This is not necessary after each loop, because assertEvents does it.
+ Services.telemetry.clearEvents();
+ Services.telemetry.clearScalars();
+
+ for (let i = 0; i < tests.length; i++) {
+ info(`Running test at index ${i}`);
+ let events = await tests[i](win);
+ if (events === null) {
+ info("Skipping test");
+ continue;
+ }
+ if (!Array.isArray(events)) {
+ events = [events];
+ }
+ // Always blur to ensure it's not accounted as an additional abandonment.
+ win.gURLBar.setSearchMode({});
+ win.gURLBar.blur();
+ TelemetryTestUtils.assertEvents(eventTelemetryEnabled ? events : [], {
+ category: "urlbar",
+ });
+
+ // Scalars should be recorded regardless of `eventTelemetry.enabled`.
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ TelemetryTestUtils.assertScalar(
+ scalars,
+ "urlbar.engagement",
+ events.filter(e => e.method == "engagement").length || undefined
+ );
+ TelemetryTestUtils.assertScalar(
+ scalars,
+ "urlbar.abandonment",
+ events.filter(e => e.method == "abandonment").length || undefined
+ );
+
+ await UrlbarTestUtils.formHistory.clear(win);
+ }
+
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+}
+
+add_task(async function enabled() {
+ await doTest(true);
+});
+
+add_task(async function disabled() {
+ await doTest(false);
+});
+
+/**
+ * Replaces the contents of Top Sites with the specified site.
+ *
+ * @param {string} site
+ * A site to add to Top Sites.
+ */
+async function addTopSite(site) {
+ await PlacesUtils.history.clear();
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits(site);
+ }
+
+ await updateTopSites(sites => sites && sites[0] && sites[0].url == site);
+}
+
+function registerTipProvider() {
+ let provider = new UrlbarTestUtils.TestProvider({
+ results: tipMatches,
+ priority: 1,
+ });
+ UrlbarProvidersManager.registerProvider(provider);
+ return provider;
+}
+
+function unregisterTipProvider(provider) {
+ UrlbarProvidersManager.unregisterProvider(provider);
+}
+
+let tipMatches = [
+ 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,
+ {
+ helpUrl: "http://example.com/",
+ helpL10n: {
+ id: UrlbarPrefs.get("resultMenu")
+ ? "urlbar-result-menu-tip-get-help"
+ : "urlbar-tip-help-icon",
+ },
+ type: "test",
+ titleL10n: { id: "urlbar-search-tips-confirm" },
+ buttons: [
+ {
+ url: "http://example.com/",
+ l10n: { id: "urlbar-search-tips-confirm" },
+ },
+ ],
+ }
+ ),
+ 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" }
+ ),
+];
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry_noEvent.js b/browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry_noEvent.js
new file mode 100644
index 0000000000..bdba6888b7
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry_noEvent.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const tests = [
+ async function (win) {
+ info("Type something, click on search settings.");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: win.gBrowser, url: "about:blank" },
+ async browser => {
+ win.gURLBar.select();
+ const promise = onSyncPaneLoaded();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "x",
+ fireInputEvent: true,
+ });
+ UrlbarTestUtils.getOneOffSearchButtons(win).settingsButton.click();
+ await promise;
+ }
+ );
+ return null;
+ },
+
+ async function (win) {
+ info("Type something, Up, Enter on search settings.");
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: win.gBrowser, url: "about:blank" },
+ async browser => {
+ win.gURLBar.select();
+ const promise = onSyncPaneLoaded();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ value: "x",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_ArrowUp", {}, win);
+ Assert.ok(
+ UrlbarTestUtils.getOneOffSearchButtons(
+ win
+ ).selectedButton.classList.contains("search-setting-button"),
+ "Should have selected the settings button"
+ );
+ EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ await promise;
+ }
+ );
+ return null;
+ },
+];
+
+function onSyncPaneLoaded() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function panesLoadedObs() {
+ Services.obs.removeObserver(panesLoadedObs, "sync-pane-loaded");
+ resolve();
+ }, "sync-pane-loaded");
+ });
+}
+
+add_task(async function test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.eventTelemetry.enabled", true]],
+ });
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ // This is not necessary after each loop, because assertEvents does it.
+ Services.telemetry.clearEvents();
+
+ for (let i = 0; i < tests.length; i++) {
+ info(`Running no event test at index ${i}`);
+ await tests[i](win);
+ // Always blur to ensure it's not accounted as an additional abandonment.
+ win.gURLBar.blur();
+ TelemetryTestUtils.assertEvents([], { category: "urlbar" });
+ }
+
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_selection.js b/browser/components/urlbar/tests/browser/browser_urlbar_selection.js
new file mode 100644
index 0000000000..3440c35e6f
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_selection.js
@@ -0,0 +1,307 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const exampleSearch = "f oo bar";
+const exampleUrl = "https://example.com/1";
+
+function click(target) {
+ let promise = BrowserTestUtils.waitForEvent(target, "click");
+ EventUtils.synthesizeMouseAtCenter(target, {}, target.ownerGlobal);
+ return promise;
+}
+
+function openContextMenu(target) {
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ target.ownerGlobal,
+ "contextmenu"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ target,
+ {
+ type: "contextmenu",
+ button: 2,
+ },
+ target.ownerGlobal
+ );
+ return popupShownPromise;
+}
+
+function drag(target, fromX, fromY, toX, toY) {
+ let promise = BrowserTestUtils.waitForEvent(target, "mouseup");
+ EventUtils.synthesizeMouse(
+ target,
+ fromX,
+ fromY,
+ { type: "mousemove" },
+ target.ownerGlobal
+ );
+ EventUtils.synthesizeMouse(
+ target,
+ fromX,
+ fromY,
+ { type: "mousedown" },
+ target.ownerGlobal
+ );
+ EventUtils.synthesizeMouse(
+ target,
+ toX,
+ toY,
+ { type: "mousemove" },
+ target.ownerGlobal
+ );
+ EventUtils.synthesizeMouse(
+ target,
+ toX,
+ toY,
+ { type: "mouseup" },
+ target.ownerGlobal
+ );
+ return promise;
+}
+
+function resetPrimarySelection(val = "") {
+ if (
+ Services.clipboard.isClipboardTypeSupported(
+ Services.clipboard.kSelectionClipboard
+ )
+ ) {
+ // Reset the clipboard.
+ clipboardHelper.copyStringToClipboard(
+ val,
+ Services.clipboard.kSelectionClipboard
+ );
+ }
+}
+
+function checkPrimarySelection(expectedVal = "") {
+ if (
+ Services.clipboard.isClipboardTypeSupported(
+ Services.clipboard.kSelectionClipboard
+ )
+ ) {
+ let primaryAsText = SpecialPowers.getClipboardData(
+ "text/plain",
+ SpecialPowers.Ci.nsIClipboard.kSelectionClipboard
+ );
+ Assert.equal(primaryAsText, expectedVal);
+ }
+}
+
+add_setup(async function () {
+ // On macOS, we must "warm up" the Urlbar to get the first test to pass.
+ gURLBar.value = "";
+ await click(gURLBar.inputField);
+ gURLBar.blur();
+});
+
+add_task(async function leftClickSelectsAll() {
+ resetPrimarySelection();
+ gURLBar.value = exampleSearch;
+ await click(gURLBar.inputField);
+ Assert.equal(
+ gURLBar.selectionStart,
+ 0,
+ "The entire search term should be selected."
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ exampleSearch.length,
+ "The entire search term should be selected."
+ );
+ gURLBar.blur();
+ checkPrimarySelection();
+});
+
+add_task(async function leftClickSelectsUrl() {
+ resetPrimarySelection();
+ gURLBar.value = exampleUrl;
+ await click(gURLBar.inputField);
+ Assert.equal(gURLBar.selectionStart, 0, "The entire url should be selected.");
+ Assert.equal(
+ gURLBar.selectionEnd,
+ exampleUrl.length,
+ "The entire url should be selected."
+ );
+ gURLBar.blur();
+ checkPrimarySelection();
+});
+
+add_task(async function rightClickSelectsAll() {
+ gURLBar.inputField.focus();
+ gURLBar.value = exampleUrl;
+
+ // Remove the selection so the focus() call above doesn't influence the test.
+ gURLBar.selectionStart = gURLBar.selectionEnd = 0;
+
+ resetPrimarySelection();
+
+ await openContextMenu(gURLBar.inputField);
+
+ Assert.equal(gURLBar.selectionStart, 0, "The entire URL should be selected.");
+ Assert.equal(
+ gURLBar.selectionEnd,
+ exampleUrl.length,
+ "The entire URL should be selected."
+ );
+
+ checkPrimarySelection();
+
+ let contextMenu = gURLBar.querySelector("moz-input-box").menupopup;
+
+ // While the context menu is open, test the "Select All" button.
+ let contextMenuItem = contextMenu.firstElementChild;
+ while (
+ contextMenuItem.nextElementSibling &&
+ contextMenuItem.getAttribute("cmd") != "cmd_selectAll"
+ ) {
+ contextMenuItem = contextMenuItem.nextElementSibling;
+ }
+ Assert.ok(
+ contextMenuItem,
+ "The context menu should have the select all menu item."
+ );
+
+ let controller = document.commandDispatcher.getControllerForCommand(
+ contextMenuItem.getAttribute("cmd")
+ );
+ let enabled = controller.isCommandEnabled(
+ contextMenuItem.getAttribute("cmd")
+ );
+ Assert.ok(enabled, "The context menu select all item should be enabled.");
+
+ await click(contextMenuItem);
+ Assert.equal(
+ gURLBar.selectionStart,
+ 0,
+ "The entire URL should be selected after clicking selectAll button."
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ exampleUrl.length,
+ "The entire URL should be selected after clicking selectAll button."
+ );
+
+ gURLBar.querySelector("moz-input-box").menupopup.hidePopup();
+ gURLBar.blur();
+ checkPrimarySelection(gURLBar.value);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function contextMenuDoesNotCancelSelection() {
+ gURLBar.inputField.focus();
+ gURLBar.value = exampleUrl;
+
+ gURLBar.selectionStart = 3;
+ gURLBar.selectionEnd = 7;
+
+ resetPrimarySelection();
+
+ await openContextMenu(gURLBar.inputField);
+
+ Assert.equal(
+ gURLBar.selectionStart,
+ 3,
+ "The selection should not have changed."
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ 7,
+ "The selection should not have changed."
+ );
+
+ gURLBar.querySelector("moz-input-box").menupopup.hidePopup();
+ gURLBar.blur();
+ checkPrimarySelection();
+});
+
+add_task(async function dragSelect() {
+ resetPrimarySelection();
+ gURLBar.value = exampleSearch.repeat(10);
+ // Drags from an artibrary offset of 30 to test for bug 1562145: that the
+ // selection does not start at the beginning.
+ await drag(gURLBar.inputField, 30, 0, 60, 0);
+ Assert.greater(
+ gURLBar.selectionStart,
+ 0,
+ "Selection should not start at the beginning of the string."
+ );
+
+ let selectedVal = gURLBar.value.substring(
+ gURLBar.selectionStart,
+ gURLBar.selectionEnd
+ );
+ gURLBar.blur();
+ checkPrimarySelection(selectedVal);
+});
+
+/**
+ * Testing for bug 1571018: that the entire Urlbar isn't selected when the
+ * Urlbar is dragged following a selectsAll event then a blur.
+ */
+add_task(async function dragAfterSelectAll() {
+ resetPrimarySelection();
+ gURLBar.value = exampleSearch.repeat(10);
+ await click(gURLBar.inputField);
+ Assert.equal(
+ gURLBar.selectionStart,
+ 0,
+ "The entire search term should be selected."
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ exampleSearch.repeat(10).length,
+ "The entire search term should be selected."
+ );
+
+ gURLBar.blur();
+ checkPrimarySelection();
+
+ // The offset of 30 is arbitrary.
+ await drag(gURLBar.inputField, 30, 0, 60, 0);
+
+ Assert.notEqual(
+ gURLBar.selectionStart,
+ 0,
+ "Only part of the search term should be selected."
+ );
+ Assert.notEqual(
+ gURLBar.selectionEnd,
+ exampleSearch.repeat(10).length,
+ "Only part of the search term should be selected."
+ );
+
+ checkPrimarySelection(
+ gURLBar.value.substring(gURLBar.selectionStart, gURLBar.selectionEnd)
+ );
+});
+
+/**
+ * Testing for bug 1571018: that the entire Urlbar is selected when the Urlbar
+ * is refocused following a partial text selection then a blur.
+ */
+add_task(async function selectAllAfterDrag() {
+ gURLBar.value = exampleSearch;
+
+ gURLBar.selectionStart = 3;
+ gURLBar.selectionEnd = 7;
+
+ gURLBar.blur();
+
+ await click(gURLBar.inputField);
+
+ Assert.equal(
+ gURLBar.selectionStart,
+ 0,
+ "The entire search term should be selected."
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ exampleSearch.length,
+ "The entire search term should be selected."
+ );
+
+ gURLBar.blur();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry.js
new file mode 100644
index 0000000000..4e48a946d5
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry.js
@@ -0,0 +1,1218 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This file tests urlbar telemetry with search related actions.
+ */
+
+"use strict";
+
+const SCALAR_URLBAR = "browser.engagement.navigation.urlbar";
+const SCALAR_SEARCHMODE = "browser.engagement.navigation.urlbar_searchmode";
+
+// The preference to enable suggestions in the urlbar.
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+
+ChromeUtils.defineESModuleGetters(this, {
+ SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.sys.mjs",
+});
+
+function searchInAwesomebar(value, win = window) {
+ return UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ waitForFocus,
+ value,
+ fireInputEvent: true,
+ });
+}
+
+/**
+ * Click one of the entries in the urlbar suggestion popup.
+ *
+ * @param {string} resultTitle
+ * The title of the result to click on.
+ * @param {number} button [optional]
+ * which button to click.
+ * @returns {number}
+ * The index of the result that was clicked, or -1 if not found.
+ */
+async function clickURLBarSuggestion(resultTitle, button = 1) {
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ const count = UrlbarTestUtils.getResultCount(window);
+ for (let i = 0; i < count; i++) {
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ if (result.displayed.title == resultTitle) {
+ // This entry is the search suggestion we're looking for.
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ i
+ );
+ if (button == 1) {
+ EventUtils.synthesizeMouseAtCenter(element, {});
+ } else if (button == 2) {
+ EventUtils.synthesizeMouseAtCenter(element, {
+ type: "mousedown",
+ button: 2,
+ });
+ }
+ return i;
+ }
+ }
+ return -1;
+}
+
+/**
+ * Create an engine to generate search suggestions and add it as default
+ * for this test.
+ *
+ * @param {Function} taskFn
+ * The function to run with the new search engine as default.
+ */
+async function withNewSearchEngine(taskFn) {
+ let suggestionEngine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "urlbarTelemetrySearchSuggestions.xml",
+ });
+ let previousEngine = await Services.search.getDefault();
+ await Services.search.setDefault(
+ suggestionEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ try {
+ await taskFn(suggestionEngine);
+ } finally {
+ await Services.search.setDefault(
+ previousEngine,
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await Services.search.removeEngine(suggestionEngine);
+ }
+}
+
+add_setup(async function () {
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: "MozSearch",
+ keyword: "mozalias",
+ search_url: "https://example.com/",
+ },
+ { setAsDefault: true }
+ );
+
+ // Make it the first one-off engine.
+ let engine = Services.search.getEngineByName("MozSearch");
+ await Services.search.moveEngine(engine, 0);
+
+ // Enable search suggestions in the urlbar.
+ let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+
+ // Enable local telemetry recording for the duration of the tests.
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+
+ // Enable event recording for the events tested here.
+ Services.telemetry.setEventRecordingEnabled("navigation", true);
+
+ // Clear history so that history added by previous tests doesn't mess up this
+ // test when it selects results in the urlbar.
+ await PlacesUtils.history.clear();
+
+ // Clear historical search suggestions to avoid interference from previous
+ // tests.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.maxHistoricalSearchSuggestions", 0]],
+ });
+
+ // This test assumes that general results are shown before suggestions.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchSuggestionsFirst", false]],
+ });
+
+ // Make sure to restore the engine once we're done.
+ registerCleanupFunction(async function () {
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
+ await PlacesUtils.history.clear();
+ await UrlbarTestUtils.formHistory.clear();
+ Services.telemetry.setEventRecordingEnabled("navigation", false);
+ });
+});
+
+add_task(async function test_simpleQuery() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+ let search_hist =
+ TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Simulate entering a simple search.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("simple query");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ // Check if the scalars contain the expected values.
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ SCALAR_URLBAR,
+ "search_enter",
+ 1
+ );
+ Assert.equal(
+ Object.keys(scalars[SCALAR_URLBAR]).length,
+ 1,
+ "This search must only increment one entry in the scalar."
+ );
+
+ // SEARCH_COUNTS should be incremented, but only the urlbar source since an
+ // internal @search keyword was not used.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar",
+ 1
+ );
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.alias",
+ undefined
+ );
+
+ // Also check events.
+ TelemetryTestUtils.assertEvents(
+ [
+ [
+ "navigation",
+ "search",
+ "urlbar",
+ "enter",
+ { engine: "other-MozSearch" },
+ ],
+ ],
+ { category: "navigation", method: "search" }
+ );
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enter,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_searchMode_enter() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Enter search mode using an alias and a query.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("mozalias query");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ // Check if the scalars contain the expected values.
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ SCALAR_SEARCHMODE,
+ "search_enter",
+ 1
+ );
+ Assert.equal(
+ Object.keys(scalars[SCALAR_SEARCHMODE]).length,
+ 1,
+ "This search must only increment one entry in the scalar."
+ );
+
+ // Also check events.
+ TelemetryTestUtils.assertEvents(
+ [
+ [
+ "navigation",
+ "search",
+ "urlbar_searchmode",
+ "enter",
+ { engine: "other-MozSearch" },
+ ],
+ ],
+ { category: "navigation", method: "search" }
+ );
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enter,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Performs a search using the first result, a one-off button, and the Return
+// (Enter) key.
+add_task(async function test_oneOff_enter() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+ let search_hist =
+ TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Perform a one-off search using the first engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("query");
+
+ info("Pressing Alt+Down to take us to the first one-off engine.");
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ let engine =
+ UrlbarTestUtils.getOneOffSearchButtons(window).selectedButton.engine;
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: engine.name,
+ entry: "oneoff",
+ });
+
+ // Now that we're in search mode, execute the search.
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ // Check if the scalars contain the expected values.
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ SCALAR_SEARCHMODE,
+ "search_enter",
+ 1
+ );
+ Assert.equal(
+ Object.keys(scalars[SCALAR_SEARCHMODE]).length,
+ 1,
+ "This search must only increment one entry in the scalar."
+ );
+
+ // SEARCH_COUNTS should be incremented, but only the urlbar-searchmode source
+ // since aliases aren't counted separately in search mode.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar-searchmode",
+ 1
+ );
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.alias",
+ undefined
+ );
+
+ // Also check events.
+ TelemetryTestUtils.assertEvents(
+ [
+ [
+ "navigation",
+ "search",
+ "urlbar_searchmode",
+ "enter",
+ { engine: "other-MozSearch" },
+ ],
+ ],
+ { category: "navigation", method: "search" }
+ );
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enter,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Performs a search using the second result, a one-off button, and the Return
+// (Enter) key. This only tests the FX_URLBAR_SELECTED_RESULT_METHOD histogram
+// since test_oneOff_enter covers everything else.
+add_task(async function test_oneOff_enterSelection() {
+ Services.telemetry.clearScalars();
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+
+ await withNewSearchEngine(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.maxHistoricalSearchSuggestions", 1]],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. Suggestions should be generated by the test engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("query");
+
+ info(
+ "Select the second result, press Alt+Down to take us to the first one-off engine."
+ );
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
+ let engine =
+ UrlbarTestUtils.getOneOffSearchButtons(window).selectedButton.engine;
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: engine.name,
+ entry: "oneoff",
+ });
+
+ // Now that we're in search mode, execute the search.
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.arrowEnterSelection,
+ 1
+ );
+
+ await SpecialPowers.popPrefEnv();
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+// Performs a search using a click on a one-off button. This only tests the
+// FX_URLBAR_SELECTED_RESULT_METHOD histogram since test_oneOff_enter covers
+// everything else.
+add_task(async function test_oneOff_click() {
+ Services.telemetry.clearScalars();
+
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("query");
+
+ info("Click the first one-off button.");
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ let oneOffButton =
+ UrlbarTestUtils.getOneOffSearchButtons(window).getSelectableButtons(
+ false
+ )[0];
+ oneOffButton.click();
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: oneOffButton.engine.name,
+ entry: "oneoff",
+ });
+
+ // Now that we're in search mode, execute the search.
+ let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+ Assert.ok(element, "Found result after entering search mode.");
+ EventUtils.synthesizeMouseAtCenter(element, {});
+ await p;
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.click,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Clicks the first suggestion offered by the test search engine.
+add_task(async function test_suggestion_click() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ await UrlbarTestUtils.formHistory.clear();
+
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+ let search_hist =
+ TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
+
+ await withNewSearchEngine(async function (engine) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. Suggestions should be generated by the test engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("query");
+ info("Clicking the urlbar suggestion.");
+ await clickURLBarSuggestion("queryfoo");
+ await p;
+
+ // Check if the scalars contain the expected values.
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ SCALAR_URLBAR,
+ "search_suggestion",
+ 1
+ );
+ Assert.equal(
+ Object.keys(scalars[SCALAR_URLBAR]).length,
+ 1,
+ "This search must only increment one entry in the scalar."
+ );
+
+ // SEARCH_COUNTS should be incremented.
+ let searchEngineId = "other-" + engine.name;
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ searchEngineId + ".urlbar",
+ 1
+ );
+
+ // Also check events.
+ TelemetryTestUtils.assertEvents(
+ [
+ [
+ "navigation",
+ "search",
+ "urlbar",
+ "suggestion",
+ { engine: searchEngineId },
+ ],
+ ],
+ { category: "navigation", method: "search" }
+ );
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.click,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+// Selects and presses the Return (Enter) key on the first suggestion offered by
+// the test search engine. This only tests the FX_URLBAR_SELECTED_RESULT_METHOD
+// histogram since test_suggestion_click covers everything else.
+add_task(async function test_suggestion_arrowEnterSelection() {
+ Services.telemetry.clearScalars();
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+
+ await withNewSearchEngine(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. Suggestions should be generated by the test engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("query");
+ info("Select the second result and press Return.");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.arrowEnterSelection,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+// Selects through tab and presses the Return (Enter) key on the first
+// suggestion offered by the test search engine.
+add_task(async function test_suggestion_tabEnterSelection() {
+ Services.telemetry.clearScalars();
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+
+ await withNewSearchEngine(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. Suggestions should be generated by the test engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("query");
+ info("Select the second result and press Return.");
+ EventUtils.synthesizeKey("KEY_Tab");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.tabEnterSelection,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+// Selects through code and presses the Return (Enter) key on the first
+// suggestion offered by the test search engine.
+add_task(async function test_suggestion_enterSelection() {
+ Services.telemetry.clearScalars();
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+
+ await withNewSearchEngine(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. Suggestions should be generated by the test engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("query");
+ info("Select the second result and press Return.");
+ UrlbarTestUtils.setSelectedRowIndex(window, 1);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enterSelection,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+// Clicks the first suggestion offered by the test search engine when in search
+// mode.
+add_task(async function test_searchmode_suggestion_click() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+ let search_hist =
+ TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
+
+ await withNewSearchEngine(async function (engine) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. Suggestions should be generated by the test engine.");
+ await searchInAwesomebar("query");
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: engine.name,
+ });
+ info("Clicking the urlbar suggestion.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await clickURLBarSuggestion("queryfoo");
+ await p;
+
+ // Check if the scalars contain the expected values.
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ SCALAR_SEARCHMODE,
+ "search_suggestion",
+ 1
+ );
+ Assert.equal(
+ Object.keys(scalars[SCALAR_SEARCHMODE]).length,
+ 1,
+ "This search must only increment one entry in the scalar."
+ );
+
+ // SEARCH_COUNTS should be incremented.
+ let searchEngineId = "other-" + engine.name;
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ searchEngineId + ".urlbar-searchmode",
+ 1
+ );
+
+ // Also check events.
+ TelemetryTestUtils.assertEvents(
+ [
+ [
+ "navigation",
+ "search",
+ "urlbar_searchmode",
+ "suggestion",
+ { engine: searchEngineId },
+ ],
+ ],
+ { category: "navigation", method: "search" }
+ );
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.click,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+// Selects and presses the Return (Enter) key on the first suggestion offered by
+// the test search engine in search mode. This only tests the
+// FX_URLBAR_SELECTED_RESULT_METHOD histogram since
+// test_searchmode_suggestion_click covers everything else.
+add_task(async function test_searchmode_suggestion_arrowEnterSelection() {
+ Services.telemetry.clearScalars();
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+
+ await withNewSearchEngine(async function (engine) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. Suggestions should be generated by the test engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("query");
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: engine.name,
+ });
+ info("Select the second result and press Return.");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.arrowEnterSelection,
+ 1
+ );
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+// Selects through tab and presses the Return (Enter) key on the first
+// suggestion offered by the test search engine in search mode.
+add_task(async function test_suggestion_tabEnterSelection() {
+ Services.telemetry.clearScalars();
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+
+ await withNewSearchEngine(async function (engine) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. Suggestions should be generated by the test engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("query");
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: engine.name,
+ });
+ info("Select the second result and press Return.");
+ EventUtils.synthesizeKey("KEY_Tab");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.tabEnterSelection,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+// Selects through code and presses the Return (Enter) key on the first
+// suggestion offered by the test search engine in search mode.
+add_task(async function test_suggestion_enterSelection() {
+ Services.telemetry.clearScalars();
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+
+ await withNewSearchEngine(async function (engine) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. Suggestions should be generated by the test engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("query");
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: engine.name,
+ });
+ info("Select the second result and press Return.");
+ UrlbarTestUtils.setSelectedRowIndex(window, 1);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enterSelection,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+// Clicks a form history result.
+add_task(async function test_formHistory_click() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ await UrlbarTestUtils.formHistory.clear();
+ await UrlbarTestUtils.formHistory.add(["foobar"]);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.maxHistoricalSearchSuggestions", 1]],
+ });
+
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+ let search_hist =
+ TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
+
+ await withNewSearchEngine(async engine => {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. There should be form history.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("foo");
+ info("Clicking the form history.");
+ await clickURLBarSuggestion("foobar");
+ await p;
+
+ // Check if the scalars contain the expected values.
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ SCALAR_URLBAR,
+ "search_formhistory",
+ 1
+ );
+ Assert.equal(
+ Object.keys(scalars[SCALAR_URLBAR]).length,
+ 1,
+ "This search must only increment one entry in the scalar."
+ );
+
+ // SEARCH_COUNTS should be incremented.
+ let searchEngineId = "other-" + engine.name;
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ searchEngineId + ".urlbar",
+ 1
+ );
+
+ // Also check events.
+ TelemetryTestUtils.assertEvents(
+ [
+ [
+ "navigation",
+ "search",
+ "urlbar",
+ "formhistory",
+ { engine: searchEngineId },
+ ],
+ ],
+ { category: "navigation", method: "search" }
+ );
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.click,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+ await SpecialPowers.popPrefEnv();
+ });
+});
+
+// Selects and presses the Return (Enter) key on a form history result. This
+// only tests the FX_URLBAR_SELECTED_RESULT_METHOD histogram since
+// test_formHistory_click covers everything else.
+add_task(async function test_formHistory_arrowEnterSelection() {
+ Services.telemetry.clearScalars();
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+
+ await UrlbarTestUtils.formHistory.clear();
+ await UrlbarTestUtils.formHistory.add(["foobar"]);
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.maxHistoricalSearchSuggestions", 1]],
+ });
+
+ await withNewSearchEngine(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. There should be form history.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("foo");
+ info("Select the form history result and press Return.");
+ while (gURLBar.untrimmedValue != "foobar") {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.arrowEnterSelection,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+ await SpecialPowers.popPrefEnv();
+ });
+});
+
+// Selects through tab and presses the Return (Enter) key on a form history
+// result.
+add_task(async function test_formHistory_tabEnterSelection() {
+ Services.telemetry.clearScalars();
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+
+ await UrlbarTestUtils.formHistory.clear();
+ await UrlbarTestUtils.formHistory.add(["foobar"]);
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.maxHistoricalSearchSuggestions", 1]],
+ });
+
+ await withNewSearchEngine(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. There should be form history.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("foo");
+ info("Select the form history result and press Return.");
+ while (gURLBar.untrimmedValue != "foobar") {
+ EventUtils.synthesizeKey("KEY_Tab");
+ }
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.tabEnterSelection,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+ await SpecialPowers.popPrefEnv();
+ });
+});
+
+// Selects through code and presses the Return (Enter) key on a form history
+// result.
+add_task(async function test_formHistory_enterSelection() {
+ Services.telemetry.clearScalars();
+ let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ );
+
+ await UrlbarTestUtils.formHistory.clear();
+ await UrlbarTestUtils.formHistory.add(["foobar"]);
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.maxHistoricalSearchSuggestions", 1]],
+ });
+
+ await withNewSearchEngine(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Type a query. There should be form history.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("foo");
+ info("Select the second result and press Return.");
+ let index = 1;
+ while (gURLBar.untrimmedValue != "foobar") {
+ UrlbarTestUtils.setSelectedRowIndex(window, index++);
+ }
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ TelemetryTestUtils.assertHistogram(
+ resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enterSelection,
+ 1
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await UrlbarTestUtils.formHistory.clear();
+ await SpecialPowers.popPrefEnv();
+ });
+});
+
+add_task(async function test_privateWindow() {
+ // This test assumes the showSearchTerms feature is not enabled,
+ // as multiple searches are made one after another, relying on
+ // urlbar as the keyed scalar SAP, not urlbar_persisted.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", false]],
+ });
+
+ // Override the search telemetry search provider info to
+ // count in-content SEARCH_COUNTs telemetry for our test engine.
+ SearchSERPTelemetry.overrideSearchTelemetryForTests([
+ {
+ telemetryId: "example",
+ searchPageRegexp: "^https://example\\.com/",
+ queryParamName: "q",
+ },
+ ]);
+
+ let search_hist =
+ TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
+
+ // First, do a bunch of searches in a private window.
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+ info("Search in a private window and the pref does not exist");
+ let p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await searchInAwesomebar("query", win);
+ EventUtils.synthesizeKey("KEY_Enter", undefined, win);
+ await p;
+
+ // SEARCH_COUNTS should be incremented.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar",
+ 1
+ );
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ console.log(scalars);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "browser.search.content.urlbar",
+ "example:organic:none",
+ 1
+ );
+
+ info("Search again in a private window after setting the pref to true");
+ Services.prefs.setBoolPref("browser.engagement.search_counts.pbm", true);
+ p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await searchInAwesomebar("another query", win);
+ EventUtils.synthesizeKey("KEY_Enter", undefined, win);
+ await p;
+
+ // SEARCH_COUNTS should *not* be incremented.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar",
+ 1
+ );
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "browser.search.content.urlbar",
+ "example:organic:none",
+ 1
+ );
+
+ info("Search again in a private window after setting the pref to false");
+ Services.prefs.setBoolPref("browser.engagement.search_counts.pbm", false);
+ p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await searchInAwesomebar("another query", win);
+ EventUtils.synthesizeKey("KEY_Enter", undefined, win);
+ await p;
+
+ // SEARCH_COUNTS should be incremented.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar",
+ 2
+ );
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "browser.search.content.urlbar",
+ "example:organic:none",
+ 2
+ );
+
+ info("Search again in a private window after clearing the pref");
+ Services.prefs.clearUserPref("browser.engagement.search_counts.pbm");
+ p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await searchInAwesomebar("another query", win);
+ EventUtils.synthesizeKey("KEY_Enter", undefined, win);
+ await p;
+
+ // SEARCH_COUNTS should be incremented.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar",
+ 3
+ );
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "browser.search.content.urlbar",
+ "example:organic:none",
+ 3
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+
+ // Now, do a bunch of searches in a non-private window. Telemetry should
+ // always be recorded regardless of the pref's value.
+ win = await BrowserTestUtils.openNewBrowserWindow();
+
+ info("Search in a non-private window and the pref does not exist");
+ p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await searchInAwesomebar("query", win);
+ EventUtils.synthesizeKey("KEY_Enter", undefined, win);
+ await p;
+
+ // SEARCH_COUNTS should be incremented.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar",
+ 4
+ );
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "browser.search.content.urlbar",
+ "example:organic:none",
+ 4
+ );
+
+ info("Search again in a non-private window after setting the pref to true");
+ Services.prefs.setBoolPref("browser.engagement.search_counts.pbm", true);
+ p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await searchInAwesomebar("another query", win);
+ EventUtils.synthesizeKey("KEY_Enter", undefined, win);
+ await p;
+
+ // SEARCH_COUNTS should be incremented.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar",
+ 5
+ );
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "browser.search.content.urlbar",
+ "example:organic:none",
+ 5
+ );
+
+ info("Search again in a non-private window after setting the pref to false");
+ Services.prefs.setBoolPref("browser.engagement.search_counts.pbm", false);
+ p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await searchInAwesomebar("another query", win);
+ EventUtils.synthesizeKey("KEY_Enter", undefined, win);
+ await p;
+
+ // SEARCH_COUNTS should be incremented.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar",
+ 6
+ );
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "browser.search.content.urlbar",
+ "example:organic:none",
+ 6
+ );
+
+ info("Search again in a non-private window after clearing the pref");
+ Services.prefs.clearUserPref("browser.engagement.search_counts.pbm");
+ p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ await searchInAwesomebar("another query", win);
+ EventUtils.synthesizeKey("KEY_Enter", undefined, win);
+ await p;
+
+ // SEARCH_COUNTS should be incremented.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar",
+ 7
+ );
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "browser.search.content.urlbar",
+ "example:organic:none",
+ 7
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+
+ // Reset the search provider info.
+ SearchSERPTelemetry.overrideSearchTelemetryForTests();
+ await UrlbarTestUtils.formHistory.clear();
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_autofill.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_autofill.js
new file mode 100644
index 0000000000..8336bde462
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_autofill.js
@@ -0,0 +1,733 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests urlbar autofill telemetry.
+ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarProviderPreloadedSites:
+ "resource:///modules/UrlbarProviderPreloadedSites.sys.mjs",
+});
+
+const SCALAR_URLBAR = "browser.engagement.navigation.urlbar";
+
+function assertSearchTelemetryEmpty(search_hist) {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+ Assert.ok(
+ !(SCALAR_URLBAR in scalars),
+ `Should not have recorded ${SCALAR_URLBAR}`
+ );
+
+ // SEARCH_COUNTS should not contain any engine counts at all. The keys in this
+ // histogram are search engine telemetry identifiers.
+ Assert.deepEqual(
+ Object.keys(search_hist.snapshot()),
+ [],
+ "SEARCH_COUNTS is empty"
+ );
+
+ // Also check events.
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ false
+ );
+ events = (events.parent || []).filter(
+ e => e[1] == "navigation" && e[2] == "search"
+ );
+ Assert.deepEqual(
+ events,
+ [],
+ "Should not have recorded any navigation search events"
+ );
+}
+
+function snapshotHistograms() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ return {
+ resultMethodHist: TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ ),
+ search_hist: TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS"),
+ };
+}
+
+function assertTelemetryResults(histograms, type, index, method) {
+ TelemetryTestUtils.assertHistogram(histograms.resultMethodHist, method, 1);
+
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ `urlbar.picked.${type}`,
+ index,
+ 1
+ );
+}
+
+/**
+ * Performs a search and picks the first result.
+ *
+ * @param {string} searchString
+ * The search string. Assumed to trigger an autofill result
+ * @param {string} autofilledValue
+ * The input's expected value after autofill occurs.
+ * @param {string} unpickResult
+ * Optional: If true, do not pick any result. Default value is false.
+ * @param {string} urlToSelect
+ * Optional: If want to select result except autofill, pass the URL.
+ */
+async function triggerAutofillAndPickResult(
+ searchString,
+ autofilledValue,
+ unpickResult = false,
+ urlToSelect = null
+) {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ fireInputEvent: true,
+ });
+
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill, "Result is autofill");
+ Assert.equal(gURLBar.value, autofilledValue, "gURLBar.value");
+ Assert.equal(gURLBar.selectionStart, searchString.length, "selectionStart");
+ Assert.equal(gURLBar.selectionEnd, autofilledValue.length, "selectionEnd");
+
+ if (urlToSelect) {
+ for (let row = 0; row < UrlbarTestUtils.getResultCount(window); row++) {
+ const result = await UrlbarTestUtils.getDetailsOfResultAt(window, row);
+ if (result.url === urlToSelect) {
+ UrlbarTestUtils.setSelectedRowIndex(window, row);
+ break;
+ }
+ }
+ }
+
+ if (unpickResult) {
+ // Close popup without any action.
+ await UrlbarTestUtils.promisePopupClose(window);
+ return;
+ }
+
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+
+ let url;
+ if (urlToSelect) {
+ url = urlToSelect;
+ } else {
+ url = autofilledValue.includes(":")
+ ? autofilledValue
+ : "http://" + autofilledValue;
+ }
+ Assert.equal(gBrowser.currentURI.spec, url, "Loaded URL is correct");
+ });
+}
+
+function createOtherAutofillProvider(searchString, autofilledValue) {
+ return new UrlbarTestUtils.TestProvider({
+ priority: Infinity,
+ type: UrlbarUtils.PROVIDER_TYPE.HEURISTIC,
+ results: [
+ Object.assign(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ title: "Test",
+ url: "http://example.com/",
+ }
+ ),
+ {
+ heuristic: true,
+ autofill: {
+ value: autofilledValue,
+ selectionStart: searchString.length,
+ selectionEnd: autofilledValue.length,
+ // Leave out `type` to trigger "other"
+ },
+ }
+ ),
+ ],
+ });
+}
+
+// Allow more time for Mac machines so they don't time out in verify mode.
+if (AppConstants.platform == "macosx") {
+ requestLongerTimeout(3);
+}
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesTestUtils.clearInputHistory();
+
+ // Enable local telemetry recording for the duration of the tests.
+ const originalCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+
+ registerCleanupFunction(async () => {
+ Services.telemetry.canRecordExtended = originalCanRecord;
+ await PlacesTestUtils.clearInputHistory();
+ await PlacesUtils.history.clear();
+ });
+});
+
+// Checks adaptive history, origin, and URL autofill.
+add_task(async function history() {
+ const testData = [
+ {
+ useAdaptiveHistory: true,
+ visitHistory: ["http://example.com/test"],
+ inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
+ userInput: "ex",
+ autofilled: "example.com/",
+ expected: "autofill_origin",
+ },
+ {
+ useAdaptiveHistory: true,
+ visitHistory: ["http://example.com/test"],
+ inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
+ userInput: "exa",
+ autofilled: "example.com/test",
+ expected: "autofill_adaptive",
+ },
+ {
+ useAdaptiveHistory: true,
+ visitHistory: ["http://example.com/test"],
+ inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
+ userInput: "exam",
+ autofilled: "example.com/test",
+ expected: "autofill_adaptive",
+ },
+ {
+ useAdaptiveHistory: true,
+ visitHistory: ["http://example.com/test"],
+ inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
+ userInput: "example.com",
+ autofilled: "example.com/test",
+ expected: "autofill_adaptive",
+ },
+ {
+ useAdaptiveHistory: true,
+ visitHistory: ["http://example.com/test"],
+ inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
+ userInput: "example.com/",
+ autofilled: "example.com/test",
+ expected: "autofill_adaptive",
+ },
+ {
+ useAdaptiveHistory: true,
+ visitHistory: ["http://example.com/test"],
+ inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
+ userInput: "example.com/test",
+ autofilled: "example.com/test",
+ expected: "autofill_adaptive",
+ },
+ {
+ useAdaptiveHistory: true,
+ visitHistory: ["http://example.com/test", "http://example.org/test"],
+ inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
+ userInput: "example.org",
+ autofilled: "example.org/",
+ expected: "autofill_origin",
+ },
+ {
+ useAdaptiveHistory: true,
+ visitHistory: ["http://example.com/test", "http://example.com/test/url"],
+ inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
+ userInput: "example.com/test/",
+ autofilled: "example.com/test/",
+ expected: "autofill_url",
+ },
+ {
+ useAdaptiveHistory: true,
+ visitHistory: [{ uri: "http://example.com/test" }],
+ inputHistory: [
+ { uri: "http://example.com/test", input: "http://example.com/test" },
+ ],
+ userInput: "http://example.com/test",
+ autofilled: "http://example.com/test",
+ expected: "autofill_adaptive",
+ },
+ {
+ useAdaptiveHistory: false,
+ visitHistory: [{ uri: "http://example.com/test" }],
+ inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
+ userInput: "example",
+ autofilled: "example.com/",
+ expected: "autofill_origin",
+ },
+ {
+ useAdaptiveHistory: false,
+ visitHistory: [{ uri: "http://example.com/test" }],
+ inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
+ userInput: "example.com/te",
+ autofilled: "example.com/test",
+ expected: "autofill_url",
+ },
+ ];
+
+ for (const {
+ useAdaptiveHistory,
+ visitHistory,
+ inputHistory,
+ userInput,
+ autofilled,
+ expected,
+ } of testData) {
+ const histograms = snapshotHistograms();
+
+ await PlacesTestUtils.addVisits(visitHistory);
+ for (const { uri, input } of inputHistory) {
+ await UrlbarUtils.addToInputHistory(uri, input);
+ }
+
+ UrlbarPrefs.set("autoFill.adaptiveHistory.enabled", useAdaptiveHistory);
+
+ await triggerAutofillAndPickResult(userInput, autofilled);
+
+ assertSearchTelemetryEmpty(histograms.search_hist);
+ assertTelemetryResults(
+ histograms,
+ expected,
+ 0,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enter
+ );
+
+ UrlbarPrefs.clear("autoFill.adaptiveHistory.enabled");
+ await PlacesTestUtils.clearInputHistory();
+ await PlacesUtils.history.clear();
+ }
+});
+
+// Checks about-page autofill (e.g., "about:about").
+add_task(async function about() {
+ let histograms = snapshotHistograms();
+ await triggerAutofillAndPickResult("about:abou", "about:about");
+
+ assertSearchTelemetryEmpty(histograms.search_hist);
+ assertTelemetryResults(
+ histograms,
+ "autofill_about",
+ 0,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enter
+ );
+
+ await PlacesUtils.history.clear();
+});
+
+// Checks preloaded sites autofill.
+add_task(async function preloaded() {
+ UrlbarPrefs.set("usepreloadedtopurls.enabled", true);
+ UrlbarPrefs.set("usepreloadedtopurls.expire_days", 100);
+ UrlbarProviderPreloadedSites.populatePreloadedSiteStorage([
+ ["http://example.com/", "Example"],
+ ]);
+
+ let histograms = snapshotHistograms();
+ await triggerAutofillAndPickResult("example", "example.com/");
+
+ assertSearchTelemetryEmpty(histograms.search_hist);
+ assertTelemetryResults(
+ histograms,
+ "autofill_preloaded",
+ 0,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enter
+ );
+
+ await PlacesUtils.history.clear();
+ UrlbarPrefs.clear("usepreloadedtopurls.enabled");
+ UrlbarPrefs.clear("usepreloadedtopurls.expire_days");
+});
+
+// Checks the "other" fallback, which shouldn't normally happen.
+add_task(async function other() {
+ let searchString = "exam";
+ let autofilledValue = "example.com/";
+ let provider = createOtherAutofillProvider(searchString, autofilledValue);
+ UrlbarProvidersManager.registerProvider(provider);
+
+ let histograms = snapshotHistograms();
+ await triggerAutofillAndPickResult(searchString, autofilledValue);
+
+ assertSearchTelemetryEmpty(histograms.search_hist);
+ assertTelemetryResults(
+ histograms,
+ "autofill_other",
+ 0,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enter
+ );
+
+ await PlacesUtils.history.clear();
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
+
+// Checks impression telemetry.
+add_task(async function impression() {
+ const testData = [
+ {
+ description: "Adaptive history autofill and pick it",
+ useAdaptiveHistory: true,
+ visitHistory: ["http://example.com/first", "http://example.com/second"],
+ inputHistory: [{ uri: "http://example.com/first", input: "exa" }],
+ userInput: "exa",
+ autofilled: "example.com/first",
+ expected: "autofill_adaptive",
+ },
+ {
+ description: "Adaptive history autofill but pick another result",
+ useAdaptiveHistory: true,
+ visitHistory: ["http://example.com/first", "http://example.com/second"],
+ inputHistory: [{ uri: "http://example.com/first", input: "exa" }],
+ userInput: "exa",
+ urlToSelect: "http://example.com/second",
+ autofilled: "example.com/first",
+ expected: "autofill_adaptive",
+ },
+ {
+ description: "Adaptive history autofill but not pick any result",
+ unpickResult: true,
+ useAdaptiveHistory: true,
+ visitHistory: ["http://example.com/first", "http://example.com/second"],
+ inputHistory: [{ uri: "http://example.com/first", input: "exa" }],
+ userInput: "exa",
+ autofilled: "example.com/first",
+ },
+ {
+ description: "Origin autofill and pick it",
+ visitHistory: ["http://example.com/first", "http://example.com/second"],
+ userInput: "exa",
+ autofilled: "example.com/",
+ expected: "autofill_origin",
+ },
+ {
+ description: "Origin autofill but pick another result",
+ visitHistory: ["http://example.com/first", "http://example.com/second"],
+ userInput: "exa",
+ urlToSelect: "http://example.com/second",
+ autofilled: "example.com/",
+ expected: "autofill_origin",
+ },
+ {
+ description: "Origin autofill but not pick any result",
+ unpickResult: true,
+ visitHistory: ["http://example.com/first", "http://example.com/second"],
+ userInput: "exa",
+ autofilled: "example.com/",
+ },
+ {
+ description: "URL autofill and pick it",
+ visitHistory: ["http://example.com/first", "http://example.com/second"],
+ userInput: "example.com/",
+ autofilled: "example.com/",
+ expected: "autofill_url",
+ },
+ {
+ description: "URL autofill but pick another result",
+ visitHistory: ["http://example.com/first", "http://example.com/second"],
+ userInput: "example.com/",
+ urlToSelect: "http://example.com/second",
+ autofilled: "example.com/",
+ expected: "autofill_url",
+ },
+ {
+ description: "URL autofill but not pick any result",
+ unpickResult: true,
+ visitHistory: ["http://example.com/first", "http://example.com/second"],
+ userInput: "example.com/",
+ autofilled: "example.com/",
+ },
+ {
+ description: "about page autofill and pick it",
+ userInput: "about:a",
+ autofilled: "about:about",
+ expected: "autofill_about",
+ },
+ {
+ description: "about page autofill but pick another result",
+ userInput: "about:a",
+ urlToSelect: "about:addons",
+ autofilled: "about:about",
+ expected: "autofill_about",
+ },
+ {
+ description: "about page autofill but not pick any result",
+ unpickResult: true,
+ userInput: "about:a",
+ autofilled: "about:about",
+ },
+ {
+ description: "Preloaded site autofill and pick it",
+ usePreloadedSite: true,
+ preloadedSites: [["http://example.com/", "Example"]],
+ userInput: "exa",
+ autofilled: "example.com/",
+ expected: "autofill_preloaded",
+ },
+ {
+ description: "Preloaded site autofill but not pick any result",
+ unpickResult: true,
+ usePreloadedSite: true,
+ preloadedSites: [["http://example.com/", "Example"]],
+ userInput: "exa",
+ autofilled: "example.com/",
+ },
+ {
+ description: "Other provider's autofill and pick it",
+ useOtherProvider: true,
+ userInput: "example",
+ autofilled: "example.com/",
+ expected: "autofill_other",
+ },
+ {
+ description: "Other provider's autofill but not pick any result",
+ unpickResult: true,
+ useOtherProvider: true,
+ userInput: "example",
+ autofilled: "example.com/",
+ },
+ ];
+
+ for (const {
+ description,
+ useAdaptiveHistory = false,
+ usePreloadedSite = false,
+ useOtherProvider = false,
+ unpickResult = false,
+ visitHistory,
+ inputHistory,
+ preloadedSites,
+ userInput,
+ select,
+ autofilled,
+ expected,
+ } of testData) {
+ info(description);
+
+ UrlbarPrefs.set("autoFill.adaptiveHistory.enabled", useAdaptiveHistory);
+ if (usePreloadedSite) {
+ UrlbarPrefs.set("usepreloadedtopurls.enabled", true);
+ UrlbarPrefs.set("usepreloadedtopurls.expire_days", 100);
+ }
+ let otherProvider;
+ if (useOtherProvider) {
+ otherProvider = createOtherAutofillProvider(userInput, autofilled);
+ UrlbarProvidersManager.registerProvider(otherProvider);
+ }
+
+ if (visitHistory) {
+ await PlacesTestUtils.addVisits(visitHistory);
+ }
+ if (inputHistory) {
+ for (const { uri, input } of inputHistory) {
+ await UrlbarUtils.addToInputHistory(uri, input);
+ }
+ }
+ if (preloadedSites) {
+ UrlbarProviderPreloadedSites.populatePreloadedSiteStorage(preloadedSites);
+ }
+
+ await triggerAutofillAndPickResult(
+ userInput,
+ autofilled,
+ unpickResult,
+ select
+ );
+
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ if (unpickResult) {
+ TelemetryTestUtils.assertScalarUnset(
+ scalars,
+ "urlbar.impression.autofill_adaptive"
+ );
+ TelemetryTestUtils.assertScalarUnset(
+ scalars,
+ "urlbar.impression.autofill_origin"
+ );
+ TelemetryTestUtils.assertScalarUnset(
+ scalars,
+ "urlbar.impression.autofill_url"
+ );
+ TelemetryTestUtils.assertScalarUnset(
+ scalars,
+ "urlbar.impression.autofill_about"
+ );
+ } else {
+ TelemetryTestUtils.assertScalar(
+ scalars,
+ `urlbar.impression.${expected}`,
+ 1
+ );
+ }
+
+ UrlbarPrefs.clear("autoFill.adaptiveHistory.enabled");
+ UrlbarPrefs.clear("usepreloadedtopurls.enabled");
+ UrlbarPrefs.clear("usepreloadedtopurls.expire_days");
+
+ if (otherProvider) {
+ UrlbarProvidersManager.unregisterProvider(otherProvider);
+ }
+
+ await PlacesTestUtils.clearInputHistory();
+ await PlacesUtils.history.clear();
+ }
+});
+
+// Checks autofill deletion telemetry.
+add_task(async function deletion() {
+ await PlacesTestUtils.addVisits(["http://example.com/"]);
+
+ info("Delete autofilled value by DELETE key");
+ await doDeletionTest({
+ firstSearchString: "exa",
+ firstAutofilledValue: "example.com/",
+ trigger: () => {
+ EventUtils.synthesizeKey("KEY_Delete");
+ Assert.equal(gURLBar.value, "exa");
+ },
+ expectedScalar: 1,
+ });
+
+ info("Delete autofilled value by BACKSPACE key");
+ await doDeletionTest({
+ firstSearchString: "exa",
+ firstAutofilledValue: "example.com/",
+ trigger: () => {
+ EventUtils.synthesizeKey("KEY_Backspace");
+ Assert.equal(gURLBar.value, "exa");
+ },
+ expectedScalar: 1,
+ });
+
+ info("Delete autofilled value twice");
+ await doDeletionTest({
+ firstSearchString: "exa",
+ firstAutofilledValue: "example.com/",
+ trigger: () => {
+ // Delete autofilled string.
+ EventUtils.synthesizeKey("KEY_Delete");
+ Assert.equal(gURLBar.value, "exa");
+
+ // Re-autofilling.
+ EventUtils.synthesizeKey("m");
+ Assert.equal(gURLBar.value, "example.com/");
+ Assert.equal(gURLBar.selectionStart, "exam".length);
+ Assert.equal(gURLBar.selectionEnd, "example.com/".length);
+
+ // Delete autofilled string again.
+ EventUtils.synthesizeKey("KEY_Backspace");
+ Assert.equal(gURLBar.value, "exam");
+ },
+ expectedScalar: 2,
+ });
+
+ info("Delete one char after unselecting autofilled string");
+ await doDeletionTest({
+ firstSearchString: "exa",
+ firstAutofilledValue: "example.com/",
+ trigger: () => {
+ // Cancel selection.
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ Assert.equal(gURLBar.selectionStart, "example.com/".length);
+ Assert.equal(gURLBar.selectionEnd, "example.com/".length);
+
+ EventUtils.synthesizeKey("KEY_Backspace");
+ Assert.equal(gURLBar.value, "example.com");
+ },
+ expectedScalar: 0,
+ });
+
+ info("Delete autofilled value after unselecting autofilled string");
+ await doDeletionTest({
+ firstSearchString: "exa",
+ firstAutofilledValue: "example.com/",
+ trigger: () => {
+ // Cancel selection.
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ Assert.equal(gURLBar.selectionStart, "example.com/".length);
+ Assert.equal(gURLBar.selectionEnd, "example.com/".length);
+
+ // Delete autofilled string one by one.
+ for (let i = 0; i < "mple.com/".length; i++) {
+ EventUtils.synthesizeKey("KEY_Backspace");
+ }
+ Assert.equal(gURLBar.value, "exa");
+ },
+ expectedScalar: 0,
+ });
+
+ info(
+ "Delete autofilled value after unselecting autofilled string then selecting them manually again"
+ );
+ await doDeletionTest({
+ firstSearchString: "exa",
+ firstAutofilledValue: "example.com/",
+ trigger: () => {
+ // Cancel selection.
+ const previousSelectionStart = gURLBar.selectionStart;
+ const previousSelectionEnd = gURLBar.selectionEnd;
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ Assert.equal(gURLBar.selectionStart, "example.com/".length);
+ Assert.equal(gURLBar.selectionEnd, "example.com/".length);
+
+ // Select same range again.
+ gURLBar.selectionStart = previousSelectionStart;
+ gURLBar.selectionEnd = previousSelectionEnd;
+
+ EventUtils.synthesizeKey("KEY_Backspace");
+ Assert.equal(gURLBar.value, "exa");
+ },
+ expectedScalar: 1,
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+async function doDeletionTest({
+ firstSearchString,
+ firstAutofilledValue,
+ trigger,
+ expectedScalar,
+}) {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: firstSearchString,
+ fireInputEvent: true,
+ });
+ const details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ Assert.ok(details.autofill, "Result is autofill");
+ Assert.equal(gURLBar.value, firstAutofilledValue, "gURLBar.value");
+ Assert.equal(
+ gURLBar.selectionStart,
+ firstSearchString.length,
+ "selectionStart"
+ );
+ Assert.equal(
+ gURLBar.selectionEnd,
+ firstAutofilledValue.length,
+ "selectionEnd"
+ );
+
+ await trigger();
+
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ if (expectedScalar) {
+ TelemetryTestUtils.assertScalar(
+ scalars,
+ "urlbar.autofill_deletion",
+ expectedScalar
+ );
+ } else {
+ TelemetryTestUtils.assertScalarUnset(scalars, "urlbar.autofill_deletion");
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window);
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_dynamic.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_dynamic.js
new file mode 100644
index 0000000000..d4f4e77d57
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_dynamic.js
@@ -0,0 +1,136 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This file tests urlbar telemetry for dynamic results.
+ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.sys.mjs",
+ UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+ UrlbarView: "resource:///modules/UrlbarView.sys.mjs",
+});
+
+const DYNAMIC_TYPE_NAME = "test";
+
+/**
+ * A test URLBar provider.
+ */
+class TestProvider extends UrlbarTestUtils.TestProvider {
+ constructor() {
+ super({
+ priority: Infinity,
+ results: [
+ Object.assign(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.DYNAMIC,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ dynamicType: DYNAMIC_TYPE_NAME,
+ }
+ ),
+ { heuristic: true }
+ ),
+ ],
+ });
+ }
+
+ getViewUpdate(result, idsByName) {
+ return {
+ title: {
+ textContent: "This is a dynamic result.",
+ },
+ button: {
+ textContent: "Click Me",
+ },
+ };
+ }
+}
+
+add_task(async function test() {
+ // Add a dynamic result type.
+ UrlbarResult.addDynamicResultType(DYNAMIC_TYPE_NAME);
+ UrlbarView.addDynamicViewTemplate(DYNAMIC_TYPE_NAME, {
+ stylesheet:
+ getRootDirectory(gTestPath) + "urlbarTelemetryUrlbarDynamic.css",
+ children: [
+ {
+ name: "title",
+ tag: "span",
+ },
+ {
+ name: "buttonSpacer",
+ tag: "span",
+ },
+ {
+ name: "button",
+ tag: "span",
+ attributes: {
+ role: "button",
+ },
+ },
+ ],
+ });
+ registerCleanupFunction(() => {
+ UrlbarView.removeDynamicViewTemplate(DYNAMIC_TYPE_NAME);
+ UrlbarResult.removeDynamicResultType(DYNAMIC_TYPE_NAME);
+ });
+
+ // Register a provider that returns the dynamic result type.
+ let provider = new TestProvider();
+ UrlbarProvidersManager.registerProvider(provider);
+ registerCleanupFunction(() => {
+ UrlbarProvidersManager.unregisterProvider(provider);
+ });
+
+ const histograms = snapshotHistograms();
+
+ // Do a search to show the dynamic result.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: "test",
+ fireInputEvent: true,
+ });
+
+ // Press enter on the result's button. It will be preselected since the
+ // result is the heuristic.
+ await UrlbarTestUtils.promisePopupClose(window, () =>
+ EventUtils.synthesizeKey("KEY_Enter")
+ );
+
+ assertTelemetryResults(
+ histograms,
+ "dynamic",
+ 0,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enter
+ );
+
+ // Clean up for subsequent tests.
+ gURLBar.handleRevert();
+});
+
+function snapshotHistograms() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ return {
+ resultMethodHist: TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ ),
+ };
+}
+
+function assertTelemetryResults(histograms, type, index, method) {
+ TelemetryTestUtils.assertHistogram(histograms.resultMethodHist, method, 1);
+
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ `urlbar.picked.${type}`,
+ index,
+ 1
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_extension.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_extension.js
new file mode 100644
index 0000000000..28eae06a6f
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_extension.js
@@ -0,0 +1,155 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This file tests urlbar telemetry with extension actions.
+ */
+
+"use strict";
+
+const SCALAR_URLBAR = "browser.engagement.navigation.urlbar";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+});
+
+function assertSearchTelemetryEmpty(search_hist) {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+ Assert.ok(
+ !(SCALAR_URLBAR in scalars),
+ `Should not have recorded ${SCALAR_URLBAR}`
+ );
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar",
+ undefined
+ );
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.alias",
+ undefined
+ );
+
+ // Also check events.
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ false
+ );
+ events = (events.parent || []).filter(
+ e => e[1] == "navigation" && e[2] == "search"
+ );
+ Assert.deepEqual(
+ events,
+ [],
+ "Should not have recorded any navigation search events"
+ );
+}
+
+function snapshotHistograms() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ return {
+ resultMethodHist: TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ ),
+ search_hist: TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS"),
+ };
+}
+
+function assertTelemetryResults(histograms, type, index, method) {
+ TelemetryTestUtils.assertHistogram(histograms.resultMethodHist, method, 1);
+
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ `urlbar.picked.${type}`,
+ index,
+ 1
+ );
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Disable search suggestions in the urlbar.
+ ["browser.urlbar.suggest.searches", false],
+ // Clear historical search suggestions to avoid interference from previous
+ // tests.
+ ["browser.urlbar.maxHistoricalSearchSuggestions", 0],
+ // Turn autofill off.
+ ["browser.urlbar.autoFill", false],
+ ],
+ });
+
+ // Enable local telemetry recording for the duration of the tests.
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+
+ // Enable event recording for the events tested here.
+ Services.telemetry.setEventRecordingEnabled("navigation", true);
+
+ // Clear history so that history added by previous tests doesn't mess up this
+ // test when it selects results in the urlbar.
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ // Make sure to restore the engine once we're done.
+ registerCleanupFunction(async function () {
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ Services.telemetry.setEventRecordingEnabled("navigation", false);
+ });
+});
+
+add_task(async function test_extension() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ omnibox: {
+ keyword: "omniboxtest",
+ },
+
+ background() {
+ /* global browser */
+ browser.omnibox.setDefaultSuggestion({
+ description: "doit",
+ });
+ // Just do nothing for this test.
+ browser.omnibox.onInputEntered.addListener(() => {});
+ browser.omnibox.onInputChanged.addListener((text, suggest) => {
+ suggest([]);
+ });
+ },
+ },
+ });
+
+ await extension.startup();
+
+ const histograms = snapshotHistograms();
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: "omniboxtest ",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ assertSearchTelemetryEmpty(histograms.search_hist);
+ assertTelemetryResults(
+ histograms,
+ "extension",
+ 0,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enter
+ );
+
+ await extension.unload();
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_handoff.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_handoff.js
new file mode 100644
index 0000000000..c2e1413a27
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_handoff.js
@@ -0,0 +1,182 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SearchSERPTelemetry } = ChromeUtils.importESModule(
+ "resource:///modules/SearchSERPTelemetry.sys.mjs"
+);
+
+const TEST_PROVIDER_INFO = [
+ {
+ telemetryId: "example",
+ searchPageRegexp:
+ /^https:\/\/example.com\/browser\/browser\/components\/search\/test\/browser\/searchTelemetry(?:Ad)?.html/,
+ queryParamName: "s",
+ codeParamName: "abc",
+ taggedCodes: ["ff"],
+ followOnParamNames: ["a"],
+ extraAdServersRegexps: [/^https:\/\/example\.com\/ad2?/],
+ },
+];
+
+function getPageUrl(useAdPage = false) {
+ let page = useAdPage ? "searchTelemetryAd.html" : "searchTelemetry.html";
+ return `https://example.com/browser/browser/components/search/test/browser/${page}`;
+}
+
+// sharedData messages are only passed to the child on idle. Therefore
+// we wait for a few idles to try and ensure the messages have been able
+// to be passed across and handled.
+async function waitForIdle() {
+ for (let i = 0; i < 10; i++) {
+ await new Promise(resolve => Services.tm.idleDispatchToMainThread(resolve));
+ }
+}
+
+add_setup(async function () {
+ SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO);
+ await waitForIdle();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar",
+ true,
+ ],
+ ],
+ });
+
+ await SearchTestUtils.installSearchExtension(
+ {
+ search_url: getPageUrl(true),
+ search_url_get_params: "s={searchTerms}&abc=ff",
+ suggest_url:
+ "https://example.com/browser/browser/components/search/test/browser/searchSuggestionEngine.sjs",
+ suggest_url_get_params: "query={searchTerms}",
+ },
+ { setAsDefault: true }
+ );
+
+ const oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+ Services.telemetry.setEventRecordingEnabled("navigation", true);
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ Services.telemetry.setEventRecordingEnabled("navigation", false);
+
+ SearchSERPTelemetry.overrideSearchTelemetryForTests();
+
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ });
+});
+
+add_task(async function test_search() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+
+ const histogram =
+ TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
+
+ info("Load about:newtab in new window");
+ const newtab = "about:newtab";
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, newtab);
+ await BrowserTestUtils.browserStopped(tab.linkedBrowser, newtab);
+
+ info("Focus on search input in newtab content");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const searchInput = content.document.querySelector(".fake-editable");
+ searchInput.click();
+ });
+
+ info("Search and wait the result");
+ const onLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeKey("q");
+ EventUtils.synthesizeKey("VK_RETURN");
+ await onLoaded;
+
+ info("Check the telemetries");
+ await assertHandoffResult(histogram);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_search_private_mode() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+
+ const histogram =
+ TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
+
+ info("Open private window");
+ let privateWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let tab = privateWindow.gBrowser.selectedTab;
+
+ info("Focus on search input in newtab content");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ const searchInput = content.document.querySelector(".fake-editable");
+ searchInput.click();
+ });
+
+ info("Search and wait the result");
+ const onLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeKey("q", {}, privateWindow);
+ EventUtils.synthesizeKey("VK_RETURN", {}, privateWindow);
+ await onLoaded;
+
+ info("Check the telemetries");
+ await assertHandoffResult(histogram);
+
+ await BrowserTestUtils.closeWindow(privateWindow);
+});
+
+async function assertHandoffResult(histogram) {
+ await assertScalars([
+ ["browser.engagement.navigation.urlbar_handoff", "search_enter", 1],
+ ["browser.search.content.urlbar_handoff", "example:tagged:ff", 1],
+ ]);
+ await assertHistogram(histogram, [["other-Example.urlbar-handoff", 1]]);
+ TelemetryTestUtils.assertEvents(
+ [
+ [
+ "navigation",
+ "search",
+ "urlbar_handoff",
+ "enter",
+ { engine: "other-Example" },
+ ],
+ ],
+ { category: "navigation", method: "search" }
+ );
+}
+
+async function assertHistogram(histogram, expectedResults) {
+ await TestUtils.waitForCondition(() => {
+ const snapshot = histogram.snapshot();
+ return expectedResults.every(([key]) => key in snapshot);
+ }, "Wait until the histogram has expected keys");
+
+ for (const [key, value] of expectedResults) {
+ TelemetryTestUtils.assertKeyedHistogramSum(histogram, key, value);
+ }
+}
+
+async function assertScalars(expectedResults) {
+ await TestUtils.waitForCondition(() => {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ return expectedResults.every(([scalarName]) => scalarName in scalars);
+ }, "Wait until the scalars have expected keyed scalars");
+
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+
+ for (const [scalarName, key, value] of expectedResults) {
+ TelemetryTestUtils.assertKeyedScalar(scalars, scalarName, key, value);
+ }
+}
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_persisted.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_persisted.js
new file mode 100644
index 0000000000..904e774a2c
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_persisted.js
@@ -0,0 +1,270 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file tests browser.engagement.navigation.urlbar_persisted and the
+ * event navigation.search.urlbar_persisted
+ */
+
+"use strict";
+
+const { SearchSERPTelemetry } = ChromeUtils.importESModule(
+ "resource:///modules/SearchSERPTelemetry.sys.mjs"
+);
+
+const SCALAR_URLBAR_PERSISTED =
+ "browser.engagement.navigation.urlbar_persisted";
+
+const SEARCH_STRING = "chocolate";
+
+let testEngine;
+add_setup(async () => {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.showSearchTerms.featureGate", true]],
+ });
+
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: "MozSearch",
+ search_url: "https://www.example.com/",
+ search_url_get_params: "q={searchTerms}&pc=fake_code",
+ },
+ { setAsDefault: true }
+ );
+
+ testEngine = Services.search.getEngineByName("MozSearch");
+
+ // Enable event recording for the events.
+ Services.telemetry.setEventRecordingEnabled("navigation", true);
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ Services.telemetry.setEventRecordingEnabled("navigation", false);
+ });
+});
+
+async function searchForString(searchString, tab) {
+ info(`Search for string: ${searchString}.`);
+ let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(
+ testEngine,
+ searchString
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedSearchUrl
+ );
+ gURLBar.focus();
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+ await browserLoadedPromise;
+ info("Finished loading search.");
+ return expectedSearchUrl;
+}
+
+async function gotoUrl(url, tab) {
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ url
+ );
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, url);
+ await browserLoadedPromise;
+ info(`Loaded page: ${url}`);
+}
+
+async function goBack(browser) {
+ let pageShowPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow"
+ );
+ browser.goBack();
+ await pageShowPromise;
+ info("Go back a page.");
+}
+
+async function goForward(browser) {
+ let pageShowPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow"
+ );
+ browser.goForward();
+ await pageShowPromise;
+ info("Go forward a page.");
+}
+
+function assertScalarSearchEnter(number) {
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ SCALAR_URLBAR_PERSISTED,
+ "search_enter",
+ number
+ );
+}
+
+function assertScalarDoesNotExist(scalar) {
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+ Assert.ok(!(scalar in scalars), scalar + " must not be recorded.");
+}
+
+function assertTelemetryEvents() {
+ TelemetryTestUtils.assertEvents(
+ [
+ [
+ "navigation",
+ "search",
+ "urlbar",
+ "enter",
+ { engine: "other-MozSearch" },
+ ],
+ [
+ "navigation",
+ "search",
+ "urlbar_persisted",
+ "enter",
+ { engine: "other-MozSearch" },
+ ],
+ ],
+ {
+ category: "navigation",
+ method: "search",
+ }
+ );
+}
+
+// A user making a search after making a search should result
+// in the telemetry being recorded.
+add_task(async function search_after_search() {
+ let search_hist =
+ TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await searchForString(SEARCH_STRING, tab);
+
+ // Scalar should not exist from a blank page, only when a search
+ // is conducted from a default SERP.
+ await assertScalarDoesNotExist(SCALAR_URLBAR_PERSISTED);
+
+ // After the first search, we should expect the SAP to change
+ // because the search term should show up on the SERP.
+ await searchForString(SEARCH_STRING, tab);
+ assertScalarSearchEnter(1);
+
+ // Check search counts.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar-persisted",
+ 1
+ );
+
+ // Check events.
+ assertTelemetryEvents();
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// A user going to a tab that contains a SERP should
+// trigger the telemetry when conducting a search.
+add_task(async function switch_to_tab_and_search() {
+ let search_hist =
+ TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
+
+ const tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await searchForString(SEARCH_STRING, tab1);
+
+ const tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await gotoUrl("https://www.example.com/some-place", tab2);
+
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+ await searchForString(SEARCH_STRING, tab1);
+ assertScalarSearchEnter(1);
+
+ // Check search count.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar-persisted",
+ 1
+ );
+
+ // Check events.
+ assertTelemetryEvents();
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+// When a user reverts the Urlbar after the search terms persist,
+// conducting another search should still be registered as a
+// urlbar-persisted SAP.
+add_task(async function handle_revert() {
+ let search_hist =
+ TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await searchForString(SEARCH_STRING, tab);
+
+ gURLBar.handleRevert();
+ await searchForString(SEARCH_STRING, tab);
+
+ assertScalarSearchEnter(1);
+
+ // Check search count.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar-persisted",
+ 1
+ );
+
+ // Check events.
+ assertTelemetryEvents();
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// A user going back and forth in history should trigger
+// urlbar-persisted telemetry when returning to a SERP
+// and conducting a search.
+add_task(async function back_and_forth() {
+ let search_hist =
+ TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // Create three pages in history: a page, a SERP, and a page.
+ await gotoUrl("https://www.example.com/some-place", tab);
+ await searchForString(SEARCH_STRING, tab);
+ await gotoUrl("https://www.example.com/another-page", tab);
+
+ // Go back to the SERP by using both back and forward.
+ await goBack(tab.linkedBrowser);
+ await goBack(tab.linkedBrowser);
+ await goForward(tab.linkedBrowser);
+ await assertScalarDoesNotExist(SCALAR_URLBAR_PERSISTED);
+
+ // Then do a search.
+ await searchForString(SEARCH_STRING, tab);
+ assertScalarSearchEnter(1);
+
+ // Check search count.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar-persisted",
+ 1
+ );
+
+ // Check events.
+ assertTelemetryEvents();
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_places.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_places.js
new file mode 100644
index 0000000000..26500033eb
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_places.js
@@ -0,0 +1,270 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This file tests urlbar telemetry with places related actions (e.g. history/
+ * bookmark selection).
+ */
+
+"use strict";
+
+const SCALAR_URLBAR = "browser.engagement.navigation.urlbar";
+
+const TEST_URL = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://mochi.test:8888"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+});
+
+function searchInAwesomebar(value, win = window) {
+ return UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ waitForFocus,
+ value,
+ fireInputEvent: true,
+ });
+}
+
+function assertSearchTelemetryEmpty(search_hist) {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+ Assert.ok(
+ !(SCALAR_URLBAR in scalars),
+ `Should not have recorded ${SCALAR_URLBAR}`
+ );
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar",
+ undefined
+ );
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.alias",
+ undefined
+ );
+
+ // Also check events.
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ false
+ );
+ events = (events.parent || []).filter(
+ e => e[1] == "navigation" && e[2] == "search"
+ );
+ Assert.deepEqual(
+ events,
+ [],
+ "Should not have recorded any navigation search events"
+ );
+}
+
+function snapshotHistograms() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ return {
+ resultMethodHist: TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ ),
+ search_hist: TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS"),
+ };
+}
+
+function assertTelemetryResults(histograms, type, index, method) {
+ TelemetryTestUtils.assertHistogram(histograms.resultMethodHist, method, 1);
+
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ `urlbar.picked.${type}`,
+ index,
+ 1
+ );
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Disable search suggestions in the urlbar.
+ ["browser.urlbar.suggest.searches", false],
+ // Clear historical search suggestions to avoid interference from previous
+ // tests.
+ ["browser.urlbar.maxHistoricalSearchSuggestions", 0],
+ // Turn autofill off.
+ ["browser.urlbar.autoFill", false],
+ ],
+ });
+
+ // Enable local telemetry recording for the duration of the tests.
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+
+ // Enable event recording for the events tested here.
+ Services.telemetry.setEventRecordingEnabled("navigation", true);
+
+ // Clear history so that history added by previous tests doesn't mess up this
+ // test when it selects results in the urlbar.
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ await PlacesUtils.keywords.insert({
+ keyword: "get",
+ url: TEST_URL + "?q=%s",
+ });
+
+ // Make sure to restore the engine once we're done.
+ registerCleanupFunction(async function () {
+ await PlacesUtils.keywords.remove("get");
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ Services.telemetry.setEventRecordingEnabled("navigation", false);
+ });
+});
+
+add_task(async function test_history() {
+ const histograms = snapshotHistograms();
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ await PlacesTestUtils.addVisits([
+ {
+ uri: "http://example.com",
+ title: "example",
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ },
+ ]);
+
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("example");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ assertSearchTelemetryEmpty(histograms.search_hist);
+ assertTelemetryResults(
+ histograms,
+ "history",
+ 1,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.arrowEnterSelection
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_bookmark() {
+ const histograms = snapshotHistograms();
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ let bm = await PlacesUtils.bookmarks.insert({
+ url: "http://example.com",
+ title: "example",
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ });
+
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("example");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ assertSearchTelemetryEmpty(histograms.search_hist);
+ assertTelemetryResults(
+ histograms,
+ "bookmark",
+ 1,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.arrowEnterSelection
+ );
+
+ await PlacesUtils.bookmarks.remove(bm);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_keyword() {
+ const histograms = snapshotHistograms();
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("get example");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ assertSearchTelemetryEmpty(histograms.search_hist);
+ assertTelemetryResults(
+ histograms,
+ "keyword",
+ 0,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enter
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_switchtab() {
+ const histograms = snapshotHistograms();
+
+ let homeTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:buildconfig"
+ );
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+
+ let p = BrowserTestUtils.waitForEvent(gBrowser, "TabSwitchDone");
+ await searchInAwesomebar("about:buildconfig");
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ assertSearchTelemetryEmpty(histograms.search_hist);
+ assertTelemetryResults(
+ histograms,
+ "switchtab",
+ 1,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.arrowEnterSelection
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(homeTab);
+});
+
+add_task(async function test_visitURL() {
+ const histograms = snapshotHistograms();
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await searchInAwesomebar("http://example.com/a/");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ assertSearchTelemetryEmpty(histograms.search_hist);
+ assertTelemetryResults(
+ histograms,
+ "visiturl",
+ 0,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enter
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_quickactions.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_quickactions.js
new file mode 100644
index 0000000000..b29807900b
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_quickactions.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This file tests urlbar telemetry for quickactions.
+ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarProviderQuickActions:
+ "resource:///modules/UrlbarProviderQuickActions.sys.mjs",
+});
+
+let testActionCalled = 0;
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.quickactions", true],
+ ["browser.urlbar.quickactions.enabled", true],
+ ],
+ });
+
+ UrlbarProviderQuickActions.addAction("testaction", {
+ commands: ["testaction"],
+ label: "quickactions-downloads2",
+ onPick: () => testActionCalled++,
+ });
+
+ registerCleanupFunction(() => {
+ UrlbarProviderQuickActions.removeAction("testaction");
+ });
+});
+
+add_task(async function test() {
+ const histograms = snapshotHistograms();
+
+ // Do a search to show the quickaction.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "testaction",
+ waitForFocus,
+ fireInputEvent: true,
+ });
+
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+ });
+
+ Assert.equal(testActionCalled, 1, "Test action was called");
+
+ TelemetryTestUtils.assertHistogram(
+ histograms.resultMethodHist,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.arrowEnterSelection,
+ 1
+ );
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ `urlbar.picked.quickaction`,
+ 1,
+ 1
+ );
+
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "quickaction.picked",
+ "testaction-10",
+ 1
+ );
+
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "quickaction.impression",
+ "testaction-10",
+ 1
+ );
+
+ // Clean up for subsequent tests.
+ gURLBar.handleRevert();
+});
+
+add_task(async function test_impressions() {
+ UrlbarProviderQuickActions.addAction("testaction2", {
+ commands: ["testaction2"],
+ label: "quickactions-downloads2",
+ onPick: () => {},
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "testaction",
+ waitForFocus,
+ fireInputEvent: true,
+ });
+
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+ });
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
+
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "quickaction.impression",
+ `testaction-10`,
+ 1
+ );
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "quickaction.impression",
+ `testaction2-10`,
+ 1
+ );
+
+ UrlbarProviderQuickActions.removeAction("testaction2");
+ gURLBar.handleRevert();
+});
+
+function snapshotHistograms() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ return {
+ resultMethodHist: TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ ),
+ };
+}
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_remotetab.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_remotetab.js
new file mode 100644
index 0000000000..ffa3158f2b
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_remotetab.js
@@ -0,0 +1,185 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This file tests urlbar telemetry with remote tab action.
+ */
+
+"use strict";
+
+const SCALAR_URLBAR = "browser.engagement.navigation.urlbar";
+
+ChromeUtils.defineESModuleGetters(this, {
+ SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs",
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+});
+
+function assertSearchTelemetryEmpty(search_hist) {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+ Assert.ok(
+ !(SCALAR_URLBAR in scalars),
+ `Should not have recorded ${SCALAR_URLBAR}`
+ );
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.urlbar",
+ undefined
+ );
+ TelemetryTestUtils.assertKeyedHistogramSum(
+ search_hist,
+ "other-MozSearch.alias",
+ undefined
+ );
+
+ // Also check events.
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ false
+ );
+ events = (events.parent || []).filter(
+ e => e[1] == "navigation" && e[2] == "search"
+ );
+ Assert.deepEqual(
+ events,
+ [],
+ "Should not have recorded any navigation search events"
+ );
+}
+
+function snapshotHistograms() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ return {
+ resultMethodHist: TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ ),
+ search_hist: TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS"),
+ };
+}
+
+function assertTelemetryResults(histograms, type, index, method) {
+ TelemetryTestUtils.assertHistogram(histograms.resultMethodHist, method, 1);
+
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ `urlbar.picked.${type}`,
+ index,
+ 1
+ );
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Disable search suggestions in the urlbar.
+ ["browser.urlbar.suggest.searches", false],
+ // Clear historical search suggestions to avoid interference from previous
+ // tests.
+ ["browser.urlbar.maxHistoricalSearchSuggestions", 0],
+ // Turn autofill off.
+ ["browser.urlbar.autoFill", false],
+ // Special prefs for remote tabs.
+ ["services.sync.username", "fake"],
+ ["services.sync.syncedTabs.showRemoteTabs", true],
+ ],
+ });
+
+ // Enable local telemetry recording for the duration of the tests.
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+
+ // Enable event recording for the events tested here.
+ Services.telemetry.setEventRecordingEnabled("navigation", true);
+
+ // Clear history so that history added by previous tests doesn't mess up this
+ // test when it selects results in the urlbar.
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ const REMOTE_TAB = {
+ id: "7cqCr77ptzX3",
+ type: "client",
+ lastModified: 1492201200,
+ name: "zcarter's Nightly on MacBook-Pro-25",
+ clientType: "desktop",
+ tabs: [
+ {
+ type: "tab",
+ title: "Test Remote",
+ url: "http://example.com",
+ icon: UrlbarUtils.ICON.DEFAULT,
+ client: "7cqCr77ptzX3",
+ lastUsed: Math.floor(Date.now() / 1000),
+ },
+ ],
+ };
+
+ const sandbox = sinon.createSandbox();
+
+ let originalSyncedTabsInternal = SyncedTabs._internal;
+ SyncedTabs._internal = {
+ isConfiguredToSyncTabs: true,
+ hasSyncedThisSession: true,
+ getTabClients() {
+ return Promise.resolve([]);
+ },
+ syncTabs() {
+ return Promise.resolve();
+ },
+ };
+
+ // Tell the Sync XPCOM service it is initialized.
+ let weaveXPCService = Cc["@mozilla.org/weave/service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ let oldWeaveServiceReady = weaveXPCService.ready;
+ weaveXPCService.ready = true;
+
+ sandbox
+ .stub(SyncedTabs._internal, "getTabClients")
+ .callsFake(() => Promise.resolve(Cu.cloneInto([REMOTE_TAB], {})));
+
+ // Make sure to restore the engine once we're done.
+ registerCleanupFunction(async function () {
+ sandbox.restore();
+ weaveXPCService.ready = oldWeaveServiceReady;
+ SyncedTabs._internal = originalSyncedTabsInternal;
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ Services.telemetry.setEventRecordingEnabled("navigation", false);
+ });
+});
+
+add_task(async function test_remotetab() {
+ const histograms = snapshotHistograms();
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: "example",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await p;
+
+ assertSearchTelemetryEmpty(histograms.search_hist);
+ assertTelemetryResults(
+ histograms,
+ "remotetab",
+ 1,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.arrowEnterSelection
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_searchmode.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_searchmode.js
new file mode 100644
index 0000000000..7830102cf6
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_searchmode.js
@@ -0,0 +1,592 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This file tests the urlbar.searchmode.* scalars telemetry with search mode
+ * related actions.
+ */
+
+"use strict";
+
+const ENTRY_SCALAR_PREFIX = "urlbar.searchmode.";
+const PICKED_SCALAR_PREFIX = "urlbar.picked.searchmode.";
+const ENGINE_ALIAS = "alias";
+const TEST_QUERY = "test";
+let engineName;
+let engineDomain;
+
+// The preference to enable suggestions.
+const SUGGEST_PREF = "browser.search.suggest.enabled";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarProviderTabToSearch:
+ "resource:///modules/UrlbarProviderTabToSearch.sys.mjs",
+});
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "TouchBarHelper",
+ "@mozilla.org/widget/touchbarhelper;1",
+ "nsITouchBarHelper"
+);
+
+/**
+ * Asserts that search mode telemetry was recorded correctly. Checks both the
+ * urlbar.searchmode.* and urlbar.searchmode_picked.* probes.
+ *
+ * @param {string} entry
+ * A search mode entry point.
+ * @param {string} engineOrSource
+ * An engine name or a search mode source.
+ * @param {number} [resultIndex]
+ * The index of the result picked while in search mode. Only pass this
+ * parameter if a result is picked.
+ */
+function assertSearchModeScalars(entry, engineOrSource, resultIndex = -1) {
+ // Check if the urlbar.searchmode.entry scalar contains the expected value.
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ ENTRY_SCALAR_PREFIX + entry,
+ engineOrSource,
+ 1
+ );
+
+ for (let e of UrlbarUtils.SEARCH_MODE_ENTRY) {
+ if (e == entry) {
+ Assert.equal(
+ Object.keys(scalars[ENTRY_SCALAR_PREFIX + entry]).length,
+ 1,
+ `This search must only increment one entry in the correct scalar: ${e}`
+ );
+ } else {
+ Assert.ok(
+ !scalars[ENTRY_SCALAR_PREFIX + e],
+ `No other urlbar.searchmode scalars should be recorded. Checking ${e}`
+ );
+ }
+ }
+
+ if (resultIndex >= 0) {
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ PICKED_SCALAR_PREFIX + entry,
+ resultIndex,
+ 1
+ );
+ }
+
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Disable tab-to-search onboarding results for general tests. They are
+ // enabled in tests that specifically address onboarding.
+ ["browser.urlbar.tabToSearch.onboard.interactionsLeft", 0],
+ ],
+ });
+
+ // Create an engine to generate search suggestions and add it as default
+ // for this test.
+ let suggestionEngine = await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + "urlbarTelemetrySearchSuggestions.xml",
+ setAsDefault: true,
+ });
+ suggestionEngine.alias = ENGINE_ALIAS;
+ engineDomain = suggestionEngine.searchUrlDomain;
+ engineName = suggestionEngine.name;
+
+ // And the first one-off engine.
+ await Services.search.moveEngine(suggestionEngine, 0);
+
+ // Enable local telemetry recording for the duration of the tests.
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+
+ // Clear history so that history added by previous tests doesn't mess up this
+ // test when it selects results in the urlbar.
+ await PlacesUtils.history.clear();
+
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+
+ // Clear historical search suggestions to avoid interference from previous
+ // tests.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.maxHistoricalSearchSuggestions", 0]],
+ });
+
+ // Make sure to restore the engine once we're done.
+ registerCleanupFunction(async function () {
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ await PlacesUtils.history.clear();
+ Services.telemetry.setEventRecordingEnabled("navigation", false);
+ });
+});
+
+// Clicks the first one off.
+add_task(async function test_oneoff_remote() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_QUERY,
+ });
+ // Enters search mode by clicking a one-off.
+ await UrlbarTestUtils.enterSearchMode(window);
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ assertSearchModeScalars("oneoff", "other", 0);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Clicks the history one off.
+add_task(async function test_oneoff_local() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_QUERY,
+ });
+ // Enters search mode by clicking a one-off.
+ await UrlbarTestUtils.enterSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ });
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ assertSearchModeScalars("oneoff", "history", 0);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Checks that the Amazon search mode name is collapsed to "Amazon".
+add_task(async function test_oneoff_amazon() {
+ // Disable suggestions to avoid hitting Amazon servers.
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUGGEST_PREF, false]],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_QUERY,
+ });
+ // Enters search mode by clicking a one-off.
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: "Amazon.com",
+ });
+ assertSearchModeScalars("oneoff", "Amazon");
+ await UrlbarTestUtils.exitSearchMode(window);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// Checks that the Wikipedia search mode name is collapsed to "Wikipedia".
+add_task(async function test_oneoff_wikipedia() {
+ // Disable suggestions to avoid hitting Wikipedia servers.
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUGGEST_PREF, false]],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_QUERY,
+ });
+ // Enters search mode by clicking a one-off.
+ await UrlbarTestUtils.enterSearchMode(window, {
+ engineName: "Wikipedia (en)",
+ });
+ assertSearchModeScalars("oneoff", "Wikipedia");
+ await UrlbarTestUtils.exitSearchMode(window);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// Enters search mode by pressing the keyboard shortcut.
+add_task(async function test_shortcut() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_QUERY,
+ });
+ // Enter search mode by pressing the keyboard shortcut.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("k", { accelKey: true });
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName,
+ source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+ entry: "shortcut",
+ });
+ assertSearchModeScalars("shortcut", "other");
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Enters search mode by selecting a Top Site from the Urlbar.
+add_task(async function test_topsites_urlbar() {
+ // Disable suggestions to avoid hitting Amazon servers.
+ await SpecialPowers.pushPrefEnv({
+ set: [[SUGGEST_PREF, false]],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // Enter search mode by selecting a Top Site from the Urlbar.
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ if (gURLBar.getAttribute("pageproxystate") == "invalid") {
+ gURLBar.handleRevert();
+ }
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ let amazonSearch = await UrlbarTestUtils.waitForAutocompleteResultAt(
+ window,
+ 0
+ );
+ Assert.equal(
+ amazonSearch.result.payload.keyword,
+ "@amazon",
+ "First result should have the Amazon keyword."
+ );
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeMouseAtCenter(amazonSearch, {});
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: amazonSearch.result.payload.engine,
+ entry: "topsites_urlbar",
+ });
+ assertSearchModeScalars("topsites_urlbar", "Amazon");
+ await UrlbarTestUtils.exitSearchMode(window);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// Enters search mode by selecting a keyword offer result.
+add_task(async function test_keywordoffer() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // Do a search for "@" + our test alias. It should autofill with a trailing
+ // space, and the heuristic result should be an autofill result with a keyword
+ // offer.
+ let alias = "@" + ENGINE_ALIAS;
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: alias,
+ });
+ let keywordOfferResult = await UrlbarTestUtils.getDetailsOfResultAt(
+ window,
+ 0
+ );
+ Assert.equal(
+ keywordOfferResult.searchParams.keyword,
+ alias,
+ "The first result should be a keyword search result with the correct alias."
+ );
+
+ // Pick the keyword offer result.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName,
+ entry: "keywordoffer",
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ assertSearchModeScalars("keywordoffer", "other", 0);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Enters search mode by typing an alias.
+add_task(async function test_typed() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // Enter search mode by selecting a keywordoffer result.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: `${ENGINE_ALIAS} `,
+ });
+
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey(" ");
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName,
+ entry: "typed",
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ assertSearchModeScalars("typed", "other", 0);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Enters search mode by calling the same function called by the Search
+// Bookmarks menu item in Library > Bookmarks.
+add_task(async function test_bookmarkmenu() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ PlacesCommandHook.searchBookmarks();
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+ entry: "bookmarkmenu",
+ });
+ assertSearchModeScalars("bookmarkmenu", "bookmarks");
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Enters search mode by calling the same function called from a History
+// menu.
+add_task(async function test_historymenu() {
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ PlacesCommandHook.searchHistory();
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ entry: "historymenu",
+ });
+ assertSearchModeScalars("historymenu", "history");
+});
+
+// Enters search mode by calling the same function called by the Search Tabs
+// menu item in the tab overflow menu.
+add_task(async function test_tabmenu() {
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ gTabsPanel.searchTabs();
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.TABS,
+ entry: "tabmenu",
+ });
+ assertSearchModeScalars("tabmenu", "tabs");
+});
+
+// Enters search mode by performing a search handoff on about:privatebrowsing.
+// Note that handoff-to-search-mode only occurs when suggestions are disabled
+// in the Urlbar.
+// NOTE: We don't test handoff on about:home. Running mochitests on about:home
+// is quite difficult. This subtest verifies that `handoff` is a valid scalar
+// suffix and that a call to UrlbarInput.handoff(value, searchEngine) records
+// values in the urlbar.searchmode.handoff scalar. PlacesFeed.test.js verfies that
+// about:home handoff makes that exact call.
+add_task(async function test_handoff_pbm() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.searches", false]],
+ });
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ waitForTabURL: "about:privatebrowsing",
+ });
+ let tab = win.gBrowser.selectedBrowser;
+
+ await SpecialPowers.spawn(tab, [], async function () {
+ let btn = content.document.getElementById("search-handoff-button");
+ btn.click();
+ });
+
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+ await new Promise(r => EventUtils.synthesizeKey("f", {}, win, r));
+ await searchPromise;
+ await UrlbarTestUtils.assertSearchMode(win, {
+ engineName,
+ entry: "handoff",
+ });
+ assertSearchModeScalars("handoff", "other");
+
+ await UrlbarTestUtils.exitSearchMode(win);
+ await UrlbarTestUtils.promisePopupClose(win);
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+});
+
+// Enters search mode by tapping a search shortcut on the Touch Bar.
+add_task(async function test_touchbar() {
+ if (AppConstants.platform != "macosx") {
+ return;
+ }
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: TEST_QUERY,
+ });
+ // We have to fake the tap on the Touch Bar since mochitests have no way of
+ // interacting with the Touch Bar.
+ TouchBarHelper.insertRestrictionInUrlbar(UrlbarTokenizer.RESTRICT.HISTORY);
+ await UrlbarTestUtils.assertSearchMode(window, {
+ source: UrlbarUtils.RESULT_SOURCE.HISTORY,
+ entry: "touchbar",
+ });
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ assertSearchModeScalars("touchbar", "history", 0);
+ BrowserTestUtils.removeTab(tab);
+});
+
+// Enters search mode by selecting a tab-to-search result.
+// Tests that tab-to-search results preview search mode when highlighted. These
+// results are worth testing separately since they do not set the
+// payload.keyword parameter.
+add_task(async function test_tabtosearch() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Do not show the onboarding result for this subtest.
+ ["browser.urlbar.tabToSearch.onboard.interactionsLeft", 0],
+ ],
+ });
+ await PlacesTestUtils.addVisits([`http://${engineDomain}/`]);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: engineDomain.slice(0, 4),
+ });
+ let tabToSearchResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1)
+ ).result;
+ Assert.equal(
+ tabToSearchResult.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+ Assert.equal(
+ tabToSearchResult.payload.engine,
+ engineName,
+ "The tab-to-search result is for the correct engine."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 1,
+ "Sanity check: The second result is selected."
+ );
+ // Pick the tab-to-search result.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName,
+ entry: "tabtosearch",
+ });
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ assertSearchModeScalars("tabtosearch", "other", 0);
+
+ BrowserTestUtils.removeTab(tab);
+
+ await PlacesUtils.history.clear();
+ await SpecialPowers.popPrefEnv();
+});
+
+// Enters search mode by selecting a tab-to-search onboarding result.
+add_task(async function test_tabtosearch_onboard() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.tabToSearch.onboard.interactionsLeft", 3]],
+ });
+ await PlacesTestUtils.addVisits([`http://${engineDomain}/`]);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: engineDomain.slice(0, 4),
+ fireInputEvent: true,
+ });
+ let tabToSearchResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1)
+ ).result;
+ Assert.equal(
+ tabToSearchResult.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+ Assert.equal(
+ tabToSearchResult.payload.engine,
+ engineName,
+ "The tab-to-search result is for the correct engine."
+ );
+ Assert.equal(
+ tabToSearchResult.payload.dynamicType,
+ "onboardTabToSearch",
+ "The tab-to-search result is an onboarding result."
+ );
+ await UrlbarTestUtils.assertSearchMode(window, null);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 1,
+ "Sanity check: The second result is selected."
+ );
+ // Pick the tab-to-search onboarding result.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName,
+ entry: "tabtosearch_onboard",
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ assertSearchModeScalars("tabtosearch_onboard", "other", 0);
+
+ UrlbarPrefs.set("tabToSearch.onboard.interactionsLeft", 3);
+ delete UrlbarProviderTabToSearch.onboardingInteractionAtTime;
+
+ BrowserTestUtils.removeTab(tab);
+ await PlacesUtils.history.clear();
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_sponsored_topsites.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_sponsored_topsites.js
new file mode 100644
index 0000000000..74d1fdb0db
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_sponsored_topsites.js
@@ -0,0 +1,181 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ CONTEXTUAL_SERVICES_PING_TYPES:
+ "resource:///modules/PartnerLinkAttribution.sys.mjs",
+ NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
+ PartnerLinkAttribution: "resource:///modules/PartnerLinkAttribution.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ HttpServer: "resource://testing-common/httpd.js",
+});
+
+const EN_US_TOPSITES =
+ "https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://twitter.com/";
+
+// This is used for "sendAttributionRequest"
+var gHttpServer = null;
+var gRequests = [];
+
+function submitHandler(request, response) {
+ gRequests.push(request);
+ response.setStatusLine(request.httpVersion, 200, "Ok");
+}
+
+// Spy for telemetry sender
+let spy;
+
+add_setup(async function () {
+ sandbox = sinon.createSandbox();
+ spy = sandbox.spy(
+ PartnerLinkAttribution._pingCentre,
+ "sendStructuredIngestionPing"
+ );
+
+ let topsitesAttribution = Services.prefs.getStringPref(
+ "browser.partnerlink.campaign.topsites"
+ );
+ gHttpServer = new HttpServer();
+ gHttpServer.registerPathHandler(`/cid/${topsitesAttribution}`, submitHandler);
+ gHttpServer.start(-1);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.sponsoredTopSites", true],
+ ["browser.urlbar.suggest.topsites", true],
+ ["browser.newtabpage.activity-stream.default.sites", EN_US_TOPSITES],
+ [
+ "browser.partnerlink.attributionURL",
+ `http://localhost:${gHttpServer.identity.primaryPort}/cid/`,
+ ],
+ ],
+ });
+
+ await updateTopSites(
+ sites => sites && sites.length == EN_US_TOPSITES.split(",").length
+ );
+
+ registerCleanupFunction(async () => {
+ sandbox.restore();
+ await gHttpServer.stop();
+ gHttpServer = null;
+ });
+});
+
+add_task(async function send_impression_and_click() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ let link = {
+ label: "test_label",
+ url: "http://example.com/",
+ sponsored_position: 1,
+ sendAttributionRequest: true,
+ sponsored_tile_id: 42,
+ sponsored_impression_url: "http://impression.test.com/",
+ sponsored_click_url: "http://click.test.com/",
+ };
+ // Pin a sponsored TopSite to set up the test fixture
+ NewTabUtils.pinnedLinks.pin(link, 0);
+
+ await updateTopSites(sites => sites && sites[0] && sites[0].isPinned);
+
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ // Select the first result and confirm it.
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+
+ let loadPromise = BrowserTestUtils.waitForDocLoadAndStopIt(
+ result.url,
+ gBrowser.selectedBrowser
+ );
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+
+ Assert.ok(
+ spy.calledTwice,
+ "Should send an impression ping and a click ping"
+ );
+
+ // Validate the impression ping
+ let [payload, endpoint] = spy.firstCall.args;
+ Assert.ok(
+ endpoint.includes(CONTEXTUAL_SERVICES_PING_TYPES.TOPSITES_IMPRESSION),
+ "Should set the endpoint for TopSites impression"
+ );
+ Assert.ok(!!payload.context_id, "Should set the context_id");
+ Assert.equal(payload.advertiser, "test_label", "Should set the advertiser");
+ Assert.equal(
+ payload.reporting_url,
+ "http://impression.test.com/",
+ "Should set the impression reporting URL"
+ );
+ Assert.equal(payload.tile_id, 42, "Should set the tile_id");
+ Assert.equal(payload.position, 1, "Should set the position");
+
+ // Validate the click ping
+ [payload, endpoint] = spy.secondCall.args;
+ Assert.ok(
+ endpoint.includes(CONTEXTUAL_SERVICES_PING_TYPES.TOPSITES_SELECTION),
+ "Should set the endpoint for TopSites click"
+ );
+ Assert.ok(!!payload.context_id, "Should set the context_id");
+ Assert.equal(
+ payload.reporting_url,
+ "http://click.test.com/",
+ "Should set the click reporting URL"
+ );
+ Assert.equal(payload.tile_id, 42, "Should set the tile_id");
+ Assert.equal(payload.position, 1, "Should set the position");
+
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ gURLBar.blur();
+ });
+
+ NewTabUtils.pinnedLinks.unpin(link);
+ });
+});
+
+add_task(async function zero_ping() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ spy.resetHistory();
+
+ // Reload the TopSites
+ await updateTopSites(
+ sites => sites && sites.length == EN_US_TOPSITES.split(",").length
+ );
+
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+
+ await UrlbarTestUtils.promiseSearchComplete(window);
+
+ // Select the first result and confirm it.
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+
+ let loadPromise = BrowserTestUtils.waitForDocLoadAndStopIt(
+ result.url,
+ gBrowser.selectedBrowser
+ );
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+
+ Assert.ok(
+ spy.notCalled,
+ "Should not send any ping if there is no sponsored Top Site"
+ );
+
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ gURLBar.blur();
+ });
+ });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tabtosearch.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tabtosearch.js
new file mode 100644
index 0000000000..35607d2f94
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tabtosearch.js
@@ -0,0 +1,416 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This file tests telemetry for tabtosearch results.
+ * NB: This file does not test the search mode `entry` field for tab-to-search
+ * results. That is tested in browser_UsageTelemetry_urlbar_searchmode.js.
+ */
+
+"use strict";
+
+const ENGINE_NAME = "MozSearch";
+const ENGINE_DOMAIN = "example.com";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarProviderTabToSearch:
+ "resource:///modules/UrlbarProviderTabToSearch.sys.mjs",
+});
+
+function snapshotHistograms() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ return {
+ resultMethodHist: TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ ),
+ search_hist: TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS"),
+ };
+}
+
+function assertTelemetryResults(histograms, type, index, method) {
+ TelemetryTestUtils.assertHistogram(histograms.resultMethodHist, method, 1);
+
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ `urlbar.picked.${type}`,
+ index,
+ 1
+ );
+}
+
+/**
+ * Checks to see if the second result in the Urlbar is a tab-to-search result
+ * with the correct engine.
+ *
+ * @param {string} engineName
+ * The expected engine name.
+ * @param {boolean} [isOnboarding]
+ * If true, expects the tab-to-search result to be an onbarding result.
+ */
+async function checkForTabToSearchResult(engineName, isOnboarding) {
+ Assert.ok(UrlbarTestUtils.isPopupOpen(window), "Popup should be open.");
+ let tabToSearchResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1)
+ ).result;
+ Assert.equal(
+ tabToSearchResult.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+ Assert.equal(
+ tabToSearchResult.payload.engine,
+ engineName,
+ "The tab-to-search result is for the first engine."
+ );
+ if (isOnboarding) {
+ Assert.equal(
+ tabToSearchResult.payload.dynamicType,
+ "onboardTabToSearch",
+ "The tab-to-search result is an onboarding result."
+ );
+ } else {
+ Assert.ok(
+ !tabToSearchResult.payload.dynamicType,
+ "The tab-to-search result should not be an onboarding result."
+ );
+ }
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.tabToSearch.onboard.interactionsLeft", 0]],
+ });
+
+ await SearchTestUtils.installSearchExtension({
+ name: ENGINE_NAME,
+ search_url: `https://${ENGINE_DOMAIN}/`,
+ });
+
+ // Reset the enginesShown sets in case a previous test showed a tab-to-search
+ // result but did not end its engagement.
+ UrlbarProviderTabToSearch.enginesShown.regular.clear();
+ UrlbarProviderTabToSearch.enginesShown.onboarding.clear();
+
+ // Enable local telemetry recording for the duration of the tests.
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+
+ registerCleanupFunction(async () => {
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ });
+});
+
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ const histograms = snapshotHistograms();
+
+ for (let i = 0; i < 3; i++) {
+ await PlacesTestUtils.addVisits([`https://${ENGINE_DOMAIN}/`]);
+ }
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: ENGINE_DOMAIN.slice(0, 4),
+ fireInputEvent: true,
+ });
+
+ let tabToSearchResult = (
+ await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1)
+ ).result;
+ Assert.equal(
+ tabToSearchResult.providerName,
+ "TabToSearch",
+ "The second result is a tab-to-search result."
+ );
+ Assert.equal(
+ tabToSearchResult.payload.engine,
+ ENGINE_NAME,
+ "The tab-to-search result is for the correct engine."
+ );
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 1,
+ "Sanity check: The second result is selected."
+ );
+
+ // Select the tab-to-search result.
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await searchPromise;
+
+ await UrlbarTestUtils.assertSearchMode(window, {
+ engineName: ENGINE_NAME,
+ entry: "tabtosearch",
+ });
+
+ assertTelemetryResults(
+ histograms,
+ "tabtosearch",
+ 1,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.arrowEnterSelection
+ );
+
+ await UrlbarTestUtils.exitSearchMode(window);
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ gURLBar.blur();
+ });
+ await PlacesUtils.history.clear();
+ });
+
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+});
+
+add_task(async function impressions() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.tabToSearch.onboard.interactionsLeft", 0]],
+ });
+ await impressions_test(false);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function onboarding_impressions() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.tabToSearch.onboard.interactionsLeft", 3]],
+ });
+ await impressions_test(true);
+ await SpecialPowers.popPrefEnv();
+ delete UrlbarProviderTabToSearch.onboardingInteractionAtTime;
+});
+
+async function impressions_test(isOnboarding) {
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ const firstEngineHost = "example";
+ let extension = await SearchTestUtils.installSearchExtension(
+ {
+ name: `${ENGINE_NAME}2`,
+ search_url: `https://${firstEngineHost}-2.com/`,
+ },
+ { skipUnload: true }
+ );
+
+ for (let i = 0; i < 3; i++) {
+ await PlacesTestUtils.addVisits([`https://${firstEngineHost}-2.com`]);
+ await PlacesTestUtils.addVisits([`https://${ENGINE_DOMAIN}/`]);
+ }
+
+ // First do multiple searches for substrings of firstEngineHost. The view
+ // should show the same tab-to-search onboarding result the entire time, so
+ // we should not continue to increment urlbar.tips.
+ for (let i = 1; i < firstEngineHost.length; i++) {
+ info(
+ `Search for "${firstEngineHost.slice(
+ 0,
+ i
+ )}". Only record one impression for this sequence.`
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: firstEngineHost.slice(0, i),
+ fireInputEvent: true,
+ });
+ await checkForTabToSearchResult(ENGINE_NAME, isOnboarding);
+ }
+
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "urlbar.tips",
+ isOnboarding ? "tabtosearch_onboard-shown" : "tabtosearch-shown",
+ 1
+ );
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ isOnboarding
+ ? "urlbar.tabtosearch.impressions_onboarding"
+ : "urlbar.tabtosearch.impressions",
+ // "other" is recorded as the engine name because we're not using a built-in engine.
+ "other",
+ 1
+ );
+
+ info("Type through autofill to second engine hostname. Record impression.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: firstEngineHost,
+ fireInputEvent: true,
+ });
+ await checkForTabToSearchResult(ENGINE_NAME, isOnboarding);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: `${firstEngineHost}-`,
+ fireInputEvent: true,
+ });
+ await checkForTabToSearchResult(`${ENGINE_NAME}2`, isOnboarding);
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ // Since the user typed past the autofill for the first engine, we showed a
+ // different onboarding result and now we increment
+ // tabtosearch_onboard-shown.
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "urlbar.tips",
+ isOnboarding ? "tabtosearch_onboard-shown" : "tabtosearch-shown",
+ 3
+ );
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ isOnboarding
+ ? "urlbar.tabtosearch.impressions_onboarding"
+ : "urlbar.tabtosearch.impressions",
+ "other",
+ 3
+ );
+
+ info("Make a typo and return to autofill. Do not record impression.");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: `${firstEngineHost}-`,
+ fireInputEvent: true,
+ });
+ await checkForTabToSearchResult(`${ENGINE_NAME}2`, isOnboarding);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: `${firstEngineHost}-3`,
+ fireInputEvent: true,
+ });
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ 1,
+ "We are not showing a tab-to-search result."
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: `${firstEngineHost}-2`,
+ fireInputEvent: true,
+ });
+ await checkForTabToSearchResult(`${ENGINE_NAME}2`, isOnboarding);
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "urlbar.tips",
+ isOnboarding ? "tabtosearch_onboard-shown" : "tabtosearch-shown",
+ 4
+ );
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ isOnboarding
+ ? "urlbar.tabtosearch.impressions_onboarding"
+ : "urlbar.tabtosearch.impressions",
+ "other",
+ 4
+ );
+
+ info(
+ "Cancel then restart autofill. Continue to show the tab-to-search result."
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: `${firstEngineHost}-2`,
+ fireInputEvent: true,
+ });
+ await checkForTabToSearchResult(`${ENGINE_NAME}2`, isOnboarding);
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey("KEY_Backspace");
+ await searchPromise;
+ await checkForTabToSearchResult(`${ENGINE_NAME}2`, isOnboarding);
+ searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ // Type the "." from `example-2.com`.
+ EventUtils.synthesizeKey(".");
+ await searchPromise;
+ await checkForTabToSearchResult(`${ENGINE_NAME}2`, isOnboarding);
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "urlbar.tips",
+ isOnboarding ? "tabtosearch_onboard-shown" : "tabtosearch-shown",
+ 5
+ );
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ isOnboarding
+ ? "urlbar.tabtosearch.impressions_onboarding"
+ : "urlbar.tabtosearch.impressions",
+ // "other" is recorded as the engine name because we're not using a built-in engine.
+ "other",
+ 5
+ );
+
+ // See javadoc for UrlbarProviderTabToSearch.onEngagement for discussion
+ // about retained results.
+ info("Reopen the result set with retained results. Record impression.");
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ await checkForTabToSearchResult(`${ENGINE_NAME}2`, isOnboarding);
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "urlbar.tips",
+ isOnboarding ? "tabtosearch_onboard-shown" : "tabtosearch-shown",
+ 6
+ );
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ isOnboarding
+ ? "urlbar.tabtosearch.impressions_onboarding"
+ : "urlbar.tabtosearch.impressions",
+ "other",
+ 6
+ );
+
+ info(
+ "Open a result page and then autofill engine host. Record impression."
+ );
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: firstEngineHost,
+ fireInputEvent: true,
+ });
+ await checkForTabToSearchResult(ENGINE_NAME, isOnboarding);
+ // Press enter on the heuristic result so we visit example.com without
+ // doing an additional search.
+ let loadPromise = BrowserTestUtils.browserLoaded(browser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ // Click the Urlbar and type to simulate what a user would actually do. If
+ // we use promiseAutocompleteResultPopup, no query would be made between
+ // this one and the previous tab-to-search query. Thus
+ // `onboardingEnginesShown` would not be cleared. This would not happen
+ // in real-world usage.
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+ searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ EventUtils.synthesizeKey(firstEngineHost.slice(0, 4));
+ await searchPromise;
+ await checkForTabToSearchResult(ENGINE_NAME, isOnboarding);
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ // We clear the scalar this time.
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ "urlbar.tips",
+ isOnboarding ? "tabtosearch_onboard-shown" : "tabtosearch-shown",
+ 8
+ );
+ TelemetryTestUtils.assertKeyedScalar(
+ scalars,
+ isOnboarding
+ ? "urlbar.tabtosearch.impressions_onboarding"
+ : "urlbar.tabtosearch.impressions",
+ "other",
+ 8
+ );
+
+ await PlacesUtils.history.clear();
+ await extension.unload();
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tip.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tip.js
new file mode 100644
index 0000000000..c234bc3ed8
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tip.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This file tests urlbar telemetry for tip results.
+ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarProvider: "resource:///modules/UrlbarUtils.sys.mjs",
+ UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.sys.mjs",
+ UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+});
+
+function snapshotHistograms() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ return {
+ resultMethodHist: TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ ),
+ search_hist: TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS"),
+ };
+}
+
+function assertTelemetryResults(histograms, type, index, method) {
+ TelemetryTestUtils.assertHistogram(histograms.resultMethodHist, method, 1);
+
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ `urlbar.picked.${type}`,
+ index,
+ 1
+ );
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Disable search suggestions in the urlbar.
+ ["browser.urlbar.suggest.searches", false],
+ // Turn autofill off.
+ ["browser.urlbar.autoFill", false],
+ ],
+ });
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+});
+
+add_task(async function test() {
+ // Add a restricting provider that returns a preselected heuristic tip result.
+ let provider = new TipProvider([
+ Object.assign(
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.TIP,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ helpUrl: "https://example.com/",
+ type: "test",
+ titleL10n: { id: "urlbar-search-tips-confirm" },
+ buttons: [
+ {
+ url: "https://example.com/",
+ l10n: { id: "urlbar-search-tips-confirm" },
+ },
+ ],
+ }
+ ),
+ { heuristic: true }
+ ),
+ ]);
+ UrlbarProvidersManager.registerProvider(provider);
+
+ const histograms = snapshotHistograms();
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ // Show the view and press enter to select the tip.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value: "test",
+ fireInputEvent: true,
+ });
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ assertTelemetryResults(
+ histograms,
+ "tip",
+ 0,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.enter
+ );
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * A test URLBar provider.
+ */
+class TipProvider extends UrlbarProvider {
+ constructor(results) {
+ super();
+ this._results = results;
+ }
+ get name() {
+ return "TestProviderTip";
+ }
+ get type() {
+ return UrlbarUtils.PROVIDER_TYPE.PROFILE;
+ }
+ isActive(context) {
+ return true;
+ }
+ getPriority(context) {
+ return 1;
+ }
+ async startQuery(context, addCallback) {
+ context.preselected = true;
+ for (const result of this._results) {
+ addCallback(this, result);
+ }
+ }
+}
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_topsite.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_topsite.js
new file mode 100644
index 0000000000..12adb27caf
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_topsite.js
@@ -0,0 +1,136 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This file tests urlbar telemetry for topsite results.
+ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AboutNewTab: "resource:///modules/AboutNewTab.jsm",
+});
+
+const EN_US_TOPSITES =
+ "https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://twitter.com/";
+
+function snapshotHistograms() {
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ return {
+ resultMethodHist: TelemetryTestUtils.getAndClearHistogram(
+ "FX_URLBAR_SELECTED_RESULT_METHOD"
+ ),
+ search_hist: TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS"),
+ };
+}
+
+function assertTelemetryResults(histograms, type, index, method) {
+ TelemetryTestUtils.assertHistogram(histograms.resultMethodHist, method, 1);
+
+ TelemetryTestUtils.assertKeyedScalar(
+ TelemetryTestUtils.getProcessScalars("parent", true, true),
+ `urlbar.picked.${type}`,
+ index,
+ 1
+ );
+}
+
+/**
+ * Updates the Top Sites feed.
+ *
+ * @param {Function} condition
+ * A callback that returns true after Top Sites are successfully updated.
+ * @param {boolean} searchShortcuts
+ * True if Top Sites search shortcuts should be enabled.
+ */
+async function updateTopSites(condition, searchShortcuts = false) {
+ // Toggle the pref to clear the feed cache and force an update.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "browser.newtabpage.activity-stream.discoverystream.endpointSpocsClear",
+ "",
+ ],
+ ["browser.newtabpage.activity-stream.feeds.system.topsites", false],
+ ["browser.newtabpage.activity-stream.feeds.system.topsites", true],
+ [
+ "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts",
+ searchShortcuts,
+ ],
+ ],
+ });
+
+ // Wait for the feed to be updated.
+ await TestUtils.waitForCondition(() => {
+ let sites = AboutNewTab.getTopSites();
+ return condition(sites);
+ }, "Waiting for top sites to be updated");
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.topsites", true],
+ ["browser.newtabpage.activity-stream.default.sites", EN_US_TOPSITES],
+ ["browser.urlbar.suggest.quickactions", false],
+ ],
+ });
+ await updateTopSites(
+ sites => sites && sites.length == EN_US_TOPSITES.split(",").length
+ );
+});
+
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ let sites = AboutNewTab.getTopSites();
+ Assert.equal(
+ sites.length,
+ 6,
+ "The test suite browser should have 6 Top Sites."
+ );
+
+ const histograms = snapshotHistograms();
+
+ await UrlbarTestUtils.promisePopupOpen(window, () => {
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
+ });
+
+ await UrlbarTestUtils.promiseSearchComplete(window);
+ Assert.equal(
+ UrlbarTestUtils.getResultCount(window),
+ sites.length,
+ "The number of results should be the same as the number of Top Sites (6)."
+ );
+ // Select the first resultm and confirm it.
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ Assert.equal(
+ UrlbarTestUtils.getSelectedRowIndex(window),
+ 0,
+ "The first result should be selected"
+ );
+
+ let loadPromise = BrowserTestUtils.waitForDocLoadAndStopIt(
+ result.url,
+ gBrowser.selectedBrowser
+ );
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+
+ assertTelemetryResults(
+ histograms,
+ "topsite",
+ 0,
+ UrlbarTestUtils.SELECTED_RESULT_METHODS.arrowEnterSelection
+ );
+ await UrlbarTestUtils.promisePopupClose(window, () => {
+ gURLBar.blur();
+ });
+ });
+});
diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_zeroPrefix.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_zeroPrefix.js
new file mode 100644
index 0000000000..b331921553
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_zeroPrefix.js
@@ -0,0 +1,266 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This file tests urlbar telemetry related to the zero-prefix view, i.e., when
+ * the search string is empty.
+ */
+
+"use strict";
+
+const HISTOGRAM_DWELL_TIME = "FX_URLBAR_ZERO_PREFIX_DWELL_TIME_MS";
+const SCALARS = {
+ ABANDONMENT: "urlbar.zeroprefix.abandonment",
+ ENGAGEMENT: "urlbar.zeroprefix.engagement",
+ EXPOSURE: "urlbar.zeroprefix.exposure",
+};
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ Services.telemetry.clearScalars();
+
+ await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
+ await updateTopSitesAndAwaitChanged();
+});
+
+// zero prefix engagement
+add_task(async function engagement() {
+ let dwellHistogram =
+ TelemetryTestUtils.getAndClearHistogram(HISTOGRAM_DWELL_TIME);
+
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await showZeroPrefix();
+ checkScalars({
+ [SCALARS.EXPOSURE]: 1,
+ });
+ checkAndClearHistogram(dwellHistogram, false);
+
+ info("Finding row with result type URL");
+ let foundURLRow = false;
+ let count = UrlbarTestUtils.getResultCount(window);
+ for (let i = 0; i < count && !foundURLRow; i++) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ let index = UrlbarTestUtils.getSelectedRowIndex(window);
+ Assert.equal(index, i, "The expected row index should be selected");
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
+ info(`Checked row at index ${i}, result type is: ${details.type}`);
+ if (details.type == UrlbarUtils.RESULT_TYPE.URL) {
+ foundURLRow = true;
+ }
+ }
+ Assert.ok(foundURLRow, "Should have found a row with result type URL");
+
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ });
+
+ checkScalars({
+ [SCALARS.ENGAGEMENT]: 1,
+ });
+ checkAndClearHistogram(dwellHistogram, true);
+});
+
+// zero prefix abandonment
+add_task(async function abandonment() {
+ let dwellHistogram =
+ TelemetryTestUtils.getAndClearHistogram(HISTOGRAM_DWELL_TIME);
+
+ // Open and close the view twice. The second time the view will used a cached
+ // query context and that shouldn't interfere with telemetry.
+ for (let i = 0; i < 2; i++) {
+ await showZeroPrefix();
+ checkScalars({
+ [SCALARS.EXPOSURE]: 1,
+ });
+ checkAndClearHistogram(dwellHistogram, false);
+
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ checkScalars({
+ [SCALARS.ABANDONMENT]: 1,
+ });
+ dwellHistogram = checkAndClearHistogram(dwellHistogram, true);
+ }
+});
+
+// Shows the zero-prefix view, does some searches, then shows it again by doing
+// a search for an empty string.
+add_task(async function searches() {
+ let dwellHistogram =
+ TelemetryTestUtils.getAndClearHistogram(HISTOGRAM_DWELL_TIME);
+
+ info("Show zero prefix");
+ await showZeroPrefix();
+ checkScalars({
+ [SCALARS.EXPOSURE]: 1,
+ });
+ checkAndClearHistogram(dwellHistogram, false);
+
+ info("Search for 't'");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "t",
+ });
+ checkScalars({});
+ dwellHistogram = checkAndClearHistogram(dwellHistogram, true);
+
+ info("Search for 'te'");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "te",
+ });
+ checkScalars({});
+ checkAndClearHistogram(dwellHistogram, false);
+
+ info("Search for 't'");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "t",
+ });
+ checkScalars({});
+ checkAndClearHistogram(dwellHistogram, false);
+
+ info("Search for ''");
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "",
+ });
+ checkScalars({
+ [SCALARS.EXPOSURE]: 1,
+ });
+ checkAndClearHistogram(dwellHistogram, false);
+
+ info("Blur urlbar and close view");
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+ checkScalars({
+ [SCALARS.ABANDONMENT]: 1,
+ });
+ checkAndClearHistogram(dwellHistogram, true);
+});
+
+// A zero prefix engagement should not be recorded when the view isn't showing
+// zero prefix.
+add_task(async function notZeroPrefix_engagement() {
+ let dwellHistogram =
+ TelemetryTestUtils.getAndClearHistogram(HISTOGRAM_DWELL_TIME);
+
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+ });
+
+ checkScalars({});
+ checkAndClearHistogram(dwellHistogram, false);
+});
+
+// A zero prefix abandonment should not be recorded when the view isn't showing
+// zero prefix.
+add_task(async function notZeroPrefix_abandonment() {
+ let dwellHistogram =
+ TelemetryTestUtils.getAndClearHistogram(HISTOGRAM_DWELL_TIME);
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "test",
+ });
+ await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
+
+ checkScalars({});
+ checkAndClearHistogram(dwellHistogram, false);
+});
+
+function checkScalars(expected) {
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ for (let scalar of Object.values(SCALARS)) {
+ if (expected.hasOwnProperty(scalar)) {
+ TelemetryTestUtils.assertScalar(scalars, scalar, expected[scalar]);
+ } else {
+ Assert.ok(
+ !scalars.hasOwnProperty(scalar),
+ "Scalar should not be recorded: " + scalar
+ );
+ }
+ }
+}
+
+function checkAndClearHistogram(histogram, expected) {
+ if (expected) {
+ Assert.deepEqual(
+ Object.values(histogram.snapshot().values).filter(v => v > 0),
+ [1],
+ "Dwell histogram should be updated"
+ );
+ } else {
+ Assert.strictEqual(
+ histogram.snapshot().sum,
+ 0,
+ "Dwell histogram should not be updated"
+ );
+ }
+
+ return TelemetryTestUtils.getAndClearHistogram(histogram.name());
+}
+
+async function showZeroPrefix() {
+ let { promise, cleanup } = waitForQueryFinished();
+ await SimpleTest.promiseFocus(window);
+ await UrlbarTestUtils.promisePopupOpen(window, () =>
+ document.getElementById("Browser:OpenLocation").doCommand()
+ );
+ await promise;
+ cleanup();
+
+ Assert.greater(
+ UrlbarTestUtils.getResultCount(window),
+ 0,
+ "There should be at least one row in the zero prefix view"
+ );
+}
+
+/**
+ * Returns a promise that's resolved on the next `onQueryFinished()`. It's
+ * important to wait for `onQueryFinished()` because that's when the view checks
+ * whether it's showing zero prefix.
+ *
+ * @returns {object}
+ * An object with the following properties:
+ * {Promise} promise
+ * Resolved when `onQueryFinished()` is called.
+ * {Function} cleanup
+ * This should be called to remove the listener.
+ */
+function waitForQueryFinished() {
+ let deferred = PromiseUtils.defer();
+ let listener = {
+ onQueryFinished: () => deferred.resolve(),
+ };
+ gURLBar.controller.addQueryListener(listener);
+
+ return {
+ promise: deferred.promise,
+ cleanup() {
+ gURLBar.controller.removeQueryListener(listener);
+ },
+ };
+}
+
+async function updateTopSitesAndAwaitChanged() {
+ let url = "http://mochi.test:8888/topsite";
+ for (let i = 0; i < 5; i++) {
+ await PlacesTestUtils.addVisits(url);
+ }
+
+ info("Updating top sites and awaiting newtab-top-sites-changed");
+ let changedPromise = TestUtils.topicObserved("newtab-top-sites-changed").then(
+ () => info("Observed newtab-top-sites-changed")
+ );
+ await updateTopSites(sites => sites?.length);
+ await changedPromise;
+}
diff --git a/browser/components/urlbar/tests/browser/browser_userTypedValue.js b/browser/components/urlbar/tests/browser/browser_userTypedValue.js
new file mode 100644
index 0000000000..8319b37962
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_userTypedValue.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ const URI = TEST_BASE_URL + "file_userTypedValue.html";
+ window.browserDOMWindow.openURI(
+ makeURI(URI),
+ null,
+ Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+ Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+
+ is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI");
+ is(gURLBar.value, URI, "location bar value matches test URI");
+
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+ gBrowser.removeCurrentTab({ skipPermitUnload: true });
+ is(
+ gBrowser.userTypedValue,
+ URI,
+ "userTypedValue matches test URI after switching tabs"
+ );
+ is(
+ gURLBar.value,
+ URI,
+ "location bar value matches test URI after switching tabs"
+ );
+
+ waitForExplicitFinish();
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
+ is(
+ gBrowser.userTypedValue,
+ null,
+ "userTypedValue is null as the page has loaded"
+ );
+ is(
+ gURLBar.value,
+ URI,
+ "location bar value matches test URI as the page has loaded"
+ );
+
+ gBrowser.removeCurrentTab({ skipPermitUnload: true });
+ finish();
+ });
+}
diff --git a/browser/components/urlbar/tests/browser/browser_valueOnTabSwitch.js b/browser/components/urlbar/tests/browser/browser_valueOnTabSwitch.js
new file mode 100644
index 0000000000..9d3e922692
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_valueOnTabSwitch.js
@@ -0,0 +1,166 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This tests for the correct URL being displayed in the URL bar after switching
+ * tabs which are in different states (e.g. deleted, partially deleted).
+ */
+
+"use strict";
+
+const TEST_URL = `${TEST_BASE_URL}dummy_page.html`;
+
+add_task(async function () {
+ // autofill may conflict with the test scope, by filling missing parts of
+ // the url due to autoOpen.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.autoFill", false]],
+ });
+
+ let charsToDelete,
+ deletedURLTab,
+ fullURLTab,
+ partialURLTab,
+ testPartialURL,
+ testURL;
+
+ charsToDelete = 5;
+ deletedURLTab = BrowserTestUtils.addTab(gBrowser);
+ fullURLTab = BrowserTestUtils.addTab(gBrowser);
+ partialURLTab = BrowserTestUtils.addTab(gBrowser);
+ testURL = TEST_URL;
+
+ let loaded1 = BrowserTestUtils.browserLoaded(
+ deletedURLTab.linkedBrowser,
+ false,
+ testURL
+ );
+ let loaded2 = BrowserTestUtils.browserLoaded(
+ fullURLTab.linkedBrowser,
+ false,
+ testURL
+ );
+ let loaded3 = BrowserTestUtils.browserLoaded(
+ partialURLTab.linkedBrowser,
+ false,
+ testURL
+ );
+ BrowserTestUtils.loadURIString(deletedURLTab.linkedBrowser, testURL);
+ BrowserTestUtils.loadURIString(fullURLTab.linkedBrowser, testURL);
+ BrowserTestUtils.loadURIString(partialURLTab.linkedBrowser, testURL);
+ await Promise.all([loaded1, loaded2, loaded3]);
+
+ testURL = BrowserUIUtils.trimURL(testURL);
+ testPartialURL = testURL.substr(0, testURL.length - charsToDelete);
+
+ function cleanUp() {
+ gBrowser.removeTab(fullURLTab);
+ gBrowser.removeTab(partialURLTab);
+ gBrowser.removeTab(deletedURLTab);
+ }
+
+ async function cycleTabs() {
+ await BrowserTestUtils.switchTab(gBrowser, fullURLTab);
+ is(
+ gURLBar.value,
+ testURL,
+ "gURLBar.value should be testURL after switching back to fullURLTab"
+ );
+
+ await BrowserTestUtils.switchTab(gBrowser, partialURLTab);
+ is(
+ gURLBar.value,
+ testPartialURL,
+ "gURLBar.value should be testPartialURL after switching back to partialURLTab"
+ );
+ await BrowserTestUtils.switchTab(gBrowser, deletedURLTab);
+ is(
+ gURLBar.value,
+ testURL,
+ "gURLBar.value should be testURL after switching back to deletedURLTab"
+ );
+
+ await BrowserTestUtils.switchTab(gBrowser, fullURLTab);
+ is(
+ gURLBar.value,
+ testURL,
+ "gURLBar.value should be testURL after switching back to fullURLTab"
+ );
+ }
+
+ function urlbarBackspace(removeAll) {
+ return new Promise((resolve, reject) => {
+ gBrowser.selectedBrowser.focus();
+ gURLBar.addEventListener(
+ "input",
+ function () {
+ resolve();
+ },
+ { once: true }
+ );
+ gURLBar.focus();
+ if (removeAll) {
+ gURLBar.select();
+ } else {
+ gURLBar.selectionStart = gURLBar.selectionEnd = gURLBar.value.length;
+ }
+ EventUtils.synthesizeKey("KEY_Backspace");
+ });
+ }
+
+ async function prepareDeletedURLTab() {
+ await BrowserTestUtils.switchTab(gBrowser, deletedURLTab);
+ is(
+ gURLBar.value,
+ testURL,
+ "gURLBar.value should be testURL after initial switch to deletedURLTab"
+ );
+
+ // simulate the user removing the whole url from the location bar
+ await urlbarBackspace(true);
+ is(gURLBar.value, "", 'gURLBar.value should be "" (just set)');
+ }
+
+ async function prepareFullURLTab() {
+ await BrowserTestUtils.switchTab(gBrowser, fullURLTab);
+ is(
+ gURLBar.value,
+ testURL,
+ "gURLBar.value should be testURL after initial switch to fullURLTab"
+ );
+ }
+
+ async function preparePartialURLTab() {
+ await BrowserTestUtils.switchTab(gBrowser, partialURLTab);
+ is(
+ gURLBar.value,
+ testURL,
+ "gURLBar.value should be testURL after initial switch to partialURLTab"
+ );
+
+ // simulate the user removing part of the url from the location bar
+ let deleted = 0;
+ while (deleted < charsToDelete) {
+ await urlbarBackspace(false);
+ deleted++;
+ }
+
+ is(
+ gURLBar.value,
+ testPartialURL,
+ "gURLBar.value should be testPartialURL (just set)"
+ );
+ }
+
+ // prepare the three tabs required by this test
+
+ // First tab
+ await prepareFullURLTab();
+ await preparePartialURLTab();
+ await prepareDeletedURLTab();
+
+ // now cycle the tabs and make sure everything looks good
+ await cycleTabs();
+ cleanUp();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_view_emptyResultSet.js b/browser/components/urlbar/tests/browser/browser_view_emptyResultSet.js
new file mode 100644
index 0000000000..f7a2721093
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_view_emptyResultSet.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the view results are cleared and the view is closed, when an empty
+// result set arrives after a non-empty one.
+
+add_task(async function () {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ Assert.ok(
+ UrlbarTestUtils.getResultCount(window) > 0,
+ `There should be some results in the view.`
+ );
+ Assert.ok(gURLBar.view.isOpen, `The view should be open.`);
+
+ // Register an high priority empty result provider.
+ let provider = new UrlbarTestUtils.TestProvider({
+ results: [],
+ priority: 999,
+ });
+ UrlbarProvidersManager.registerProvider(provider);
+ registerCleanupFunction(async function () {
+ UrlbarProvidersManager.unregisterProvider(provider);
+ await PlacesUtils.history.clear();
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ });
+ Assert.ok(
+ UrlbarTestUtils.getResultCount(window) == 0,
+ `There should be no results in the view.`
+ );
+ Assert.ok(!gURLBar.view.isOpen, `The view should have been closed.`);
+});
diff --git a/browser/components/urlbar/tests/browser/browser_view_resultDisplay.js b/browser/components/urlbar/tests/browser/browser_view_resultDisplay.js
new file mode 100644
index 0000000000..c4053eaed7
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_view_resultDisplay.js
@@ -0,0 +1,354 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that a result has the various elements displayed in the URL bar as
+ * we expect them to be.
+ */
+
+add_setup(async function () {
+ await PlacesUtils.history.clear();
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ Services.prefs.clearUserPref("browser.urlbar.trimURLs");
+ });
+});
+
+async function testResult(input, expected, index = 1) {
+ const ESCAPED_URL = encodeURI(input.url);
+
+ await PlacesUtils.history.clear();
+ if (index > 0) {
+ await PlacesTestUtils.addVisits({
+ uri: input.url,
+ title: input.title,
+ });
+ }
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: input.query,
+ });
+ let result = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+ Assert.equal(result.url, ESCAPED_URL, "Should have the correct url to load");
+ Assert.equal(
+ result.displayed.url,
+ expected.displayedUrl,
+ "Should have the correct displayed url"
+ );
+ Assert.equal(
+ result.displayed.title,
+ input.title,
+ "Should have the expected title"
+ );
+ Assert.equal(
+ result.displayed.typeIcon,
+ "none",
+ "Should not have a type icon"
+ );
+ if (index > 0) {
+ Assert.equal(
+ result.image,
+ `page-icon:${ESCAPED_URL}`,
+ "Should have the correct favicon"
+ );
+ }
+
+ assertDisplayedHighlights(
+ "title",
+ result.element.title,
+ expected.highlightedTitle
+ );
+
+ assertDisplayedHighlights("url", result.element.url, expected.highlightedUrl);
+}
+
+function assertDisplayedHighlights(elementName, element, expectedResults) {
+ Assert.equal(
+ element.childNodes.length,
+ expectedResults.length,
+ `Should have the correct number of child nodes for ${elementName}`
+ );
+
+ for (let i = 0; i < element.childNodes.length; i++) {
+ let child = element.childNodes[i];
+ Assert.equal(
+ child.textContent,
+ expectedResults[i][0],
+ `Should have the correct text for the ${i} part of the ${elementName}`
+ );
+ Assert.equal(
+ child.nodeName,
+ expectedResults[i][1] ? "strong" : "#text",
+ `Should have the correct text/strong status for the ${i} part of the ${elementName}`
+ );
+ }
+}
+
+add_task(async function test_url_result() {
+ await testResult(
+ {
+ query: "\u6e2C\u8a66",
+ title: "The \u6e2C\u8a66 URL",
+ url: "https://example.com/\u6e2C\u8a66test",
+ },
+ {
+ displayedUrl: "example.com/\u6e2C\u8a66test",
+ highlightedTitle: [
+ ["The ", false],
+ ["\u6e2C\u8a66", true],
+ [" URL", false],
+ ],
+ highlightedUrl: [
+ ["example.com/", false],
+ ["\u6e2C\u8a66", true],
+ ["test", false],
+ ],
+ }
+ );
+});
+
+add_task(async function test_url_result_no_path() {
+ await testResult(
+ {
+ query: "ample",
+ title: "The Title",
+ url: "https://example.com/",
+ },
+ {
+ displayedUrl: "example.com",
+ highlightedTitle: [["The Title", false]],
+ highlightedUrl: [
+ ["ex", false],
+ ["ample", true],
+ [".com", false],
+ ],
+ }
+ );
+});
+
+add_task(async function test_url_result_www() {
+ await testResult(
+ {
+ query: "ample",
+ title: "The Title",
+ url: "https://www.example.com/",
+ },
+ {
+ displayedUrl: "example.com",
+ highlightedTitle: [["The Title", false]],
+ highlightedUrl: [
+ ["ex", false],
+ ["ample", true],
+ [".com", false],
+ ],
+ }
+ );
+});
+
+add_task(async function test_url_result_no_trimming() {
+ Services.prefs.setBoolPref("browser.urlbar.trimURLs", false);
+
+ await testResult(
+ {
+ query: "\u6e2C\u8a66",
+ title: "The \u6e2C\u8a66 URL",
+ url: "http://example.com/\u6e2C\u8a66test",
+ },
+ {
+ displayedUrl: "http://example.com/\u6e2C\u8a66test",
+ highlightedTitle: [
+ ["The ", false],
+ ["\u6e2C\u8a66", true],
+ [" URL", false],
+ ],
+ highlightedUrl: [
+ ["http://example.com/", false],
+ ["\u6e2C\u8a66", true],
+ ["test", false],
+ ],
+ }
+ );
+
+ Services.prefs.clearUserPref("browser.urlbar.trimURLs");
+});
+
+add_task(async function test_case_insensitive_highlights_1() {
+ await testResult(
+ {
+ query: "exam",
+ title: "The examPLE URL EXAMple",
+ url: "https://example.com/ExAm",
+ },
+ {
+ displayedUrl: "example.com/ExAm",
+ highlightedTitle: [
+ ["The ", false],
+ ["exam", true],
+ ["PLE URL ", false],
+ ["EXAM", true],
+ ["ple", false],
+ ],
+ highlightedUrl: [
+ ["exam", true],
+ ["ple.com/", false],
+ ["ExAm", true],
+ ],
+ }
+ );
+});
+
+add_task(async function test_case_insensitive_highlights_2() {
+ await testResult(
+ {
+ query: "EXAM",
+ title: "The examPLE URL EXAMple",
+ url: "https://example.com/ExAm",
+ },
+ {
+ displayedUrl: "example.com/ExAm",
+ highlightedTitle: [
+ ["The ", false],
+ ["exam", true],
+ ["PLE URL ", false],
+ ["EXAM", true],
+ ["ple", false],
+ ],
+ highlightedUrl: [
+ ["exam", true],
+ ["ple.com/", false],
+ ["ExAm", true],
+ ],
+ }
+ );
+});
+
+add_task(async function test_case_insensitive_highlights_3() {
+ await testResult(
+ {
+ query: "eXaM",
+ title: "The examPLE URL EXAMple",
+ url: "https://example.com/ExAm",
+ },
+ {
+ displayedUrl: "example.com/ExAm",
+ highlightedTitle: [
+ ["The ", false],
+ ["exam", true],
+ ["PLE URL ", false],
+ ["EXAM", true],
+ ["ple", false],
+ ],
+ highlightedUrl: [
+ ["exam", true],
+ ["ple.com/", false],
+ ["ExAm", true],
+ ],
+ }
+ );
+});
+
+add_task(async function test_case_insensitive_highlights_4() {
+ await testResult(
+ {
+ query: "ExAm",
+ title: "The examPLE URL EXAMple",
+ url: "https://example.com/ExAm",
+ },
+ {
+ displayedUrl: "example.com/ExAm",
+ highlightedTitle: [
+ ["The ", false],
+ ["exam", true],
+ ["PLE URL ", false],
+ ["EXAM", true],
+ ["ple", false],
+ ],
+ highlightedUrl: [
+ ["exam", true],
+ ["ple.com/", false],
+ ["ExAm", true],
+ ],
+ }
+ );
+});
+
+add_task(async function test_case_insensitive_highlights_5() {
+ await testResult(
+ {
+ query: "exam foo",
+ title: "The examPLE URL foo EXAMple FOO",
+ url: "https://example.com/ExAm/fOo",
+ },
+ {
+ displayedUrl: "example.com/ExAm/fOo",
+ highlightedTitle: [
+ ["The ", false],
+ ["exam", true],
+ ["PLE URL ", false],
+ ["foo", true],
+ [" ", false],
+ ["EXAM", true],
+ ["ple ", false],
+ ["FOO", true],
+ ],
+ highlightedUrl: [
+ ["exam", true],
+ ["ple.com/", false],
+ ["ExAm", true],
+ ["/", false],
+ ["fOo", true],
+ ],
+ }
+ );
+});
+
+add_task(async function test_case_insensitive_highlights_6() {
+ await testResult(
+ {
+ query: "EXAM FOO",
+ title: "The examPLE URL foo EXAMple FOO",
+ url: "https://example.com/ExAm/fOo",
+ },
+ {
+ displayedUrl: "example.com/ExAm/fOo",
+ highlightedTitle: [
+ ["The ", false],
+ ["exam", true],
+ ["PLE URL ", false],
+ ["foo", true],
+ [" ", false],
+ ["EXAM", true],
+ ["ple ", false],
+ ["FOO", true],
+ ],
+ highlightedUrl: [
+ ["exam", true],
+ ["ple.com/", false],
+ ["ExAm", true],
+ ["/", false],
+ ["fOo", true],
+ ],
+ }
+ );
+});
+
+add_task(async function test_no_highlight_fallback_heuristic_url() {
+ info("Test unvisited heuristic (fallback provider)");
+ await testResult(
+ {
+ query: "nonexisting.com",
+ title: "http://nonexisting.com/",
+ url: "http://nonexisting.com/",
+ },
+ {
+ displayedUrl: "", // URL heuristic only has title.
+ highlightedTitle: [["http://nonexisting.com/", false]],
+ highlightedUrl: [],
+ },
+ 0 // Test the heuristic result.
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_view_resultTypes_display.js b/browser/components/urlbar/tests/browser/browser_view_resultTypes_display.js
new file mode 100644
index 0000000000..0cf56f107d
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_view_resultTypes_display.js
@@ -0,0 +1,317 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SyncedTabs } = ChromeUtils.importESModule(
+ "resource://services-sync/SyncedTabs.sys.mjs"
+);
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+function assertElementsDisplayed(details, expected) {
+ Assert.equal(
+ details.type,
+ expected.type,
+ "Should be displaying a row of the correct type"
+ );
+ Assert.equal(
+ details.title,
+ expected.title,
+ "Should be displaying the correct title"
+ );
+ let separatorVisible =
+ window.getComputedStyle(details.element.separator).display != "none" &&
+ window.getComputedStyle(details.element.separator).visibility != "collapse";
+ Assert.equal(
+ expected.separator,
+ separatorVisible,
+ `Should${expected.separator ? " " : " not "}be displaying a separator`
+ );
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.suggest.searches", false],
+ // Disable search suggestions in the urlbar.
+ ["browser.urlbar.suggest.searches", false],
+ // Clear historical search suggestions to avoid interference from previous
+ // tests.
+ ["browser.urlbar.maxHistoricalSearchSuggestions", 0],
+ // Turn autofill off.
+ ["browser.urlbar.autoFill", false],
+ ],
+ });
+
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME,
+ setAsDefault: true,
+ });
+
+ // Move the mouse away from the results panel, because hovering a result may
+ // change its aspect (e.g. by showing a " - search with Engine" suffix).
+ await EventUtils.promiseNativeMouseEvent({
+ type: "mousemove",
+ target: gURLBar.inputField,
+ offsetX: 0,
+ offsetY: 0,
+ });
+});
+
+add_task(async function test_tab_switch_result() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:mozilla"
+ );
+
+ await BrowserTestUtils.withNewTab({ gBrowser }, async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "about:mozilla",
+ fireInputEvent: true,
+ });
+
+ const details = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+
+ assertElementsDisplayed(details, {
+ separator: true,
+ title: "about:mozilla",
+ type: UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
+ });
+ });
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_search_result() {
+ Services.prefs.setBoolPref("browser.urlbar.suggest.searches", true);
+
+ await BrowserTestUtils.withNewTab({ gBrowser }, async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "foo",
+ fireInputEvent: true,
+ });
+
+ let index = await UrlbarTestUtils.promiseSuggestionsPresent(window);
+ const details = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
+
+ // We'll initially display no separator.
+ assertElementsDisplayed(details, {
+ separator: false,
+ title: "foofoo",
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ });
+
+ // Down to select the first search suggestion.
+ for (let i = index; i > 0; --i) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+
+ // We should now be displaying one.
+ assertElementsDisplayed(details, {
+ separator: true,
+ title: "foofoo",
+ type: UrlbarUtils.RESULT_TYPE.SEARCH,
+ });
+ });
+
+ await PlacesUtils.history.clear();
+ Services.prefs.setBoolPref("browser.urlbar.suggest.searches", false);
+});
+
+add_task(async function test_url_result() {
+ await PlacesTestUtils.addVisits([
+ {
+ uri: "http://example.com",
+ title: "example",
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ },
+ ]);
+
+ await BrowserTestUtils.withNewTab({ gBrowser }, async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "example",
+ fireInputEvent: true,
+ });
+
+ const details = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+
+ assertElementsDisplayed(details, {
+ separator: true,
+ title: "example",
+ type: UrlbarUtils.RESULT_TYPE.URL,
+ });
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_keyword_result() {
+ const TEST_URL = `${TEST_BASE_URL}print_postdata.sjs`;
+
+ await PlacesUtils.keywords.insert({
+ keyword: "get",
+ url: TEST_URL + "?q=%s",
+ });
+
+ await BrowserTestUtils.withNewTab({ gBrowser }, async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "get ",
+ fireInputEvent: true,
+ });
+
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+
+ // Because only the keyword is typed, we show the bookmark url.
+ assertElementsDisplayed(details, {
+ separator: true,
+ title: TEST_URL + "?q=",
+ type: UrlbarUtils.RESULT_TYPE.KEYWORD,
+ });
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "get test",
+ fireInputEvent: true,
+ });
+
+ details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+
+ assertElementsDisplayed(details, {
+ separator: false,
+ title: "example.com: test",
+ type: UrlbarUtils.RESULT_TYPE.KEYWORD,
+ });
+ });
+});
+
+add_task(async function test_omnibox_result() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ omnibox: {
+ keyword: "omniboxtest",
+ },
+
+ background() {
+ /* global browser */
+ browser.omnibox.setDefaultSuggestion({
+ description: "doit",
+ });
+ // Just do nothing for this test.
+ browser.omnibox.onInputEntered.addListener(() => {});
+ browser.omnibox.onInputChanged.addListener((text, suggest) => {
+ suggest([]);
+ });
+ },
+ },
+ });
+
+ await extension.startup();
+
+ await BrowserTestUtils.withNewTab({ gBrowser }, async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "omniboxtest ",
+ fireInputEvent: true,
+ });
+
+ const details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+
+ assertElementsDisplayed(details, {
+ separator: true,
+ title: "Generated extension",
+ type: UrlbarUtils.RESULT_TYPE.OMNIBOX,
+ });
+ });
+
+ await extension.unload();
+});
+
+add_task(async function test_remote_tab_result() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["services.sync.username", "fake"],
+ ["services.sync.syncedTabs.showRemoteTabs", true],
+ ],
+ });
+ // Clear history so that history added by previous tests doesn't mess up this
+ // test when it selects results in the urlbar.
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ const REMOTE_TAB = {
+ id: "7cqCr77ptzX3",
+ type: "client",
+ lastModified: 1492201200,
+ name: "zcarter's Nightly on MacBook-Pro-25",
+ clientType: "desktop",
+ tabs: [
+ {
+ type: "tab",
+ title: "Test Remote",
+ url: "http://example.com",
+ icon: UrlbarUtils.ICON.DEFAULT,
+ client: "7cqCr77ptzX3",
+ lastUsed: Math.floor(Date.now() / 1000),
+ },
+ ],
+ };
+
+ const sandbox = sinon.createSandbox();
+
+ let originalSyncedTabsInternal = SyncedTabs._internal;
+ SyncedTabs._internal = {
+ isConfiguredToSyncTabs: true,
+ hasSyncedThisSession: true,
+ getTabClients() {
+ return Promise.resolve([]);
+ },
+ syncTabs() {
+ return Promise.resolve();
+ },
+ };
+
+ // Tell the Sync XPCOM service it is initialized.
+ let weaveXPCService = Cc["@mozilla.org/weave/service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ let oldWeaveServiceReady = weaveXPCService.ready;
+ weaveXPCService.ready = true;
+
+ sandbox
+ .stub(SyncedTabs._internal, "getTabClients")
+ .callsFake(() => Promise.resolve(Cu.cloneInto([REMOTE_TAB], {})));
+
+ // Reset internal cache in UrlbarProviderRemoteTabs.
+ Services.obs.notifyObservers(null, "weave:engine:sync:finish", "tabs");
+
+ registerCleanupFunction(async function () {
+ sandbox.restore();
+ weaveXPCService.ready = oldWeaveServiceReady;
+ SyncedTabs._internal = originalSyncedTabsInternal;
+ await PlacesUtils.history.clear();
+ await PlacesUtils.bookmarks.eraseEverything();
+ Services.telemetry.setEventRecordingEnabled("navigation", false);
+ });
+
+ await BrowserTestUtils.withNewTab({ gBrowser }, async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "example",
+ fireInputEvent: true,
+ });
+
+ const details = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+
+ assertElementsDisplayed(details, {
+ separator: true,
+ title: "Test Remote",
+ type: UrlbarUtils.RESULT_TYPE.REMOTE_TAB,
+ });
+ });
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/urlbar/tests/browser/browser_view_selectionByMouse.js b/browser/components/urlbar/tests/browser/browser_view_selectionByMouse.js
new file mode 100644
index 0000000000..2de8439b58
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_view_selectionByMouse.js
@@ -0,0 +1,607 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test selection on result view by mouse.
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarProviderQuickActions:
+ "resource:///modules/UrlbarProviderQuickActions.sys.mjs",
+});
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.quickactions.enabled", true],
+ ["browser.urlbar.suggest.quickactions", true],
+ ["browser.urlbar.shortcuts.quickactions", true],
+ ],
+ });
+
+ UrlbarTestUtils.disableResultMenuAutohide(window);
+
+ await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
+
+ UrlbarProviderQuickActions.addAction("test-addons", {
+ commands: ["test-addons"],
+ label: "quickactions-addons",
+ onPick: () =>
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, "about:about"),
+ });
+ UrlbarProviderQuickActions.addAction("test-downloads", {
+ commands: ["test-downloads"],
+ label: "quickactions-downloads2",
+ onPick: () =>
+ BrowserTestUtils.loadURIString(
+ gBrowser.selectedBrowser,
+ "about:downloads"
+ ),
+ });
+
+ registerCleanupFunction(function () {
+ UrlbarProviderQuickActions.removeAction("test-addons");
+ UrlbarProviderQuickActions.removeAction("test-downloads");
+ });
+});
+
+add_task(async function basic() {
+ const testData = [
+ {
+ description: "Normal result to quick action button",
+ mousedown: ".urlbarView-row:nth-child(1) > .urlbarView-row-inner",
+ mouseup: ".urlbarView-quickaction-row[data-key=test-downloads]",
+ expected: "about:downloads",
+ },
+ {
+ description: "Normal result to out of result",
+ mousedown: ".urlbarView-row:nth-child(1) > .urlbarView-row-inner",
+ mouseup: "body",
+ expected: false,
+ },
+ {
+ description: "Quick action button to normal result",
+ mousedown: ".urlbarView-quickaction-row[data-key=test-addons]",
+ mouseup: ".urlbarView-row:nth-child(1)",
+ expected: "https://example.com/?q=test",
+ },
+ {
+ description: "Quick action button to quick action button",
+ mousedown: ".urlbarView-quickaction-row[data-key=test-addons]",
+ mouseup: ".urlbarView-quickaction-row[data-key=test-downloads]",
+ expected: "about:downloads",
+ },
+ {
+ description: "Quick action button to out of result",
+ mousedown: ".urlbarView-quickaction-row[data-key=test-downloads]",
+ mouseup: "body",
+ expected: false,
+ },
+ ];
+
+ for (const { description, mousedown, mouseup, expected } of testData) {
+ info(description);
+
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "test",
+ window,
+ });
+ let [downElement, upElement] = await waitForElements([
+ mousedown,
+ mouseup,
+ ]);
+
+ EventUtils.synthesizeMouseAtCenter(downElement, {
+ type: "mousedown",
+ });
+ Assert.ok(
+ downElement.hasAttribute("selected"),
+ "Mousedown element should be selected after mousedown"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(upElement, { type: "mouseup" });
+ Assert.ok(
+ !downElement.hasAttribute("selected"),
+ "Mousedown element should not be selected after mouseup"
+ );
+ Assert.ok(
+ !upElement.hasAttribute("selected"),
+ "Mouseup element should not be selected after mouseup"
+ );
+
+ if (expected) {
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ expected
+ );
+ Assert.ok(true, "Expected page is opened");
+ }
+ });
+ }
+});
+
+add_task(async function outOfBrowser() {
+ const testData = [
+ {
+ description: "Normal result to out of browser",
+ mousedown: ".urlbarView-row:nth-child(1) > .urlbarView-row-inner",
+ },
+ {
+ description: "Normal result to out of result",
+ mousedown: ".urlbarView-row:nth-child(1) > .urlbarView-row-inner",
+ expected: false,
+ },
+ {
+ description: "Quick action button to out of browser",
+ mousedown: ".urlbarView-quickaction-row[data-key=test-addons]",
+ },
+ ];
+
+ for (const { description, mousedown } of testData) {
+ info(description);
+
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "test",
+ window,
+ });
+ let [downElement] = await waitForElements([mousedown]);
+
+ EventUtils.synthesizeMouseAtCenter(downElement, {
+ type: "mousedown",
+ });
+ Assert.ok(
+ downElement.hasAttribute("selected"),
+ "Mousedown element should be selected after mousedown"
+ );
+
+ // Mouseup at out of browser.
+ EventUtils.synthesizeMouse(document.documentElement, -1, -1, {
+ type: "mouseup",
+ });
+
+ Assert.ok(
+ !downElement.hasAttribute("selected"),
+ "Mousedown element should not be selected after mouseup"
+ );
+ });
+ }
+});
+
+add_task(async function withSelectionByKeyboard() {
+ const testData = [
+ {
+ description: "Select normal result, then click on out of result",
+ mousedown: "body",
+ mouseup: "body",
+ expected: {
+ selectedElementByKey:
+ "#urlbar-results .urlbarView-row > .urlbarView-row-inner[selected]",
+ selectedElementAfterMouseDown:
+ "#urlbar-results .urlbarView-row > .urlbarView-row-inner[selected]",
+ actionedPage: false,
+ },
+ },
+ {
+ description: "Select quick action button, then click on out of result",
+ arrowDown: 1,
+ mousedown: "body",
+ mouseup: "body",
+ expected: {
+ selectedElementByKey:
+ "#urlbar-results .urlbarView-quickaction-row[selected]",
+ selectedElementAfterMouseDown:
+ "#urlbar-results .urlbarView-quickaction-row[selected]",
+ actionedPage: false,
+ },
+ },
+ {
+ description: "Select normal result, then click on about:downloads",
+ mousedown: ".urlbarView-quickaction-row[data-key=test-downloads]",
+ mouseup: ".urlbarView-quickaction-row[data-key=test-downloads]",
+ expected: {
+ selectedElementByKey:
+ "#urlbar-results .urlbarView-row > .urlbarView-row-inner[selected]",
+ selectedElementAfterMouseDown:
+ ".urlbarView-quickaction-row[data-key=test-downloads]",
+ actionedPage: "about:downloads",
+ },
+ },
+ ];
+
+ for (const {
+ description,
+ arrowDown,
+ mousedown,
+ mouseup,
+ expected,
+ } of testData) {
+ info(description);
+
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "test",
+ window,
+ });
+ let [downElement, upElement] = await waitForElements([
+ mousedown,
+ mouseup,
+ ]);
+
+ if (arrowDown) {
+ EventUtils.synthesizeKey(
+ "KEY_ArrowDown",
+ { repeat: arrowDown },
+ window
+ );
+ }
+
+ let [selectedElementByKey] = await waitForElements([
+ expected.selectedElementByKey,
+ ]);
+ Assert.ok(
+ selectedElementByKey.hasAttribute("selected"),
+ "selectedElementByKey should be selected after arrow down"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(downElement, {
+ type: "mousedown",
+ });
+
+ if (
+ expected.selectedElementByKey !== expected.selectedElementAfterMouseDown
+ ) {
+ let [selectedElementAfterMouseDown] = await waitForElements([
+ expected.selectedElementAfterMouseDown,
+ ]);
+ Assert.ok(
+ selectedElementAfterMouseDown.hasAttribute("selected"),
+ "selectedElementAfterMouseDown should be selected after mousedown"
+ );
+ Assert.ok(
+ !selectedElementByKey.hasAttribute("selected"),
+ "selectedElementByKey should not be selected after mousedown"
+ );
+ }
+
+ EventUtils.synthesizeMouseAtCenter(upElement, {
+ type: "mouseup",
+ });
+
+ if (expected.actionedPage) {
+ Assert.ok(
+ !selectedElementByKey.hasAttribute("selected"),
+ "selectedElementByKey should not be selected after page starts load"
+ );
+ await BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ expected.actionedPage
+ );
+ Assert.ok(true, "Expected page is opened");
+ } else {
+ Assert.ok(
+ selectedElementByKey.hasAttribute("selected"),
+ "selectedElementByKey should remain selected"
+ );
+ }
+ });
+ }
+});
+
+add_task(async function withDnsFirstForSingleWordsPref() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.fixup.dns_first_for_single_words", true]],
+ });
+
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "https://example.org/",
+ title: "example",
+ });
+ await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
+
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ value: "ex",
+ window,
+ });
+
+ const details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+ const target = details.element.action;
+ EventUtils.synthesizeMouseAtCenter(target, { type: "mousedown" });
+ const onLoaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ "https://example.org/"
+ );
+ EventUtils.synthesizeMouseAtCenter(target, { type: "mouseup" });
+ await onLoaded;
+ Assert.ok(true, "Expected page is opened");
+
+ await PlacesUtils.bookmarks.eraseEverything();
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function buttons() {
+ let initialTabUrl = "https://example.com/initial";
+ let mainResultUrl = "https://example.com/main";
+ let mainResultHelpUrl = "https://example.com/help";
+ let otherResultUrl = "https://example.com/other";
+
+ let searchString = "test";
+
+ // Add a provider with two results: The first has buttons and the second has a
+ // URL that should or shouldn't become the input's value when the block button
+ // in the first result is clicked, depending on the test.
+ let provider = new UrlbarTestUtils.TestProvider({
+ priority: Infinity,
+ results: [
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ url: mainResultUrl,
+ helpUrl: mainResultHelpUrl,
+ helpL10n: {
+ id: UrlbarPrefs.get("resultMenu")
+ ? "urlbar-result-menu-learn-more-about-firefox-suggest"
+ : "firefox-suggest-urlbar-learn-more",
+ },
+ isBlockable: true,
+ blockL10n: {
+ id: UrlbarPrefs.get("resultMenu")
+ ? "urlbar-result-menu-dismiss-firefox-suggest"
+ : "firefox-suggest-urlbar-block",
+ },
+ }
+ ),
+ new UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ {
+ url: otherResultUrl,
+ }
+ ),
+ ],
+ });
+
+ // Implement the provider's `onEngagement()` so it removes the result.
+ let onEngagementCallCount = 0;
+ provider.onEngagement = (isPrivate, state, queryContext, details) => {
+ onEngagementCallCount++;
+ queryContext.view.controller.removeResult(details.result);
+ };
+
+ UrlbarProvidersManager.registerProvider(provider);
+
+ let assertBlockResultCalled = () => {
+ Assert.equal(
+ onEngagementCallCount,
+ 1,
+ "blockResult() should have been called once"
+ );
+ onEngagementCallCount = 0;
+
+ let rowUrls = [];
+ let rows = UrlbarTestUtils.getResultsContainer(window).children;
+ for (let row of rows) {
+ rowUrls.push(row.result.payload.url);
+ }
+ Assert.ok(
+ !rowUrls.includes(mainResultUrl),
+ "The main result should not be in the view after blocking it: " +
+ JSON.stringify(rowUrls)
+ );
+ };
+ let assertResultMenuOpen = () => {
+ Assert.equal(
+ gURLBar.view.resultMenu.state,
+ "showing",
+ "Result menu is showing"
+ );
+ EventUtils.synthesizeKey("KEY_Escape");
+ };
+
+ let testData = [
+ {
+ description: UrlbarPrefs.get("resultMenu")
+ ? "Menu button to menu button"
+ : "Block button to block button",
+ mousedown: UrlbarPrefs.get("resultMenu")
+ ? ".urlbarView-row:nth-child(1) .urlbarView-button-menu"
+ : ".urlbarView-row:nth-child(1) .urlbarView-button-block",
+ afterMouseupCallback: UrlbarPrefs.get("resultMenu")
+ ? assertResultMenuOpen
+ : assertBlockResultCalled,
+ expected: {
+ mousedownSelected: false,
+ topSites: {
+ pageProxyState: "valid",
+ value: initialTabUrl,
+ },
+ searchString: {
+ pageProxyState: "invalid",
+ value: searchString,
+ },
+ },
+ },
+ {
+ skip: UrlbarPrefs.get("resultMenu"),
+ description: "Help button to help button",
+ mousedown: ".urlbarView-row:nth-child(1) .urlbarView-button-help",
+ expected: {
+ mousedownSelected: false,
+ url: mainResultHelpUrl,
+ newTab: true,
+ },
+ },
+ {
+ description: UrlbarPrefs.get("resultMenu")
+ ? "Row-inner to menu button"
+ : "Row-inner to block button",
+ mousedown: ".urlbarView-row:nth-child(1) > .urlbarView-row-inner",
+ mouseup: UrlbarPrefs.get("resultMenu")
+ ? ".urlbarView-row:nth-child(1) .urlbarView-button-menu"
+ : ".urlbarView-row:nth-child(1) .urlbarView-button-block",
+ afterMouseupCallback: UrlbarPrefs.get("resultMenu")
+ ? assertResultMenuOpen
+ : assertBlockResultCalled,
+ expected: {
+ mousedownSelected: true,
+ topSites: {
+ pageProxyState: "invalid",
+ value: UrlbarPrefs.get("resultMenu") ? initialTabUrl : otherResultUrl,
+ },
+ searchString: {
+ pageProxyState: "invalid",
+ value: UrlbarPrefs.get("resultMenu") ? searchString : otherResultUrl,
+ },
+ },
+ },
+ {
+ description: UrlbarPrefs.get("resultMenu")
+ ? "Menu button to row-inner"
+ : "Block button to row-inner",
+ mousedown: UrlbarPrefs.get("resultMenu")
+ ? ".urlbarView-row:nth-child(1) .urlbarView-button-menu"
+ : ".urlbarView-row:nth-child(1) .urlbarView-button-block",
+ mouseup: ".urlbarView-row:nth-child(1) > .urlbarView-row-inner",
+ expected: {
+ mousedownSelected: false,
+ url: mainResultUrl,
+ newTab: false,
+ },
+ },
+ ];
+
+ for (let showTopSites of [true, false]) {
+ for (let {
+ description,
+ mousedown,
+ mouseup,
+ expected,
+ afterMouseupCallback = null,
+ skip = false,
+ } of testData) {
+ if (skip) {
+ info(
+ `Skipping test with showTopSites = ${showTopSites}: ${description}`
+ );
+ continue;
+ }
+ info(`Running test with showTopSites = ${showTopSites}: ${description}`);
+ mouseup ||= mousedown;
+
+ await BrowserTestUtils.withNewTab(initialTabUrl, async () => {
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ "valid",
+ "Sanity check: pageproxystate should be valid initially"
+ );
+ Assert.equal(
+ gURLBar.value,
+ initialTabUrl,
+ "Sanity check: input.value should be the initial URL initially"
+ );
+
+ if (showTopSites) {
+ // Open the view and show top sites by performing the accel+L command.
+ await SimpleTest.promiseFocus(window);
+ let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+ document.getElementById("Browser:OpenLocation").doCommand();
+ await searchPromise;
+ } else {
+ // Do a search.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ });
+ }
+
+ let [downElement, upElement] = await waitForElements([
+ mousedown,
+ mouseup,
+ ]);
+
+ // Mousedown and check the selection.
+ EventUtils.synthesizeMouseAtCenter(downElement, {
+ type: "mousedown",
+ });
+ if (expected.mousedownSelected) {
+ Assert.ok(
+ downElement.hasAttribute("selected"),
+ "Mousedown element should be selected after mousedown"
+ );
+ } else {
+ Assert.ok(
+ !downElement.hasAttribute("selected"),
+ "Mousedown element should not be selected after mousedown"
+ );
+ }
+
+ let loadPromise;
+ if (expected.url) {
+ loadPromise = expected.newTab
+ ? BrowserTestUtils.waitForNewTab(gBrowser, expected.url)
+ : BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ null,
+ expected.url
+ );
+ }
+
+ // Mouseup and check the selection.
+ EventUtils.synthesizeMouseAtCenter(upElement, { type: "mouseup" });
+ Assert.ok(
+ !downElement.hasAttribute("selected"),
+ "Mousedown element should not be selected after mouseup"
+ );
+ Assert.ok(
+ !upElement.hasAttribute("selected"),
+ "Mouseup element should not be selected after mouseup"
+ );
+
+ // If we expect a URL to load, we're done since the view will close and
+ // the input value will be set to the URL.
+ if (loadPromise) {
+ info("Waiting for URL to load: " + expected.url);
+ let tab = await loadPromise;
+ Assert.ok(true, "Expected URL loaded");
+ if (expected.newTab) {
+ BrowserTestUtils.removeTab(tab);
+ }
+ return;
+ }
+
+ if (afterMouseupCallback) {
+ await afterMouseupCallback();
+ }
+
+ let state = showTopSites ? expected.topSites : expected.searchString;
+ Assert.equal(
+ gURLBar.getAttribute("pageproxystate"),
+ state.pageProxyState,
+ "pageproxystate should be as expected"
+ );
+ Assert.equal(
+ gURLBar.value,
+ state.value,
+ "input.value should be as expected"
+ );
+ });
+ }
+ }
+
+ UrlbarProvidersManager.unregisterProvider(provider);
+});
+
+async function waitForElements(selectors) {
+ let elements;
+ await BrowserTestUtils.waitForCondition(() => {
+ elements = selectors.map(s => document.querySelector(s));
+ return elements.every(e => e && BrowserTestUtils.is_visible(e));
+ }, "Waiting for elements to become visible: " + JSON.stringify(selectors));
+ return elements;
+}
diff --git a/browser/components/urlbar/tests/browser/browser_waitForLoadOrTimeout.js b/browser/components/urlbar/tests/browser/browser_waitForLoadOrTimeout.js
new file mode 100644
index 0000000000..352e37b9d0
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_waitForLoadOrTimeout.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the waitForLoadOrTimeout test helper function in head.js.
+ */
+
+"use strict";
+
+add_task(async function load() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ let url = "http://example.com/";
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: url,
+ });
+
+ let loadPromise = waitForLoadOrTimeout();
+ EventUtils.synthesizeKey("KEY_Enter");
+ let loadEvent = await loadPromise;
+
+ Assert.ok(loadEvent, "Page should have loaded before timeout");
+ Assert.equal(
+ loadEvent.target.currentURI.spec,
+ url,
+ "example.com should have loaded"
+ );
+ });
+});
+
+add_task(async function timeout() {
+ let loadEvent = await waitForLoadOrTimeout();
+ Assert.ok(
+ !loadEvent,
+ "No page should have loaded, and timeout should have fired"
+ );
+});
diff --git a/browser/components/urlbar/tests/browser/browser_whereToOpen.js b/browser/components/urlbar/tests/browser/browser_whereToOpen.js
new file mode 100644
index 0000000000..339a20d90e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_whereToOpen.js
@@ -0,0 +1,192 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const NON_EMPTY_TAB = "example.com/non-empty";
+const EMPTY_TAB = "about:blank";
+const META_KEY = AppConstants.platform == "macosx" ? "metaKey" : "ctrlKey";
+const ENTER = new KeyboardEvent("keydown", {});
+const ALT_ENTER = new KeyboardEvent("keydown", { altKey: true });
+const ALTGR_ENTER = new KeyboardEvent("keydown", { modifierAltGraph: true });
+const CLICK = new MouseEvent("click", { button: 0 });
+const META_CLICK = new MouseEvent("click", { button: 0, [META_KEY]: true });
+const MIDDLE_CLICK = new MouseEvent("click", { button: 1 });
+
+let old_openintab = Preferences.get("browser.urlbar.openintab");
+registerCleanupFunction(async function () {
+ Preferences.set("browser.urlbar.openintab", old_openintab);
+});
+
+add_task(async function openInTab() {
+ // Open a non-empty tab.
+ let tab = (gBrowser.selectedTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ NON_EMPTY_TAB
+ ));
+
+ for (let test of [
+ {
+ pref: false,
+ event: ALT_ENTER,
+ desc: "Alt+Enter, non-empty tab, default prefs",
+ },
+ {
+ pref: false,
+ event: ALTGR_ENTER,
+ desc: "AltGr+Enter, non-empty tab, default prefs",
+ },
+ {
+ pref: false,
+ event: META_CLICK,
+ desc: "Meta+click, non-empty tab, default prefs",
+ },
+ {
+ pref: false,
+ event: MIDDLE_CLICK,
+ desc: "Middle click, non-empty tab, default prefs",
+ },
+ { pref: true, event: ENTER, desc: "Enter, non-empty tab, openInTab" },
+ {
+ pref: true,
+ event: CLICK,
+ desc: "Normal click, non-empty tab, openInTab",
+ },
+ ]) {
+ info(test.desc);
+
+ Preferences.set("browser.urlbar.openintab", test.pref);
+ let where = gURLBar._whereToOpen(test.event);
+ is(where, "tab", "URL would be loaded in a new tab");
+ }
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function keepEmptyTab() {
+ // Open an empty tab.
+ let tab = (gBrowser.selectedTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ EMPTY_TAB
+ ));
+
+ for (let test of [
+ {
+ pref: false,
+ event: META_CLICK,
+ desc: "Meta+click, empty tab, default prefs",
+ },
+ {
+ pref: false,
+ event: MIDDLE_CLICK,
+ desc: "Middle click, empty tab, default prefs",
+ },
+ ]) {
+ info(test.desc);
+
+ Preferences.set("browser.urlbar.openintab", test.pref);
+ let where = gURLBar._whereToOpen(test.event);
+ is(where, "tab", "URL would be loaded in a new tab");
+ }
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function reuseEmptyTab() {
+ // Open an empty tab.
+ let tab = (gBrowser.selectedTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ EMPTY_TAB
+ ));
+
+ for (let test of [
+ {
+ pref: false,
+ event: ALT_ENTER,
+ desc: "Alt+Enter, empty tab, default prefs",
+ },
+ {
+ pref: false,
+ event: ALTGR_ENTER,
+ desc: "AltGr+Enter, empty tab, default prefs",
+ },
+ { pref: true, event: ENTER, desc: "Enter, empty tab, openInTab" },
+ { pref: true, event: CLICK, desc: "Normal click, empty tab, openInTab" },
+ ]) {
+ info(test.desc);
+ Preferences.set("browser.urlbar.openintab", test.pref);
+ let where = gURLBar._whereToOpen(test.event);
+ is(where, "current", "New URL would reuse the current empty tab");
+ }
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function openInCurrentTab() {
+ for (let test of [
+ {
+ pref: false,
+ url: NON_EMPTY_TAB,
+ event: ENTER,
+ desc: "Enter, non-empty tab, default prefs",
+ },
+ {
+ pref: false,
+ url: NON_EMPTY_TAB,
+ event: CLICK,
+ desc: "Normal click, non-empty tab, default prefs",
+ },
+ {
+ pref: false,
+ url: EMPTY_TAB,
+ event: ENTER,
+ desc: "Enter, empty tab, default prefs",
+ },
+ {
+ pref: false,
+ url: EMPTY_TAB,
+ event: CLICK,
+ desc: "Normal click, empty tab, default prefs",
+ },
+ {
+ pref: true,
+ url: NON_EMPTY_TAB,
+ event: ALT_ENTER,
+ desc: "Alt+Enter, non-empty tab, openInTab",
+ },
+ {
+ pref: true,
+ url: NON_EMPTY_TAB,
+ event: ALTGR_ENTER,
+ desc: "AltGr+Enter, non-empty tab, openInTab",
+ },
+ {
+ pref: true,
+ url: NON_EMPTY_TAB,
+ event: META_CLICK,
+ desc: "Meta+click, non-empty tab, openInTab",
+ },
+ {
+ pref: true,
+ url: NON_EMPTY_TAB,
+ event: MIDDLE_CLICK,
+ desc: "Middle click, non-empty tab, openInTab",
+ },
+ ]) {
+ info(test.desc);
+
+ // Open a new tab.
+ let tab = (gBrowser.selectedTab =
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, test.url));
+
+ Preferences.set("browser.urlbar.openintab", test.pref);
+ let where = gURLBar._whereToOpen(test.event);
+ is(where, "current", "URL would open in the current tab");
+
+ // Clean up.
+ BrowserTestUtils.removeTab(tab);
+ }
+});
diff --git a/browser/components/urlbar/tests/browser/dummy_page.html b/browser/components/urlbar/tests/browser/dummy_page.html
new file mode 100644
index 0000000000..1a87e28408
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/dummy_page.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/browser/components/urlbar/tests/browser/dynamicResult0.css b/browser/components/urlbar/tests/browser/dynamicResult0.css
new file mode 100644
index 0000000000..328127b594
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/dynamicResult0.css
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+#urlbar {
+ --testDynamicResult0: ok0;
+}
+
+.urlbarView-row[dynamicType=test] > .urlbarView-row-inner {
+ display: flex;
+ align-items: center;
+ min-height: 32px;
+ width: 100%;
+}
+
+.urlbarView-dynamic-test-buttonBox {
+ display: flex;
+ align-items: center;
+ min-height: 32px;
+}
+
+.urlbarView-dynamic-test-text {
+ flex-grow: 1;
+ flex-shrink: 1;
+ padding: 10px;
+}
+
+.urlbarView-dynamic-test-selectable,
+.urlbarView-dynamic-test-button1,
+.urlbarView-dynamic-test-button2 {
+ min-height: 16px;
+ padding: 8px;
+ border: none;
+ border-radius: 2px;
+ font-size: 0.93em;
+ color: inherit;
+ background-color: var(--urlbarView-button-background);
+ min-width: 8.75em;
+ text-align: center;
+ flex-basis: initial;
+ flex-shrink: 0;
+ margin-inline-end: 10px;
+}
+
+.urlbarView-dynamic-test-selectable[selected],
+.urlbarView-dynamic-test-button1[selected],
+.urlbarView-dynamic-test-button2[selected] {
+ color: white;
+ background-color: var(--urlbarView-primary-button-background);
+ box-shadow: 0 0 0 1px #0a84ff inset, 0 0 0 1px #0a84ff, 0 0 0 4px rgba(10, 132, 255, 0.3);
+}
diff --git a/browser/components/urlbar/tests/browser/dynamicResult1.css b/browser/components/urlbar/tests/browser/dynamicResult1.css
new file mode 100644
index 0000000000..ae43fd3f9a
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/dynamicResult1.css
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+#urlbar {
+ --testDynamicResult1: ok1;
+}
+
+.urlbarView-row[dynamicType=test] > .urlbarView-row-inner {
+ display: flex;
+ align-items: center;
+ min-height: 32px;
+ width: 100%;
+}
+
+.urlbarView-dynamic-test-buttonBox {
+ display: flex;
+ align-items: center;
+ min-height: 32px;
+}
+
+.urlbarView-dynamic-test-text {
+ flex-grow: 1;
+ flex-shrink: 1;
+ padding: 10px;
+}
+
+.urlbarView-dynamic-test-selectable,
+.urlbarView-dynamic-test-button1,
+.urlbarView-dynamic-test-button2 {
+ min-height: 16px;
+ padding: 8px;
+ border: none;
+ border-radius: 2px;
+ font-size: 0.93em;
+ color: inherit;
+ background-color: var(--urlbarView-button-background);
+ min-width: 8.75em;
+ text-align: center;
+ flex-basis: initial;
+ flex-shrink: 0;
+ margin-inline-end: 10px;
+}
+
+.urlbarView-dynamic-test-selectable[selected],
+.urlbarView-dynamic-test-button1[selected],
+.urlbarView-dynamic-test-button2[selected] {
+ color: white;
+ background-color: var(--urlbarView-primary-button-background);
+ box-shadow: 0 0 0 1px #0a84ff inset, 0 0 0 1px #0a84ff, 0 0 0 4px rgba(10, 132, 255, 0.3);
+}
diff --git a/browser/components/urlbar/tests/browser/file_blank_but_not_blank.html b/browser/components/urlbar/tests/browser/file_blank_but_not_blank.html
new file mode 100644
index 0000000000..1f5fea8dcf
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/file_blank_but_not_blank.html
@@ -0,0 +1,2 @@
+<script>var q = "1";</script>
+<a href="javascript:q">Click me</a>
diff --git a/browser/components/urlbar/tests/browser/file_urlbar_edit_dos.html b/browser/components/urlbar/tests/browser/file_urlbar_edit_dos.html
new file mode 100644
index 0000000000..e02242f6a1
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/file_urlbar_edit_dos.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Try editing the URL bar</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<script>
+function dos_hash() {
+ location.hash = "#";
+}
+
+function dos_pushState() {
+ history.pushState({}, "Some title", "");
+}
+</script>
+</body>
+</html>
diff --git a/browser/components/urlbar/tests/browser/file_userTypedValue.html b/browser/components/urlbar/tests/browser/file_userTypedValue.html
new file mode 100644
index 0000000000..a787b70898
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/file_userTypedValue.html
@@ -0,0 +1 @@
+<html><body>bug562649</body></html>
diff --git a/browser/components/urlbar/tests/browser/head-common.js b/browser/components/urlbar/tests/browser/head-common.js
new file mode 100644
index 0000000000..4c41483944
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/head-common.js
@@ -0,0 +1,156 @@
+ChromeUtils.defineESModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ Preferences: "resource://gre/modules/Preferences.sys.mjs",
+ UrlbarProvider: "resource:///modules/UrlbarUtils.sys.mjs",
+ UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.sys.mjs",
+ UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
+ UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.sys.mjs",
+ UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ HttpServer: "resource://testing-common/httpd.js",
+});
+
+XPCOMUtils.defineLazyGetter(this, "TEST_BASE_URL", () =>
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ )
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "clipboardHelper",
+ "@mozilla.org/widget/clipboardhelper;1",
+ "nsIClipboardHelper"
+);
+
+XPCOMUtils.defineLazyGetter(this, "UrlbarTestUtils", () => {
+ const { UrlbarTestUtils: module } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlbarTestUtils.sys.mjs"
+ );
+ module.init(this);
+ return module;
+});
+
+XPCOMUtils.defineLazyGetter(this, "SearchTestUtils", () => {
+ const { SearchTestUtils: module } = ChromeUtils.importESModule(
+ "resource://testing-common/SearchTestUtils.sys.mjs"
+ );
+ module.init(this);
+ return module;
+});
+
+/**
+ * Initializes an HTTP Server, and runs a task with it.
+ *
+ * @param {object} details {scheme, host, port}
+ * @param {Function} taskFn The task to run, gets the server as argument.
+ */
+async function withHttpServer(
+ details = { scheme: "http", host: "localhost", port: -1 },
+ taskFn
+) {
+ let server = new HttpServer();
+ let url = `${details.scheme}://${details.host}:${details.port}`;
+ try {
+ info(`starting HTTP Server for ${url}`);
+ try {
+ server.start(details.port);
+ details.port = server.identity.primaryPort;
+ server.identity.setPrimary(details.scheme, details.host, details.port);
+ } catch (ex) {
+ throw new Error("We can't launch our http server successfully. " + ex);
+ }
+ Assert.ok(
+ server.identity.has(details.scheme, details.host, details.port),
+ `${url} is listening.`
+ );
+ try {
+ await taskFn(server);
+ } catch (ex) {
+ throw new Error("Exception in the task function " + ex);
+ }
+ } finally {
+ server.identity.remove(details.scheme, details.host, details.port);
+ try {
+ await new Promise(resolve => server.stop(resolve));
+ } catch (ex) {}
+ server = null;
+ }
+}
+
+/**
+ * Updates the Top Sites feed.
+ *
+ * @param {Function} condition
+ * A callback that returns true after Top Sites are successfully updated.
+ * @param {boolean} searchShortcuts
+ * True if Top Sites search shortcuts should be enabled.
+ */
+async function updateTopSites(condition, searchShortcuts = false) {
+ // Toggle the pref to clear the feed cache and force an update.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "browser.newtabpage.activity-stream.discoverystream.endpointSpocsClear",
+ "",
+ ],
+ ["browser.newtabpage.activity-stream.feeds.system.topsites", false],
+ ["browser.newtabpage.activity-stream.feeds.system.topsites", true],
+ [
+ "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts",
+ searchShortcuts,
+ ],
+ ],
+ });
+
+ // Wait for the feed to be updated.
+ await TestUtils.waitForCondition(() => {
+ let sites = AboutNewTab.getTopSites();
+ return condition(sites);
+ }, "Waiting for top sites to be updated");
+}
+
+/**
+ * Asserts a search term is in the url bar and state values are
+ * what they should be.
+ *
+ * @param {string} searchString
+ * String that should be matched in the url bar.
+ * @param {object | null} options
+ * Options for the assertions.
+ * @param {Window | null} options.window
+ * Window to use for tests.
+ * @param {string | null} options.pageProxyState
+ * The pageproxystate that should be expected. Defaults to "valid".
+ * @param {string | null} options.userTypedValue
+ * The userTypedValue that should be expected. Defaults to null.
+ */
+function assertSearchStringIsInUrlbar(
+ searchString,
+ { win = window, pageProxyState = "valid", userTypedValue = null } = {}
+) {
+ Assert.equal(
+ win.gURLBar.value,
+ searchString,
+ `Search string should be the urlbar value.`
+ );
+ Assert.equal(
+ win.gBrowser.selectedBrowser.searchTerms,
+ searchString,
+ `Search terms should match.`
+ );
+ Assert.equal(
+ win.gBrowser.userTypedValue,
+ userTypedValue,
+ "userTypedValue should match."
+ );
+ Assert.equal(
+ win.gURLBar.getAttribute("pageproxystate"),
+ pageProxyState,
+ "Pageproxystate should match."
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/head.js b/browser/components/urlbar/tests/browser/head.js
new file mode 100644
index 0000000000..4d381320c9
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/head.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * These tests unit test the result/url loading functionality of UrlbarController.
+ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
+ ExperimentFakes: "resource://testing-common/NimbusTestUtils.sys.mjs",
+ PromiseUtils: "resource://gre/modules/PromiseUtils.sys.mjs",
+ PromptTestUtils: "resource://testing-common/PromptTestUtils.sys.mjs",
+ ResetProfile: "resource://gre/modules/ResetProfile.sys.mjs",
+ SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
+ UrlbarController: "resource:///modules/UrlbarController.sys.mjs",
+ UrlbarQueryContext: "resource:///modules/UrlbarUtils.sys.mjs",
+ UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
+ UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.sys.mjs",
+ UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
+ UrlbarView: "resource:///modules/UrlbarView.sys.mjs",
+ sinon: "resource://testing-common/Sinon.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AboutNewTab: "resource:///modules/AboutNewTab.jsm",
+ ObjectUtils: "resource://gre/modules/ObjectUtils.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(this, "PlacesFrecencyRecalculator", () => {
+ return Cc["@mozilla.org/places/frecency-recalculator;1"].getService(
+ Ci.nsIObserver
+ ).wrappedJSObject;
+});
+
+let sandbox;
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/head-common.js",
+ this
+);
+
+registerCleanupFunction(async () => {
+ // Ensure the Urlbar popup is always closed at the end of a test, to save having
+ // to do it within each test.
+ await UrlbarTestUtils.promisePopupClose(window);
+});
+
+async function selectAndPaste(str, win = window) {
+ await SimpleTest.promiseClipboardChange(str, () => {
+ clipboardHelper.copyString(str);
+ });
+ win.gURLBar.select();
+ win.document.commandDispatcher
+ .getControllerForCommand("cmd_paste")
+ .doCommand("cmd_paste");
+}
+
+/**
+ * Waits for a load in any browser or a timeout, whichever comes first.
+ *
+ * @param {window} win
+ * The top-level browser window to listen in.
+ * @param {number} timeoutMs
+ * The timeout in ms.
+ * @returns {event|null}
+ * If a load event was detected before the timeout fired, then the event is
+ * returned. event.target will be the browser in which the load occurred. If
+ * the timeout fired before a load was detected, null is returned.
+ */
+async function waitForLoadOrTimeout(win = window, timeoutMs = 1000) {
+ let event;
+ let listener;
+ let timeout;
+ let eventName = "BrowserTestUtils:ContentEvent:load";
+ try {
+ event = await Promise.race([
+ new Promise(resolve => {
+ listener = resolve;
+ win.addEventListener(eventName, listener, true);
+ }),
+ new Promise(resolve => {
+ timeout = win.setTimeout(resolve, timeoutMs);
+ }),
+ ]);
+ } finally {
+ win.removeEventListener(eventName, listener, true);
+ win.clearTimeout(timeout);
+ }
+ return event || null;
+}
+
+/**
+ * Opens the url bar context menu by synthesizing a click.
+ * Returns a menu item that is specified by an id.
+ *
+ * @param {string} anonid - Identifier of a menu item of the url bar context menu.
+ * @returns {string} - The element that has the corresponding identifier.
+ */
+async function promiseContextualMenuitem(anonid) {
+ let textBox = gURLBar.querySelector("moz-input-box");
+ let cxmenu = textBox.menupopup;
+ let cxmenuPromise = BrowserTestUtils.waitForEvent(cxmenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {
+ type: "contextmenu",
+ button: 2,
+ });
+ await cxmenuPromise;
+ return textBox.getMenuItem(anonid);
+}
+
+/**
+ * Puts all CustomizableUI widgetry back to their default locations, and
+ * then fires the `aftercustomization` toolbox event so that UrlbarInput
+ * knows to reinitialize itself.
+ *
+ * @param {window} [win=window]
+ * The top-level browser window to fire the `aftercustomization` event in.
+ */
+function resetCUIAndReinitUrlbarInput(win = window) {
+ CustomizableUI.reset();
+ CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, win);
+}
diff --git a/browser/components/urlbar/tests/browser/moz.png b/browser/components/urlbar/tests/browser/moz.png
new file mode 100644
index 0000000000..769c636340
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/moz.png
Binary files differ
diff --git a/browser/components/urlbar/tests/browser/print_postdata.sjs b/browser/components/urlbar/tests/browser/print_postdata.sjs
new file mode 100644
index 0000000000..5884a1d598
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/print_postdata.sjs
@@ -0,0 +1,25 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ if (request.method == "GET") {
+ response.write(request.queryString);
+ } else {
+ let body = new BinaryInputStream(request.bodyInputStream);
+
+ let avail;
+ let bytes = [];
+
+ while ((avail = body.available()) > 0) {
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+ }
+
+ let data = String.fromCharCode.apply(null, bytes);
+ response.bodyOutputStream.write(data, data.length);
+ }
+}
diff --git a/browser/components/urlbar/tests/browser/redirect_error.sjs b/browser/components/urlbar/tests/browser/redirect_error.sjs
new file mode 100644
index 0000000000..a3937b0e7a
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/redirect_error.sjs
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host.
+
+function handleRequest(aRequest, aResponse) {
+ // Set HTTP Status
+ aResponse.setStatusLine(aRequest.httpVersion, 301, "Moved Permanently");
+
+ // Set redirect URI, mirroring the hash value.
+ let hash = /\#.+/.test(aRequest.path)
+ ? "#" + aRequest.path.split("#")[1]
+ : "";
+ aResponse.setHeader("Location", REDIRECT_TO + hash);
+}
diff --git a/browser/components/urlbar/tests/browser/redirect_to.sjs b/browser/components/urlbar/tests/browser/redirect_to.sjs
new file mode 100644
index 0000000000..b52ebdc63e
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/redirect_to.sjs
@@ -0,0 +1,9 @@
+"use strict";
+
+function handleRequest(request, response) {
+ // redirect_to.sjs?ctxmenu-image.png
+ // redirects to : ctxmenu-image.png
+ const redirectUrl = request.queryString;
+ response.setStatusLine(request.httpVersion, "302", "Found");
+ response.setHeader("Location", redirectUrl, false);
+}
diff --git a/browser/components/urlbar/tests/browser/searchSuggestionEngine.sjs b/browser/components/urlbar/tests/browser/searchSuggestionEngine.sjs
new file mode 100644
index 0000000000..145392fcf2
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/searchSuggestionEngine.sjs
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let gTimer;
+
+function handleRequest(req, resp) {
+ // Parse the query params. If the params aren't in the form "foo=bar", then
+ // treat the entire query string as a search string.
+ let params = req.queryString.split("&").reduce((memo, pair) => {
+ let [key, val] = pair.split("=");
+ if (!val) {
+ // This part isn't in the form "foo=bar". Treat it as the search string
+ // (the "query").
+ val = key;
+ key = "query";
+ }
+ memo[decode(key)] = decode(val);
+ return memo;
+ }, {});
+
+ let timeout = parseInt(params.timeout);
+ if (timeout) {
+ // Write the response after a timeout.
+ resp.processAsync();
+ gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ gTimer.init(
+ () => {
+ writeResponse(params, resp);
+ resp.finish();
+ },
+ timeout,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+ return;
+ }
+
+ writeResponse(params, resp);
+}
+
+function writeResponse(params, resp) {
+ // Echo back the search string with "foo" and "bar" appended.
+ let suffixes = ["foo", "bar"];
+ if (params.count) {
+ // Add more suffixes.
+ let serial = 0;
+ while (suffixes.length < params.count) {
+ suffixes.push(++serial);
+ }
+ }
+ let data = [params.query, suffixes.map(s => params.query + s)];
+ resp.setHeader("Content-Type", "application/json", false);
+ resp.write(JSON.stringify(data));
+}
+
+function decode(str) {
+ return decodeURIComponent(str.replace(/\+/g, encodeURIComponent(" ")));
+}
diff --git a/browser/components/urlbar/tests/browser/searchSuggestionEngine.xml b/browser/components/urlbar/tests/browser/searchSuggestionEngine.xml
new file mode 100644
index 0000000000..142c91849c
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/searchSuggestionEngine.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/searchSuggestionEngine.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform">
+ <Param name="terms" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/components/urlbar/tests/browser/searchSuggestionEngine2.xml b/browser/components/urlbar/tests/browser/searchSuggestionEngine2.xml
new file mode 100644
index 0000000000..565aaf2bc0
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/searchSuggestionEngine2.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngine2 searchSuggestionEngine2.xml</ShortName>
+<!-- Redirect the actual search request to the test-server because of proxy restriction -->
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/searchSuggestionEngine.sjs?{searchTerms}"/>
+<!-- Redirect speculative connect to a local http server we run for this test -->
+<Url type="text/html" method="GET" template="http://localhost:20709/" rel="searchform">
+ <Param name="terms" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/components/urlbar/tests/browser/searchSuggestionEngineMany.xml b/browser/components/urlbar/tests/browser/searchSuggestionEngineMany.xml
new file mode 100644
index 0000000000..7e77e32029
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/searchSuggestionEngineMany.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngineMany searchSuggestionEngineMany.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/searchSuggestionEngine.sjs?{searchTerms}&amp;count=10"/>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform">
+ <Param name="terms" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/components/urlbar/tests/browser/searchSuggestionEngineSlow.xml b/browser/components/urlbar/tests/browser/searchSuggestionEngineSlow.xml
new file mode 100644
index 0000000000..e7214e65cc
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/searchSuggestionEngineSlow.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>searchSuggestionEngineSlow.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/searchSuggestionEngine.sjs?query={searchTerms}&amp;timeout=3000"/>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform">
+ <Param name="terms" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/components/urlbar/tests/browser/slow-page.sjs b/browser/components/urlbar/tests/browser/slow-page.sjs
new file mode 100644
index 0000000000..ce9a759744
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/slow-page.sjs
@@ -0,0 +1,23 @@
+"use strict";
+
+let timer;
+
+const DELAY_MS = 5000;
+function handleRequest(request, response) {
+ if (request.queryString.endsWith("faster")) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<body>Not so slow!</body>");
+ return;
+ }
+ response.processAsync();
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(
+ () => {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<body>This is a slow loading page.</body>");
+ response.finish();
+ },
+ DELAY_MS,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/browser/components/urlbar/tests/browser/urlbarTelemetrySearchSuggestions.sjs b/browser/components/urlbar/tests/browser/urlbarTelemetrySearchSuggestions.sjs
new file mode 100644
index 0000000000..1978b4f665
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/urlbarTelemetrySearchSuggestions.sjs
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(req, resp) {
+ let suffixes = ["foo", "bar"];
+ let data = [req.queryString, suffixes.map(s => req.queryString + s)];
+ resp.setHeader("Content-Type", "application/json", false);
+ resp.write(JSON.stringify(data));
+}
diff --git a/browser/components/urlbar/tests/browser/urlbarTelemetrySearchSuggestions.xml b/browser/components/urlbar/tests/browser/urlbarTelemetrySearchSuggestions.xml
new file mode 100644
index 0000000000..8ed4fef6f1
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/urlbarTelemetrySearchSuggestions.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_urlbar_telemetry urlbarTelemetrySearchSuggestions.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/components/urlbar/tests/browser/urlbarTelemetrySearchSuggestions.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://example.com" rel="searchform"/>
+</SearchPlugin>
diff --git a/browser/components/urlbar/tests/browser/urlbarTelemetryUrlbarDynamic.css b/browser/components/urlbar/tests/browser/urlbarTelemetryUrlbarDynamic.css
new file mode 100644
index 0000000000..e81052522f
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/urlbarTelemetryUrlbarDynamic.css
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+.urlbarView-row[dynamicType=test] > .urlbarView-row-inner {
+ display: flex;
+ align-items: center;
+ min-height: 32px;
+ width: 100%;
+}
+
+.urlbarView-dynamic-test-button {
+ min-height: 16px;
+ padding: 8px;
+ border: none;
+ border-radius: 2px;
+ font-size: 0.93em;
+ color: inherit;
+ background-color: var(--urlbarView-button-background);
+ min-width: 8.75em;
+ text-align: center;
+ flex-basis: initial;
+ flex-shrink: 0;
+}
+
+.urlbarView-dynamic-test-button[selected] {
+ color: white;
+ background-color: var(--urlbarView-primary-button-background);
+ box-shadow: 0 0 0 1px #0a84ff inset, 0 0 0 1px #0a84ff, 0 0 0 4px rgba(10, 132, 255, 0.3);
+}
+
+.urlbarView-dynamic-test-button:hover {
+ color: white;
+ background-color: var(--urlbarView-primary-button-background-hover);
+}
+
+.urlbarView-dynamic-test-button:active {
+ color: white;
+ background-color: var(--urlbarView-primary-button-background-active);
+}
+
+.urlbarView-dynamic-test-buttonSpacer {
+ flex-basis: 48px;
+ flex-grow: 1;
+ flex-shrink: 1;
+}