tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_search_userEngineDialog.js (22275B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   https://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 requestLongerTimeout(2);
      7 
      8 const { PlacesTestUtils } = ChromeUtils.importESModule(
      9  "resource://testing-common/PlacesTestUtils.sys.mjs"
     10 );
     11 const { SearchTestUtils } = ChromeUtils.importESModule(
     12  "resource://testing-common/SearchTestUtils.sys.mjs"
     13 );
     14 const { SearchUtils } = ChromeUtils.importESModule(
     15  "moz-src:///toolkit/components/search/SearchUtils.sys.mjs"
     16 );
     17 
     18 add_task(async function test_addEngineGet() {
     19  await openPreferencesViaOpenPreferencesAPI("search", {
     20    leaveOpen: true,
     21  });
     22 
     23  // Add new engine via add engine dialog.
     24  let doc = gBrowser.contentDocument;
     25  let addButton = doc.querySelector("#addEngineButton");
     26  let dialogWin = await openDialogWith(doc, () => addButton.click());
     27  Assert.equal(
     28    dialogWin.document.getElementById("titleContainer").style.display,
     29    "none",
     30    "Adjustable title is hidden in add engine dialog."
     31  );
     32 
     33  setName("Bugzilla", dialogWin);
     34  setUrl(
     35    "https://bugzilla.mozilla.org/buglist.cgi?quicksearch=%s&list_id=17442621",
     36    dialogWin
     37  );
     38  await setAlias("bz", dialogWin);
     39 
     40  let advanced = dialogWin.document.querySelector("dialog").getButton("extra1");
     41  Assert.ok(!advanced.hidden, "Button is visible");
     42  Assert.ok(
     43    dialogWin.document.getElementById("advanced-section").hidden,
     44    "Advanced section is hidden"
     45  );
     46  advanced.click();
     47  Assert.ok(advanced.hidden, "Button was hidden");
     48  Assert.ok(
     49    !dialogWin.document.getElementById("advanced-section").hidden,
     50    "Advanced section was made visible"
     51  );
     52 
     53  let promiseAdded = SearchTestUtils.promiseSearchNotification(
     54    SearchUtils.MODIFIED_TYPE.ADDED,
     55    SearchUtils.TOPIC_ENGINE_MODIFIED
     56  );
     57  EventUtils.synthesizeKey("VK_RETURN", {}, dialogWin);
     58  await promiseAdded;
     59  Assert.ok(true, "Got added notification.");
     60 
     61  // Check new engine.
     62  let engine = Services.search.getEngineByName("Bugzilla");
     63  Assert.equal(engine.name, "Bugzilla", "Name is correct.");
     64  Assert.equal(
     65    engine.getSubmission("föö").uri.spec,
     66    "https://bugzilla.mozilla.org/buglist.cgi?quicksearch=f%C3%B6%C3%B6&list_id=17442621",
     67    "URL is correct and encodes search terms using utf-8."
     68  );
     69  Assert.equal(engine.alias, "bz", "Alias is correct.");
     70 
     71  // Clean up.
     72  BrowserTestUtils.removeTab(gBrowser.selectedTab);
     73  await Services.search.removeEngine(engine);
     74 });
     75 
     76 add_task(async function test_addEnginePost() {
     77  await openPreferencesViaOpenPreferencesAPI("search", {
     78    leaveOpen: true,
     79  });
     80 
     81  // Add new engine via add engine dialog.
     82  let doc = gBrowser.contentDocument;
     83  let addButton = doc.querySelector("#addEngineButton");
     84  let dialogWin = await openDialogWith(doc, () => addButton.click());
     85  dialogWin.document.querySelector("dialog").getButton("extra1").click();
     86 
     87  setName("Bugzilla Post", dialogWin);
     88  setUrl("https://bugzilla.mozilla.org/buglist.cgi", dialogWin);
     89  await setAlias("bz", dialogWin);
     90  setPostData("quicksearch=%s&list_id=17442621", dialogWin);
     91  setSuggestUrl("https://bugzilla.mozilla.org/suggest?q=%s", dialogWin);
     92 
     93  let promiseAdded = SearchTestUtils.promiseSearchNotification(
     94    SearchUtils.MODIFIED_TYPE.ADDED,
     95    SearchUtils.TOPIC_ENGINE_MODIFIED
     96  );
     97  EventUtils.synthesizeKey("VK_RETURN", {}, dialogWin);
     98  await promiseAdded;
     99  Assert.ok(true, "Got added notification.");
    100 
    101  // Check new engine.
    102  let engine = Services.search.getEngineByName("Bugzilla Post");
    103  Assert.equal(engine.name, "Bugzilla Post", "Name is correct.");
    104  let submission = engine.getSubmission("föö");
    105  Assert.equal(
    106    submission.uri.spec,
    107    "https://bugzilla.mozilla.org/buglist.cgi",
    108    "URL is correct."
    109  );
    110  Assert.equal(
    111    decodePostData(submission.postData),
    112    "quicksearch=f%C3%B6%C3%B6&list_id=17442621",
    113    "Post Data is correct and encodes search terms using utf-8."
    114  );
    115  Assert.equal(
    116    engine.getSubmission("föö", SearchUtils.URL_TYPE.SUGGEST_JSON).uri.spec,
    117    "https://bugzilla.mozilla.org/suggest?q=f%C3%B6%C3%B6",
    118    "Suggest URL is correct and encodes search terms using utf-8."
    119  );
    120  Assert.equal(engine.alias, "bz", "Alias is correct.");
    121 
    122  // Clean up.
    123  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    124  await Services.search.removeEngine(engine);
    125 });
    126 
    127 add_task(async function test_validation() {
    128  await openPreferencesViaOpenPreferencesAPI("search", {
    129    leaveOpen: true,
    130  });
    131  let existingEngine = await Services.search.addUserEngine({
    132    name: "user",
    133    url: "https://example.com/user?q={searchTerms}&b=ff",
    134    alias: "u",
    135  });
    136 
    137  let doc = gBrowser.contentDocument;
    138  let addButton = doc.querySelector("#addEngineButton");
    139  let dialogWin = await openDialogWith(doc, () => addButton.click());
    140 
    141  let accept = dialogWin.document.querySelector("dialog").getButton("accept");
    142  let advanced = dialogWin.document.querySelector("dialog").getButton("extra1");
    143  let name = dialogWin.document.getElementById("engineName");
    144  let url = dialogWin.document.getElementById("engineUrl");
    145  let alias = dialogWin.document.getElementById("engineAlias");
    146  let suggestUrl = dialogWin.document.getElementById("suggestUrl");
    147  let postData = dialogWin.document.getElementById("enginePostData");
    148 
    149  Assert.ok(
    150    name.value == "" && url.value == "" && alias.value == "",
    151    "Everything is empty initially."
    152  );
    153  Assert.ok(accept.disabled, "Button is disabled initially.");
    154  await assertError(name, "add-engine-no-name");
    155  await assertError(url, "add-engine-no-url");
    156  await assertError(alias, null);
    157  await assertError(suggestUrl, null);
    158 
    159  setName("Example", dialogWin);
    160  setUrl("https://example.com/search?q=%s", dialogWin);
    161  await setAlias("abc", dialogWin);
    162  setSuggestUrl("https://example.com/search?q=%s", dialogWin);
    163  Assert.ok(!accept.disabled, "Button is enabled when everything is there.");
    164 
    165  info("Checking name.");
    166  setName("", dialogWin);
    167  await assertError(name, "add-engine-no-name");
    168  Assert.ok(accept.disabled, "Name is required.");
    169 
    170  setName(existingEngine.name, dialogWin);
    171  await assertError(name, "add-engine-name-exists");
    172  Assert.ok(accept.disabled, "Existing name is not allowed.");
    173 
    174  setName("Example", dialogWin);
    175  await assertError(name, null);
    176  Assert.ok(!accept.disabled, "Good name enables the button.");
    177 
    178  info("Checking search URL.");
    179  setUrl("", dialogWin);
    180  Assert.ok(accept.disabled, "URL is required.");
    181  await assertError(url, "add-engine-no-url");
    182 
    183  setUrl("javascript://%s", dialogWin);
    184  await assertError(url, "add-engine-invalid-protocol");
    185  Assert.ok(accept.disabled, "Javascript URLs are not allowed.");
    186 
    187  setUrl("not a url", dialogWin);
    188  await assertError(url, "add-engine-invalid-url");
    189  Assert.ok(accept.disabled, "Invalid URLs are not allowed.");
    190 
    191  setUrl("https://example.com/search?q=kitten", dialogWin);
    192  await assertError(url, "add-engine-missing-terms-url");
    193  Assert.ok(accept.disabled, "URLs without %s are not allowed.");
    194 
    195  setUrl("https://example.com/search?q=%s", dialogWin);
    196  await assertError(url, null);
    197  Assert.ok(!accept.disabled, "Good URL enables the button.");
    198 
    199  info("Checking alias.");
    200  await setAlias("", dialogWin);
    201  await assertError(alias, null);
    202  Assert.ok(!accept.disabled, "Alias is not required.");
    203 
    204  await setAlias(existingEngine.alias, dialogWin);
    205  await assertError(alias, "add-engine-keyword-exists");
    206  Assert.ok(accept.disabled, "Existing alias is not allowed.");
    207 
    208  await setAlias(existingEngine.alias.toUpperCase(), dialogWin);
    209  await assertError(alias, "add-engine-keyword-exists");
    210  Assert.ok(accept.disabled, "Alias duplicate test is case insensitive.");
    211 
    212  await setAlias("abc", dialogWin);
    213  await assertError(alias, null);
    214  Assert.ok(!accept.disabled, "Good alias enables the button.");
    215 
    216  advanced.click();
    217  info("Checking suggest URL.");
    218  setSuggestUrl("javascript://%s", dialogWin);
    219  await assertError(suggestUrl, "add-engine-invalid-protocol");
    220  Assert.ok(accept.disabled, "Javascript URLs are not allowed.");
    221 
    222  setSuggestUrl("not a url", dialogWin);
    223  await assertError(suggestUrl, "add-engine-invalid-url");
    224  Assert.ok(accept.disabled, "Invalid URLs are not allowed.");
    225 
    226  setSuggestUrl("https://example.com/search?q=kitten", dialogWin);
    227  await assertError(suggestUrl, "add-engine-missing-terms-url");
    228  Assert.ok(accept.disabled, "URLs without %s are not allowed.");
    229 
    230  setSuggestUrl("https://example.com/search?q=%s", dialogWin);
    231  await assertError(suggestUrl, null);
    232  Assert.ok(!accept.disabled, "Good URL enables the button.");
    233 
    234  info("Checking post data.");
    235  setUrl("https://example.com/search", dialogWin);
    236  await assertError(url, "add-engine-missing-terms-url");
    237 
    238  setPostData("test", dialogWin);
    239  await assertError(postData, "add-engine-missing-terms-post-data");
    240  await assertError(url, null);
    241  Assert.ok(accept.disabled, "Post data without %s is not allowed.");
    242 
    243  setUrl("https://example.com/search", dialogWin);
    244  setPostData("q=%s", dialogWin);
    245  await assertError(postData, null);
    246  await assertError(url, null);
    247  Assert.ok(!accept.disabled, "Post data containing %s enables the button.");
    248 
    249  // Clean up.
    250  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    251  await Services.search.removeEngine(existingEngine);
    252 });
    253 
    254 add_task(async function test_editGetEngine() {
    255  await openPreferencesViaOpenPreferencesAPI("search", {
    256    leaveOpen: true,
    257  });
    258 
    259  let doc = gBrowser.contentDocument;
    260  let tree = doc.querySelector("#engineList");
    261  let view = tree.view.wrappedJSObject;
    262  let engine = await Services.search.addUserEngine({
    263    name: "user",
    264    url: "https://example.com/user?q={searchTerms}&b=ff",
    265    alias: "u",
    266  });
    267  engine.wrappedJSObject.changeUrl(
    268    SearchUtils.URL_TYPE.SUGGEST_JSON,
    269    "https://example.com/suggest?query={searchTerms}",
    270    null
    271  );
    272 
    273  // Check buttons of all search engines + local shortcuts.
    274  let removeButton = doc.querySelector("#removeEngineButton");
    275  let editButton = doc.querySelector("#editEngineButton");
    276 
    277  let userEngineIndex = null;
    278  for (let i = 0; i < tree.view.rowCount; i++) {
    279    view.selection.select(i);
    280    let selectedEngine = view.selectedEngine;
    281    if (selectedEngine?.isUserEngine) {
    282      Assert.equal(selectedEngine.name, "user", "Is the new engine.");
    283      Assert.ok(!removeButton.disabled, "Remove button is enabled.");
    284      Assert.ok(!editButton.disabled, "Edit button is enabled.");
    285      userEngineIndex = i;
    286    } else {
    287      Assert.ok(editButton.disabled, "Edit button is disabled.");
    288    }
    289  }
    290 
    291  // Check if table contains new engine without reloading.
    292  Assert.ok(!!userEngineIndex, "User engine is in the table.");
    293  view.selection.select(userEngineIndex);
    294 
    295  // Open the dialog and check values.
    296  let dialogWin = await openDialogWith(doc, () => editButton.click());
    297  let acceptButton = dialogWin.document
    298    .querySelector("dialog")
    299    .shadowRoot.querySelector('button[dlgtype="accept"]');
    300 
    301  Assert.equal(
    302    dialogWin.document.getElementById("titleContainer").style.display,
    303    "none",
    304    "Adjustable title is hidden in edit engine dialog."
    305  );
    306  Assert.equal(
    307    dialogWin.document.getElementById("engineName").value,
    308    "user",
    309    "Name in dialog is correct."
    310  );
    311  Assert.equal(
    312    dialogWin.document.getElementById("engineUrl").value,
    313    "https://example.com/user?q=%s&b=ff",
    314    "URL in dialog is correct."
    315  );
    316  Assert.ok(
    317    !dialogWin.document.getElementById("advanced-section").hidden,
    318    "Advanced section is visible"
    319  );
    320  Assert.equal(
    321    dialogWin.document.getElementById("enginePostData").value,
    322    "",
    323    "Post data input is empty."
    324  );
    325  Assert.equal(
    326    dialogWin.document.getElementById("suggestUrl").value,
    327    "https://example.com/suggest?query=%s",
    328    "Suggest URL in dialog is correct"
    329  );
    330  Assert.equal(
    331    dialogWin.document.getElementById("engineAlias").value,
    332    "u",
    333    "Alias in dialog is correct."
    334  );
    335 
    336  // Set new values.
    337  setName("Searchfox", dialogWin);
    338  setUrl("https://searchfox.org/mozilla-central/search", dialogWin);
    339  await setAlias("sf", dialogWin);
    340  setSuggestUrl("", dialogWin);
    341 
    342  dialogWin.document.querySelector("dialog").getButton("extra1").click();
    343  setPostData("q=%s&path=&case=false&regexp=false", dialogWin);
    344 
    345  // Save changes to engine.
    346  let promiseChanged = SearchTestUtils.promiseSearchNotification(
    347    SearchUtils.MODIFIED_TYPE.CHANGED,
    348    SearchUtils.TOPIC_ENGINE_MODIFIED,
    349    3
    350  );
    351  acceptButton.click();
    352  await promiseChanged;
    353  Assert.ok(true, "Got 3 change notifications.");
    354 
    355  // Open dialog again and check values.
    356  dialogWin = await openDialogWith(doc, () => editButton.click());
    357  Assert.equal(
    358    dialogWin.document.getElementById("engineName").value,
    359    "Searchfox",
    360    "Name in dialog reflects change"
    361  );
    362  Assert.equal(
    363    dialogWin.document.getElementById("engineUrl").value,
    364    "https://searchfox.org/mozilla-central/search",
    365    "URL in dialog reflects change"
    366  );
    367  Assert.equal(
    368    dialogWin.document.getElementById("engineAlias").value,
    369    "sf",
    370    "Alias in dialog reflects change"
    371  );
    372  Assert.ok(
    373    !dialogWin.document.getElementById("advanced-section").hidden,
    374    "Advanced section is still visible"
    375  );
    376  Assert.equal(
    377    dialogWin.document.getElementById("enginePostData").value,
    378    "q=%s&path=&case=false&regexp=false",
    379    "Post data reflects changes"
    380  );
    381  Assert.equal(
    382    dialogWin.document.getElementById("suggestUrl").value,
    383    "",
    384    "Suggest URL in dialog was removed"
    385  );
    386 
    387  // Check search engine object.
    388  let submission = engine.getSubmission("foo");
    389  Assert.equal(
    390    submission.uri.spec,
    391    "https://searchfox.org/mozilla-central/search",
    392    "Search URL reflects changes"
    393  );
    394  Assert.equal(
    395    decodePostData(submission.postData),
    396    "q=foo&path=&case=false&regexp=false",
    397    "Engine was converted into a POST engine."
    398  );
    399  submission = engine.getSubmission("foo", SearchUtils.URL_TYPE.SUGGEST_JSON);
    400  Assert.ok(!submission, "Suggest URL was removed");
    401 
    402  // Clean up.
    403  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    404  await Services.search.removeEngine(engine);
    405 });
    406 
    407 add_task(async function test_editPostEngine() {
    408  await openPreferencesViaOpenPreferencesAPI("search", {
    409    leaveOpen: true,
    410  });
    411 
    412  let doc = gBrowser.contentDocument;
    413  let tree = doc.querySelector("#engineList");
    414  let view = tree.view.wrappedJSObject;
    415 
    416  let params = new URLSearchParams();
    417  params.append("q", "{searchTerms}");
    418  let engine = await Services.search.addUserEngine({
    419    name: "user post",
    420    url: "https://example.com/user",
    421    params,
    422    method: "POST",
    423    alias: "u",
    424  });
    425 
    426  let editButton = doc.querySelector("#editEngineButton");
    427 
    428  for (let i = 0; i < tree.view.rowCount; i++) {
    429    view.selection.select(i);
    430    let selectedEngine = view.selectedEngine;
    431    if (selectedEngine?.isUserEngine) {
    432      view.selection.select(i);
    433      break;
    434    }
    435  }
    436 
    437  // Open the dialog.
    438  let dialogWin = await openDialogWith(doc, () => editButton.click());
    439  Assert.equal(
    440    dialogWin.document.getElementById("engineName").value,
    441    "user post",
    442    "Name in dialog is correct."
    443  );
    444  Assert.equal(
    445    dialogWin.document.getElementById("engineUrl").value,
    446    "https://example.com/user",
    447    "URL in dialog is correct."
    448  );
    449  Assert.ok(
    450    !dialogWin.document.getElementById("advanced-section").hidden,
    451    "Advanced section is visible."
    452  );
    453  Assert.equal(
    454    dialogWin.document.getElementById("enginePostData").value,
    455    "q=%s",
    456    "Post data in dialog is correct."
    457  );
    458  Assert.equal(
    459    dialogWin.document.getElementById("suggestUrl").value,
    460    "",
    461    "Suggest URL in dialog is empty."
    462  );
    463  Assert.equal(
    464    dialogWin.document.getElementById("engineAlias").value,
    465    "u",
    466    "Alias in dialog is correct."
    467  );
    468 
    469  // Set new values.
    470  setName("Searchfox", dialogWin);
    471  setUrl("https://searchfox.org/mozilla-central/search", dialogWin);
    472  setPostData("q=%s&path=&case=false&regexp=false", dialogWin);
    473  setSuggestUrl("https://searchfox.org/suggest/%s", dialogWin);
    474  await setAlias("sf", dialogWin);
    475 
    476  // Save changes to engine.
    477  let promiseChanged = SearchTestUtils.promiseSearchNotification(
    478    SearchUtils.MODIFIED_TYPE.CHANGED,
    479    SearchUtils.TOPIC_ENGINE_MODIFIED,
    480    4
    481  );
    482  EventUtils.synthesizeKey("VK_RETURN", {}, dialogWin);
    483  await promiseChanged;
    484  Assert.ok(true, "Got 4 change notifications.");
    485 
    486  // Open dialog again and check values.
    487  dialogWin = await openDialogWith(doc, () => editButton.click());
    488  Assert.equal(
    489    dialogWin.document.getElementById("engineName").value,
    490    "Searchfox",
    491    "Name in dialog reflects change."
    492  );
    493  Assert.equal(
    494    dialogWin.document.getElementById("engineUrl").value,
    495    "https://searchfox.org/mozilla-central/search",
    496    "URL in dialog reflects changes."
    497  );
    498  Assert.equal(
    499    dialogWin.document.getElementById("enginePostData").value,
    500    "q=%s&path=&case=false&regexp=false",
    501    "Post data in dialog reflects changes."
    502  );
    503  Assert.equal(
    504    dialogWin.document.getElementById("suggestUrl").value,
    505    "https://searchfox.org/suggest/%s",
    506    "Suggest URL in dialog reflects changes."
    507  );
    508  Assert.equal(
    509    dialogWin.document.getElementById("engineAlias").value,
    510    "sf",
    511    "Alias in dialog reflects changes."
    512  );
    513 
    514  // Check values of search engine object.
    515  Assert.equal(
    516    engine.name,
    517    "Searchfox",
    518    "Name of search engine object was updated."
    519  );
    520  let submission = engine.getSubmission("foo");
    521  Assert.equal(
    522    submission.uri.spec,
    523    "https://searchfox.org/mozilla-central/search",
    524    "Submission URL reflects changes."
    525  );
    526  Assert.equal(
    527    decodePostData(submission.postData),
    528    "q=foo&path=&case=false&regexp=false",
    529    "Submission post data reflects changes"
    530  );
    531  submission = engine.getSubmission("foo", SearchUtils.URL_TYPE.SUGGEST_JSON);
    532  Assert.equal(
    533    submission.uri.spec,
    534    "https://searchfox.org/suggest/foo",
    535    "Submission URL reflects changes."
    536  );
    537  Assert.equal(submission.postData, null, "Submission URL is still GET.");
    538  Assert.equal(
    539    engine.alias,
    540    "sf",
    541    "Alias of search engine object was updated."
    542  );
    543 
    544  // Clean up.
    545  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    546  await Services.search.removeEngine(engine);
    547 });
    548 
    549 add_task(async function test_icon() {
    550  // Set up favicon.
    551  let pageUrl = "https://search.test/";
    552  let iconUrl = "https://search.test/favicon.svg";
    553  let dataURL = "data:image/svg+xml;base64,PHN2Zy8+";
    554 
    555  await PlacesTestUtils.addVisits({ uri: new URL(pageUrl).URI });
    556  await PlacesTestUtils.setFaviconForPage(pageUrl, iconUrl, dataURL);
    557 
    558  // Open Settings.
    559  await openPreferencesViaOpenPreferencesAPI("search", {
    560    leaveOpen: true,
    561  });
    562 
    563  let doc = gBrowser.contentDocument;
    564  let tree = doc.querySelector("#engineList");
    565  let view = tree.view.wrappedJSObject;
    566 
    567  let addButton = doc.querySelector("#addEngineButton");
    568  let editButton = doc.querySelector("#editEngineButton");
    569 
    570  // Add engine and check favicon.
    571  let dialogWin = await openDialogWith(doc, () => addButton.click());
    572  setName("Bugzilla", dialogWin);
    573  setUrl("https://search.test/search?q=%s", dialogWin);
    574 
    575  let promiseIcon = SearchTestUtils.promiseSearchNotification(
    576    SearchUtils.MODIFIED_TYPE.ICON_CHANGED,
    577    SearchUtils.TOPIC_ENGINE_MODIFIED
    578  );
    579  dialogWin.document.querySelector("dialog").getButton("accept").click();
    580  let engine = await promiseIcon;
    581 
    582  Assert.ok(true, "Icon was added");
    583  Assert.equal(await engine.getIconURL(), dataURL, "Icon is correct");
    584 
    585  // Change favicon.
    586  dataURL = "data:image/svg+xml;base64,PHN2Zz48Y2lyY2xlIHI9IjEiLz48L3N2Zz4=";
    587  await PlacesTestUtils.setFaviconForPage(pageUrl, iconUrl, dataURL);
    588 
    589  // Edit engine and check favicon.
    590  let engines = await Services.search.getEngines();
    591  let i = engines.findIndex(e => e.id == engine.id);
    592  view.selection.select(i);
    593  dialogWin = await openDialogWith(doc, () => editButton.click());
    594 
    595  promiseIcon = SearchTestUtils.promiseSearchNotification(
    596    SearchUtils.MODIFIED_TYPE.ICON_CHANGED,
    597    SearchUtils.TOPIC_ENGINE_MODIFIED
    598  );
    599  dialogWin.document.querySelector("dialog").getButton("accept").click();
    600  await promiseIcon;
    601 
    602  Assert.ok(true, "Icon was changed");
    603  Assert.equal(await engine.getIconURL(), dataURL, "New icon is correct");
    604 
    605  // Clean up.
    606  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    607  await Services.search.removeEngine(engine);
    608  PlacesUtils.favicons.expireAllFavicons();
    609  await PlacesUtils.history.clear();
    610 });
    611 
    612 /**
    613 * Checks the error label of an input of the add engine dialog.
    614 *
    615 * @param {HTMLInputElement} elt
    616 *   The element whose error should be checked
    617 * @param {?string} error
    618 *   The l10n id of the expected error message or null if no error is expected.
    619 */
    620 async function assertError(elt, error = null) {
    621  let errorLabel = elt.parentElement.querySelector(".error-label");
    622 
    623  if (error) {
    624    let msg = await document.l10n.formatValue(error);
    625    Assert.equal(errorLabel.textContent, msg);
    626  } else {
    627    Assert.equal(errorLabel.textContent, "valid");
    628  }
    629 }
    630 
    631 async function openDialogWith(doc, fn) {
    632  info("Opening dialog.");
    633  let dialogLoaded = TestUtils.topicObserved("subdialog-loaded");
    634  await fn();
    635  let [dialogWin] = await dialogLoaded;
    636  await doc.ownerGlobal.gSubDialog.dialogs[0]._dialogReady;
    637  Assert.ok(true, "Engine dialog opened");
    638  return dialogWin;
    639 }
    640 
    641 function setName(value, win) {
    642  fillTextField("engineName", value, win);
    643 }
    644 
    645 function setUrl(value, win) {
    646  fillTextField("engineUrl", value, win);
    647 }
    648 
    649 function setSuggestUrl(value, win) {
    650  fillTextField("suggestUrl", value, win);
    651 }
    652 
    653 function setPostData(value, win) {
    654  fillTextField("enginePostData", value, win);
    655 }
    656 
    657 async function setAlias(value, win) {
    658  fillTextField("engineAlias", value, win);
    659  await TestUtils.waitForTick();
    660 }
    661 
    662 function fillTextField(id, text, win) {
    663  let elt = win.document.getElementById(id);
    664  elt.focus();
    665  elt.select();
    666  EventUtils.synthesizeKey("a", { metaKey: true }, win);
    667  EventUtils.synthesizeKey("KEY_Backspace", {}, win);
    668 
    669  for (let c of text.split("")) {
    670    EventUtils.synthesizeKey(c, {}, win);
    671  }
    672 }
    673 
    674 function decodePostData(postData) {
    675  let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
    676    Ci.nsIBinaryInputStream
    677  );
    678  binaryStream.setInputStream(postData.data);
    679 
    680  return binaryStream
    681    .readBytes(binaryStream.available())
    682    .replace("searchTerms", "%s");
    683 }