diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /toolkit/components/search/tests/xpcshell | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/components/search/tests/xpcshell')
94 files changed, 5257 insertions, 0 deletions
diff --git a/toolkit/components/search/tests/xpcshell/.eslintrc.js b/toolkit/components/search/tests/xpcshell/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/toolkit/components/search/tests/xpcshell/data/chrome.manifest b/toolkit/components/search/tests/xpcshell/data/chrome.manifest new file mode 100644 index 000000000..ec412e050 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/chrome.manifest @@ -0,0 +1,3 @@ +locale testsearchplugin ar jar:jar:searchTest.jar!/chrome/searchTest.jar!/ +content testsearchplugin ./ + diff --git a/toolkit/components/search/tests/xpcshell/data/engine-addon.xml b/toolkit/components/search/tests/xpcshell/data/engine-addon.xml new file mode 100644 index 000000000..24e53d0c1 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine-addon.xml @@ -0,0 +1,8 @@ +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>addon</ShortName> +<Description>addon</Description> +<InputEncoding>UTF-8</InputEncoding> +<Url type="text/html" method="GET" template="http://searchtest.local"> + <Param name="search" value="{searchTerms}"/> +</Url> +</SearchPlugin> diff --git a/toolkit/components/search/tests/xpcshell/data/engine-app.xml b/toolkit/components/search/tests/xpcshell/data/engine-app.xml new file mode 100644 index 000000000..fe1b3a67c --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine-app.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>TestEngineApp</ShortName> +<Description>A test search engine installed in the application directory</Description> +<InputEncoding>ISO-8859-1</InputEncoding> +<Url type="text/html" method="GET" template="http://localhost/" resultdomain="localhost"> + <Param name="q" value="{searchTerms}"/> +</Url> +</SearchPlugin> diff --git a/toolkit/components/search/tests/xpcshell/data/engine-chromeicon.xml b/toolkit/components/search/tests/xpcshell/data/engine-chromeicon.xml new file mode 100644 index 000000000..856732c6d --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine-chromeicon.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>engine-chromeicon</ShortName> +<Image width="16" height="16">chrome://branding/content/icon16.png</Image> +<Image width="32" height="32">chrome://branding/content/icon32.png</Image> +<Url type="text/html" method="GET" template="http://www.google.com/search"> + <Param name="q" value="{searchTerms}"/> +</Url> +</SearchPlugin> diff --git a/toolkit/components/search/tests/xpcshell/data/engine-fr.xml b/toolkit/components/search/tests/xpcshell/data/engine-fr.xml new file mode 100644 index 000000000..fad3e7574 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine-fr.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Test search engine (fr)</ShortName>
+<Description>A test search engine (based on Google search for a different locale)</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Url type="text/html" method="GET" template="http://www.google.fr/search" resultdomain="google.fr">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="ie" value="iso-8859-1"/>
+ <Param name="oe" value="iso-8859-1"/>
+</Url>
+<SearchForm>http://www.google.fr/</SearchForm>
+</SearchPlugin>
diff --git a/toolkit/components/search/tests/xpcshell/data/engine-override.xml b/toolkit/components/search/tests/xpcshell/data/engine-override.xml new file mode 100644 index 000000000..473be82fd --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine-override.xml @@ -0,0 +1,8 @@ +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>bug645970</ShortName> +<Description>override</Description> +<InputEncoding>UTF-8</InputEncoding> +<Url type="text/html" method="GET" template="http://searchtest.local"> + <Param name="search" value="{searchTerms}"/> +</Url> +</SearchPlugin> diff --git a/toolkit/components/search/tests/xpcshell/data/engine-pref.xml b/toolkit/components/search/tests/xpcshell/data/engine-pref.xml new file mode 100644 index 000000000..0555caf3e --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine-pref.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>engine-pref</ShortName> +<Url type="text/html" method="GET" template="http://www.google.com/search"> + <Param name="q" value="{searchTerms}"/> + <!-- Dynamic parameters --> + <MozParam name="code" condition="pref" pref="code"/> +</Url> +</SearchPlugin> diff --git a/toolkit/components/search/tests/xpcshell/data/engine-rel-searchform-post.xml b/toolkit/components/search/tests/xpcshell/data/engine-rel-searchform-post.xml new file mode 100644 index 000000000..8b6eb7cab --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine-rel-searchform-post.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>engine-rel-searchform-post.xml</ShortName> +<Url type="text/html" method="POST" template="http://engine-rel-searchform-post.xml/POST" rel="searchform"/> +<SearchForm>http://engine-rel-searchform-post.xml/?search</SearchForm> +</SearchPlugin> diff --git a/toolkit/components/search/tests/xpcshell/data/engine-rel-searchform-purpose.xml b/toolkit/components/search/tests/xpcshell/data/engine-rel-searchform-purpose.xml new file mode 100644 index 000000000..18026210f --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine-rel-searchform-purpose.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>engine-rel-searchform-purpose</ShortName> +<Url type="text/html" method="GET" template="http://www.google.com/search" resultdomain="google.com" rel="searchform"> + <Param name="q" value="{searchTerms}"/> + <!-- Dynamic parameters --> + <MozParam name="channel" condition="purpose" purpose="contextmenu" value="rcs"/> + <MozParam name="channel" condition="purpose" purpose="keyword" value="fflb"/> + <MozParam name="channel" condition="purpose" purpose="searchbar" value="sb"/> +</Url> +</SearchPlugin> diff --git a/toolkit/components/search/tests/xpcshell/data/engine-rel-searchform.xml b/toolkit/components/search/tests/xpcshell/data/engine-rel-searchform.xml new file mode 100644 index 000000000..bcd164877 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine-rel-searchform.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>engine-rel-searchform.xml</ShortName> +<Url type="text/html" method="GET" template="http://engine-rel-searchform.xml/?search" rel="searchform"/> +</SearchPlugin> diff --git a/toolkit/components/search/tests/xpcshell/data/engine-resourceicon.xml b/toolkit/components/search/tests/xpcshell/data/engine-resourceicon.xml new file mode 100644 index 000000000..6fb2a778d --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine-resourceicon.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>engine-resourceicon</ShortName> +<Image width="16" height="16">resource://search-plugins/icon16.png</Image> +<Image width="32" height="32">resource://search-plugins/icon32.png</Image> +<Url type="text/html" method="GET" template="http://www.google.com/search"> + <Param name="q" value="{searchTerms}"/> +</Url> +</SearchPlugin> diff --git a/toolkit/components/search/tests/xpcshell/data/engine-system-purpose.xml b/toolkit/components/search/tests/xpcshell/data/engine-system-purpose.xml new file mode 100644 index 000000000..57ecd32d7 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine-system-purpose.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>engine-system-purpose</ShortName> +<Url type="text/html" method="GET" template="http://www.google.com/search"> + <Param name="q" value="{searchTerms}"/> + <!-- Dynamic parameters --> + <MozParam name="channel" condition="purpose" purpose="searchbar" value="sb"/> + <MozParam name="channel" condition="purpose" purpose="system" value="sys"/> +</Url> +</SearchPlugin> diff --git a/toolkit/components/search/tests/xpcshell/data/engine-update.xml b/toolkit/components/search/tests/xpcshell/data/engine-update.xml new file mode 100644 index 000000000..b8ef7224d --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine-update.xml @@ -0,0 +1,10 @@ +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> +<ShortName>update</ShortName> +<Description>update</Description> +<InputEncoding>UTF-8</InputEncoding> +<Url type="text/html" method="GET" template="http://searchtest.local"> + <Param name="search" value="{searchTerms}"/> +</Url> +<UpdateUrl>http://searchtest.local/opensearch.xml</UpdateUrl> +<IconUpdateUrl>http://searchtest.local/favicon.ico</IconUpdateUrl> +</SearchPlugin> diff --git a/toolkit/components/search/tests/xpcshell/data/engine.xml b/toolkit/components/search/tests/xpcshell/data/engine.xml new file mode 100644 index 000000000..e7af1d9e9 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Test search engine</ShortName>
+<Description>A test search engine (based on Google search)</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}&q={searchTerms}"/>
+<Url type="text/html" method="GET" template="http://www.google.com/search" resultdomain="google.com">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="ie" value="utf-8"/>
+ <Param name="oe" value="utf-8"/>
+ <Param name="aq" value="t"/>
+ <!-- Dynamic parameters -->
+ <MozParam name="channel" condition="purpose" purpose="contextmenu" value="rcs"/>
+ <MozParam name="channel" condition="purpose" purpose="keyword" value="fflb"/>
+</Url>
+<Url type="application/x-moz-default-purpose" method="GET" template="http://www.google.com/search" resultdomain="purpose.google.com">
+ <Param name="q" value="{searchTerms}"/>
+ <!-- MozParam uses searchbar if purpose is not specified -->
+ <MozParam name="channel" condition="purpose" purpose="searchbar" value="ffsb"/>
+ <MozParam name="channel" condition="purpose" purpose="contextmenu" value="rcs"/>
+ <MozParam name="channel" condition="purpose" purpose="keyword" value="fflb"/>
+</Url>
+<SearchForm>http://www.google.com/</SearchForm>
+</SearchPlugin>
diff --git a/toolkit/components/search/tests/xpcshell/data/engine2.xml b/toolkit/components/search/tests/xpcshell/data/engine2.xml new file mode 100644 index 000000000..9957bfdf4 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engine2.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>A second test engine</ShortName> + <Description>A second test search engine (based on DuckDuckGo)</Description> + <InputEncoding>UTF-8</InputEncoding> + <LongName>A second test search engine (based on DuckDuckGo)</LongName> + <Image width="16" height="16">data:image/vnd.microsoft.icon;base64,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</Image> + <Url type="text/html" method="get" template="https://duckduckgo.com/?q={searchTerms}"/> +</OpenSearchDescription> diff --git a/toolkit/components/search/tests/xpcshell/data/engineImages.xml b/toolkit/components/search/tests/xpcshell/data/engineImages.xml new file mode 100644 index 000000000..65b550b31 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engineImages.xml @@ -0,0 +1,22 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> + <ShortName>IconsTest</ShortName> + <Description>IconsTest. Search by Test.</Description> + <InputEncoding>UTF-8</InputEncoding> + <Image width="16" height="16">data:image/x-icon;base64,ico16</Image> + <Image width="32" height="32">data:image/x-icon;base64,ico32</Image> + <Image width="74" height="74">data:image/png;base64,ico74</Image> + <Url type="application/x-suggestions+json" template="http://api.bing.com/osjson.aspx"> + <Param name="query" value="{searchTerms}"/> + <Param name="form" value="MOZW"/> + </Url> + <Url type="text/html" method="GET" template="http://www.bing.com/search"> + <Param name="q" value="{searchTerms}"/> + <MozParam name="pc" condition="pref" pref="ms-pc"/> + <Param name="form" value="MOZW"/> + </Url> + <SearchForm>http://www.bing.com/search</SearchForm> +</SearchPlugin> diff --git a/toolkit/components/search/tests/xpcshell/data/engineMaker.sjs b/toolkit/components/search/tests/xpcshell/data/engineMaker.sjs new file mode 100644 index 000000000..4c432e7ee --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/engineMaker.sjs @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +/** + * Dynamically create a search engine offering search suggestions via searchSuggestions.sjs. + * + * The engine is constructed by passing a JSON object with engine datails as the query string. + */ + +function handleRequest(request, response) { + let engineData = JSON.parse(unescape(request.queryString).replace("+", " ")); + + if (!engineData.baseURL) { + response.setStatusLine(request.httpVersion, 500, "baseURL required"); + return; + } + + engineData.name = engineData.name || "Generated test engine"; + engineData.description = engineData.description || "Generated test engine description"; + engineData.method = engineData.method || "GET"; + + response.setStatusLine(request.httpVersion, 200, "OK"); + createOpenSearchEngine(response, engineData); +} + +/** + * Create an OpenSearch engine for the given base URL. + */ +function createOpenSearchEngine(response, engineData) { + let params = "", queryString = ""; + if (engineData.method == "POST") { + params = "<Param name='q' value='{searchTerms}'/>"; + } else { + queryString = "?q={searchTerms}"; + } + + let result = "<?xml version='1.0' encoding='utf-8'?>\ +<OpenSearchDescription xmlns='http://a9.com/-/spec/opensearch/1.1/'>\ + <ShortName>" + engineData.name + "</ShortName>\ + <Description>" + engineData.description + "</Description>\ + <InputEncoding>UTF-8</InputEncoding>\ + <LongName>" + engineData.name + "</LongName>\ + <Url type='application/x-suggestions+json' method='" + engineData.method + "'\ + template='" + engineData.baseURL + "searchSuggestions.sjs" + queryString + "'>\ + " + params + "\ + </Url>\ + <Url type='text/html' method='" + engineData.method + "'\ + template='" + engineData.baseURL + queryString + "'/>\ +</OpenSearchDescription>\ +"; + response.write(result); +} diff --git a/toolkit/components/search/tests/xpcshell/data/ico-size-16x16-png.ico b/toolkit/components/search/tests/xpcshell/data/ico-size-16x16-png.ico Binary files differnew file mode 100644 index 000000000..442ab4dc8 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/ico-size-16x16-png.ico diff --git a/toolkit/components/search/tests/xpcshell/data/install.rdf b/toolkit/components/search/tests/xpcshell/data/install.rdf new file mode 100644 index 000000000..df361ade4 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/install.rdf @@ -0,0 +1,23 @@ +<?xml version="1.0"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:install-manifest"> + <em:id>search-engine@tests.mozilla.org</em:id> + <em:unpack>true</em:unpack> + <em:version>1.0</em:version> + + <em:targetApplication> + <Description> + <em:id>toolkit@mozilla.org</em:id> + <em:minVersion>0</em:minVersion> + <em:maxVersion>*</em:maxVersion> + </Description> + </em:targetApplication> + + <!-- Front End MetaData --> + <em:name>Search Engine</em:name> + + </Description> +</RDF> diff --git a/toolkit/components/search/tests/xpcshell/data/invalid-engine.xml b/toolkit/components/search/tests/xpcshell/data/invalid-engine.xml new file mode 100644 index 000000000..e8efce672 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/invalid-engine.xml @@ -0,0 +1 @@ +# An invalid xml engine file. diff --git a/toolkit/components/search/tests/xpcshell/data/langpack-metadata.json b/toolkit/components/search/tests/xpcshell/data/langpack-metadata.json new file mode 100644 index 000000000..e1ff95bc0 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/langpack-metadata.json @@ -0,0 +1,5 @@ +{ + "[app]/bug645970.xml": { + "alias": "lp" + } +} diff --git a/toolkit/components/search/tests/xpcshell/data/list.json b/toolkit/components/search/tests/xpcshell/data/list.json new file mode 100644 index 000000000..68163bb88 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/list.json @@ -0,0 +1,7 @@ +{ + "default": { + "visibleDefaultEngines": [ + "engine", "engine-pref", "engine-rel-searchform-purpose", "engine-system-purpose", "engine-chromeicon", "engine-resourceicon" + ] + } +} diff --git a/toolkit/components/search/tests/xpcshell/data/metadata.json b/toolkit/components/search/tests/xpcshell/data/metadata.json new file mode 100644 index 000000000..77b003d4e --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/metadata.json @@ -0,0 +1,30 @@ +{ + "[global]": { + "searchdefaultexpir": 1471013469846 + }, + "[profile]\/engine.xml": { + "order": 1, + "alias": "foo" + }, + "[app]\/google.xml": { + "order": 2 + }, + "[app]\/yahoo.xml": { + "order": 3 + }, + "[app]\/bing.xml": { + "order": 4 + }, + "[app]\/amazondotcom.xml": { + "order": 5 + }, + "[app]\/ddg.xml": { + "order": 6 + }, + "[app]\/twitter.xml": { + "order": 7 + }, + "[app]\/wikipedia.xml": { + "order": 8 + } +} diff --git a/toolkit/components/search/tests/xpcshell/data/search.json b/toolkit/components/search/tests/xpcshell/data/search.json new file mode 100644 index 000000000..f4f907778 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/search.json @@ -0,0 +1,86 @@ +{ + "version": 1, + "buildID": "20121106", + "locale": "en-US", + "metaData": {}, + "engines": [ + { + "_name": "Test search engine", + "_shortName": "test-search-engine", + "description": "A test search engine (based on Google search)", + "extensionID": "test-addon-id@mozilla.org", + "__searchForm": "http://www.google.com/", + "_iconURL": "data:image/png;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA", + "_metaData": {}, + "_urls": [ + { + "template": "http://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}&q={searchTerms}", + "rels": [ + ], + "type": "application/x-suggestions+json", + "params": [ + ] + }, + { + "template": "http://www.google.com/search", + "resultDomain": "google.com", + "rels": [ + ], + "params": [ + { + "name": "q", + "value": "{searchTerms}" + }, + { + "name": "ie", + "value": "utf-8" + }, + { + "name": "oe", + "value": "utf-8" + }, + { + "name": "aq", + "value": "t" + }, + { + "name": "channel", + "value": "fflb", + "purpose": "keyword" + }, + { + "name": "channel", + "value": "rcs", + "purpose": "contextmenu" + } + ] + }, + { + "template": "http://www.google.com/search", + "resultDomain": "purpose.google.com", + "rels": [ + ], + "type": "application/x-moz-default-purpose", + "params": [ + { + "name": "q", + "value": "{searchTerms}" + }, + { + "name": "channel", + "value": "fflb", + "purpose": "keyword" + }, + { + "name": "channel", + "value": "rcs", + "purpose": "contextmenu" + } + ] + } + ], + "queryCharset": "UTF-8", + "_readOnly": false + } + ] +} diff --git a/toolkit/components/search/tests/xpcshell/data/search.sqlite b/toolkit/components/search/tests/xpcshell/data/search.sqlite Binary files differnew file mode 100644 index 000000000..983bb831a --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/search.sqlite diff --git a/toolkit/components/search/tests/xpcshell/data/searchSuggestions.sjs b/toolkit/components/search/tests/xpcshell/data/searchSuggestions.sjs new file mode 100644 index 000000000..abd94428e --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/searchSuggestions.sjs @@ -0,0 +1,78 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +Cu.import("resource://gre/modules/Timer.jsm"); +Cu.import("resource://gre/modules/NetUtil.jsm"); + +/** + * Provide search suggestions in the OpenSearch JSON format. + */ + +function handleRequest(request, response) { + // Get the query parameters from the query string. + let query = parseQueryString(request.queryString); + + function writeSuggestions(query, completions = []) { + let result = [query, completions]; + response.write(JSON.stringify(result)); + return result; + } + + response.setStatusLine(request.httpVersion, 200, "OK"); + + let q = request.method == "GET" ? query.q : undefined; + if (q == "no remote" || q == "no results") { + writeSuggestions(q); + } else if (q == "Query Mismatch") { + writeSuggestions("This is an incorrect query string", ["some result"]); + } else if (q == "Query Case Mismatch") { + writeSuggestions(q.toUpperCase(), [q]); + } else if (q == "") { + writeSuggestions("", ["The server should never be sent an empty query"]); + } else if (q && q.startsWith("mo")) { + writeSuggestions(q, ["Mozilla", "modern", "mom"]); + } else if (q && q.startsWith("I ❤️")) { + writeSuggestions(q, ["I ❤️ Mozilla"]); + } else if (q && q.startsWith("letter ")) { + let letters = []; + for (let charCode = "A".charCodeAt(); charCode <= "Z".charCodeAt(); charCode++) { + letters.push("letter " + String.fromCharCode(charCode)); + } + writeSuggestions(q, letters); + } else if (q && q.startsWith("HTTP ")) { + response.setStatusLine(request.httpVersion, q.replace("HTTP ", ""), q); + writeSuggestions(q, [q]); + } else if (q && q.startsWith("delay")) { + // Delay the response by 200 milliseconds (less than the timeout but hopefully enough to abort + // before completion). + response.processAsync(); + writeSuggestions(q, [q]); + setTimeout(() => response.finish(), 200); + } else if (q && q.startsWith("slow ")) { + // Delay the response by 10 seconds so the client timeout is reached. + response.processAsync(); + writeSuggestions(q, [q]); + setTimeout(() => response.finish(), 10000); + } else if (request.method == "POST") { + // This includes headers, not just the body + let requestText = NetUtil.readInputStreamToString(request.bodyInputStream, + request.bodyInputStream.available()); + // Only use the last line which contains the encoded params + let requestLines = requestText.split("\n"); + let postParams = parseQueryString(requestLines[requestLines.length - 1]); + writeSuggestions(postParams.q, ["Mozilla", "modern", "mom"]); + } else { + response.setStatusLine(request.httpVersion, 404, "Not Found"); + } +} + +function parseQueryString(queryString) { + let query = {}; + queryString.split('&').forEach(function (val) { + let [name, value] = val.split('='); + query[name] = unescape(value).replace(/[+]/g, " "); + }); + return query; +} diff --git a/toolkit/components/search/tests/xpcshell/data/searchTest.jar b/toolkit/components/search/tests/xpcshell/data/searchTest.jar Binary files differnew file mode 100644 index 000000000..8bfbe6f21 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/data/searchTest.jar diff --git a/toolkit/components/search/tests/xpcshell/head_search.js b/toolkit/components/search/tests/xpcshell/head_search.js new file mode 100644 index 000000000..2f40d84f8 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/head_search.js @@ -0,0 +1,544 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ + +var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +Cu.import("resource://gre/modules/FileUtils.jsm"); +Cu.import("resource://gre/modules/osfile.jsm"); +Cu.import("resource://gre/modules/NetUtil.jsm"); +Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://testing-common/AppInfo.jsm"); +Cu.import("resource://testing-common/httpd.js"); + +const BROWSER_SEARCH_PREF = "browser.search."; +const NS_APP_SEARCH_DIR = "SrchPlugns"; + +const MODE_RDONLY = FileUtils.MODE_RDONLY; +const MODE_WRONLY = FileUtils.MODE_WRONLY; +const MODE_CREATE = FileUtils.MODE_CREATE; +const MODE_TRUNCATE = FileUtils.MODE_TRUNCATE; + +const CACHE_FILENAME = "search.json.mozlz4"; + +// nsSearchService.js uses Services.appinfo.name to build a salt for a hash. +var XULRuntime = Components.classesByID["{95d89e3e-a169-41a3-8e56-719978e15b12}"] + .getService(Ci.nsIXULRuntime); + +var isChild = XULRuntime.processType == XULRuntime.PROCESS_TYPE_CONTENT; + +updateAppInfo({ + name: "XPCShell", + ID: "xpcshell@test.mozilla.org", + version: "5", + platformVersion: "1.9", + // mirror OS from the base impl as some of the "location" tests rely on it + OS: XULRuntime.OS, + // mirror processType from the base implementation + extraProps: { + processType: XULRuntime.processType, + }, +}); + +var gProfD; +if (!isChild) { + // Need to create and register a profile folder. + gProfD = do_get_profile(); +} + +function dumpn(text) +{ + dump("search test: " + text + "\n"); +} + +/** + * Configure preferences to load engines from + * chrome://testsearchplugin/locale/searchplugins/ + */ +function configureToLoadJarEngines() +{ + let url = "chrome://testsearchplugin/locale/searchplugins/"; + let resProt = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + resProt.setSubstitution("search-plugins", + Services.io.newURI(url, null, null)); + + // Ensure a test engine exists in the app dir anyway. + let dir = Services.dirsvc.get(NS_APP_SEARCH_DIR, Ci.nsIFile); + if (!dir.exists()) + dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + do_get_file("data/engine-app.xml").copyTo(dir, "app.xml"); +} + +/** + * Fake the installation of an add-on in the profile, by creating the + * directory and registering it with the directory service. + */ +function installAddonEngine(name = "engine-addon") +{ + const XRE_EXTENSIONS_DIR_LIST = "XREExtDL"; + const profD = do_get_profile().QueryInterface(Ci.nsILocalFile); + + let dir = profD.clone(); + dir.append("extensions"); + if (!dir.exists()) + dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + dir.append("search-engine@tests.mozilla.org"); + dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + do_get_file("data/install.rdf").copyTo(dir, "install.rdf"); + let addonDir = dir.clone(); + dir.append("searchplugins"); + dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + do_get_file("data/" + name + ".xml").copyTo(dir, "bug645970.xml"); + + Services.dirsvc.registerProvider({ + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider, + Ci.nsIDirectoryServiceProvider2]), + + getFile: function (prop, persistant) { + throw Cr.NS_ERROR_FAILURE; + }, + + getFiles: function (prop) { + let result = []; + + switch (prop) { + case XRE_EXTENSIONS_DIR_LIST: + result.push(addonDir); + break; + default: + throw Cr.NS_ERROR_FAILURE; + } + + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]), + hasMoreElements: () => result.length > 0, + getNext: () => result.shift() + }; + } + }); +} + +/** + * Copy the engine-distribution.xml engine to a fake distribution + * created in the profile, and registered with the directory service. + */ +function installDistributionEngine() +{ + const XRE_APP_DISTRIBUTION_DIR = "XREAppDist"; + + const profD = do_get_profile().QueryInterface(Ci.nsILocalFile); + + let dir = profD.clone(); + dir.append("distribution"); + dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + let distDir = dir.clone(); + + dir.append("searchplugins"); + dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + dir.append("common"); + dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + do_get_file("data/engine-override.xml").copyTo(dir, "bug645970.xml"); + + Services.dirsvc.registerProvider({ + getFile: function(aProp, aPersistent) { + aPersistent.value = true; + if (aProp == XRE_APP_DISTRIBUTION_DIR) + return distDir.clone(); + return null; + } + }); +} + +/** + * Clean the profile of any metadata files left from a previous run. + */ +function removeMetadata() +{ + let file = gProfD.clone(); + file.append("search-metadata.json"); + if (file.exists()) { + file.remove(false); + } + + file = gProfD.clone(); + file.append("search.sqlite"); + if (file.exists()) { + file.remove(false); + } +} + +function promiseCacheData() { + return new Promise(resolve => Task.spawn(function* () { + let path = OS.Path.join(OS.Constants.Path.profileDir, CACHE_FILENAME); + let bytes = yield OS.File.read(path, {compression: "lz4"}); + resolve(JSON.parse(new TextDecoder().decode(bytes))); + })); +} + +function promiseSaveCacheData(data) { + return OS.File.writeAtomic(OS.Path.join(OS.Constants.Path.profileDir, CACHE_FILENAME), + new TextEncoder().encode(JSON.stringify(data)), + {compression: "lz4"}); +} + +function promiseEngineMetadata() { + return new Promise(resolve => Task.spawn(function* () { + let cache = yield promiseCacheData(); + let data = {}; + for (let engine of cache.engines) { + data[engine._shortName] = engine._metaData; + } + resolve(data); + })); +} + +function promiseGlobalMetadata() { + return new Promise(resolve => Task.spawn(function* () { + let cache = yield promiseCacheData(); + resolve(cache.metaData); + })); +} + +function promiseSaveGlobalMetadata(globalData) { + return new Promise(resolve => Task.spawn(function* () { + let data = yield promiseCacheData(); + data.metaData = globalData; + yield promiseSaveCacheData(data); + resolve(); + })); +} + +var forceExpiration = Task.async(function* () { + let metadata = yield promiseGlobalMetadata(); + + // Make the current geodefaults expire 1s ago. + metadata.searchDefaultExpir = Date.now() - 1000; + yield promiseSaveGlobalMetadata(metadata); +}); + +/** + * Clean the profile of any cache file left from a previous run. + * Returns a boolean indicating if the cache file existed. + */ +function removeCacheFile() +{ + let file = gProfD.clone(); + file.append(CACHE_FILENAME); + if (file.exists()) { + file.remove(false); + return true; + } + return false; +} + +/** + * isUSTimezone taken from nsSearchService.js + */ +function isUSTimezone() { + // Timezone assumptions! We assume that if the system clock's timezone is + // between Newfoundland and Hawaii, that the user is in North America. + + // This includes all of South America as well, but we have relatively few + // en-US users there, so that's OK. + + // 150 minutes = 2.5 hours (UTC-2.5), which is + // Newfoundland Daylight Time (http://www.timeanddate.com/time/zones/ndt) + + // 600 minutes = 10 hours (UTC-10), which is + // Hawaii-Aleutian Standard Time (http://www.timeanddate.com/time/zones/hast) + + let UTCOffset = (new Date()).getTimezoneOffset(); + return UTCOffset >= 150 && UTCOffset <= 600; +} + +const kDefaultenginenamePref = "browser.search.defaultenginename"; +const kTestEngineName = "Test search engine"; +const kLocalePref = "general.useragent.locale"; + +function getDefaultEngineName(isUS) { + const nsIPLS = Ci.nsIPrefLocalizedString; + // Copy the logic from nsSearchService + let pref = kDefaultenginenamePref; + if (isUS === undefined) + isUS = Services.prefs.getCharPref(kLocalePref) == "en-US" && isUSTimezone(); + if (isUS) { + pref += ".US"; + } + return Services.prefs.getComplexValue(pref, nsIPLS).data; +} + +/** + * Waits for the cache file to be saved. + * @return {Promise} Resolved when the cache file is saved. + */ +function promiseAfterCache() { + return waitForSearchNotification("write-cache-to-disk-complete"); +} + +function parseJsonFromStream(aInputStream) { + const json = Cc["@mozilla.org/dom/json;1"].createInstance(Components.interfaces.nsIJSON); + const data = json.decodeFromStream(aInputStream, aInputStream.available()); + return data; +} + +/** + * Read a JSON file and return the JS object + */ +function readJSONFile(aFile) { + let stream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + try { + stream.init(aFile, MODE_RDONLY, FileUtils.PERMS_FILE, 0); + return parseJsonFromStream(stream, stream.available()); + } catch (ex) { + dumpn("readJSONFile: Error reading JSON file: " + ex); + } finally { + stream.close(); + } + return false; +} + +/** + * Recursively compare two objects and check that every property of expectedObj has the same value + * on actualObj. + */ +function isSubObjectOf(expectedObj, actualObj) { + for (let prop in expectedObj) { + if (expectedObj[prop] instanceof Object) { + do_check_eq(expectedObj[prop].length, actualObj[prop].length); + isSubObjectOf(expectedObj[prop], actualObj[prop]); + } else { + if (expectedObj[prop] != actualObj[prop]) + do_print("comparing property " + prop); + do_check_eq(expectedObj[prop], actualObj[prop]); + } + } +} + +// Can't set prefs if we're running in a child process, but the search service +// doesn't run in child processes anyways. +if (!isChild) { + // Expand the amount of information available in error logs + Services.prefs.setBoolPref("browser.search.log", true); + + // The geo-specific search tests assume certain prefs are already setup, which + // might not be true when run in comm-central etc. So create them here. + Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", true); + Services.prefs.setIntPref("browser.search.geoip.timeout", 3000); + // But still disable geoip lookups - tests that need it will re-configure this. + Services.prefs.setCharPref("browser.search.geoip.url", ""); + // Also disable region defaults - tests using it will also re-configure it. + Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref("geoSpecificDefaults.url", ""); +} + +/** + * After useHttpServer() is called, this string contains the URL of the "data" + * directory, including the final slash. + */ +var gDataUrl; + +/** + * Initializes the HTTP server and ensures that it is terminated when tests end. + * + * @return The HttpServer object in case further customization is needed. + */ +function useHttpServer() { + let httpServer = new HttpServer(); + httpServer.start(-1); + httpServer.registerDirectory("/", do_get_cwd()); + gDataUrl = "http://localhost:" + httpServer.identity.primaryPort + "/data/"; + do_register_cleanup(() => httpServer.stop(() => {})); + return httpServer; +} + +/** + * Adds test engines and returns a promise resolved when they are installed. + * + * The engines are added in the given order. + * + * @param aItems + * Array of objects with the following properties: + * { + * name: Engine name, used to wait for it to be loaded. + * xmlFileName: Name of the XML file in the "data" folder. + * details: Array containing the parameters of addEngineWithDetails, + * except for the engine name. Alternative to xmlFileName. + * } + */ +var addTestEngines = Task.async(function* (aItems) { + if (!gDataUrl) { + do_throw("useHttpServer must be called before addTestEngines."); + } + + let engines = []; + + for (let item of aItems) { + do_print("Adding engine: " + item.name); + yield new Promise((resolve, reject) => { + Services.obs.addObserver(function obs(subject, topic, data) { + try { + let engine = subject.QueryInterface(Ci.nsISearchEngine); + do_print("Observed " + data + " for " + engine.name); + if (data != "engine-added" || engine.name != item.name) { + return; + } + + Services.obs.removeObserver(obs, "browser-search-engine-modified"); + engines.push(engine); + resolve(); + } catch (ex) { + reject(ex); + } + }, "browser-search-engine-modified", false); + + if (item.xmlFileName) { + Services.search.addEngine(gDataUrl + item.xmlFileName, + null, null, false); + } else { + Services.search.addEngineWithDetails(item.name, ...item.details); + } + }); + } + + return engines; +}); + +/** + * Installs a test engine into the test profile. + */ +function installTestEngine() { + removeMetadata(); + removeCacheFile(); + + do_check_false(Services.search.isInitialized); + + let engineDummyFile = gProfD.clone(); + engineDummyFile.append("searchplugins"); + engineDummyFile.append("test-search-engine.xml"); + let engineDir = engineDummyFile.parent; + engineDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + do_get_file("data/engine.xml").copyTo(engineDir, "engine.xml"); + + do_register_cleanup(function() { + removeMetadata(); + removeCacheFile(); + }); +} + +/** + * Set a localized preference on the default branch + * @param aPrefName + * The name of the pref to set. + */ +function setLocalizedDefaultPref(aPrefName, aValue) { + let value = "data:text/plain," + BROWSER_SEARCH_PREF + aPrefName + "=" + aValue; + Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF) + .setCharPref(aPrefName, value); +} + + +/** + * Installs two test engines, sets them as default for US vs. general. + */ +function setUpGeoDefaults() { + removeMetadata(); + removeCacheFile(); + + do_check_false(Services.search.isInitialized); + + let engineDummyFile = gProfD.clone(); + engineDummyFile.append("searchplugins"); + engineDummyFile.append("test-search-engine.xml"); + let engineDir = engineDummyFile.parent; + engineDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + do_get_file("data/engine.xml").copyTo(engineDir, "engine.xml"); + + engineDummyFile = gProfD.clone(); + engineDummyFile.append("searchplugins"); + engineDummyFile.append("test-search-engine2.xml"); + + do_get_file("data/engine2.xml").copyTo(engineDir, "engine2.xml"); + + setLocalizedDefaultPref("defaultenginename", "Test search engine"); + setLocalizedDefaultPref("defaultenginename.US", "A second test engine"); + + do_register_cleanup(function() { + removeMetadata(); + removeCacheFile(); + }); +} + +/** + * Returns a promise that is resolved when an observer notification from the + * search service fires with the specified data. + * + * @param aExpectedData + * The value the observer notification sends that causes us to resolve + * the promise. + */ +function waitForSearchNotification(aExpectedData) { + return new Promise(resolve => { + const SEARCH_SERVICE_TOPIC = "browser-search-service"; + Services.obs.addObserver(function observer(aSubject, aTopic, aData) { + if (aData != aExpectedData) + return; + + Services.obs.removeObserver(observer, SEARCH_SERVICE_TOPIC); + resolve(aSubject); + }, SEARCH_SERVICE_TOPIC, false); + }); +} + +function asyncInit() { + return new Promise(resolve => { + Services.search.init(function() { + do_check_true(Services.search.isInitialized); + resolve(); + }); + }); +} + +function asyncReInit() { + let promise = waitForSearchNotification("reinit-complete"); + + Services.search.QueryInterface(Ci.nsIObserver) + .observe(null, "nsPref:changed", kLocalePref); + + return promise; +} + +// This "enum" from nsSearchService.js +const TELEMETRY_RESULT_ENUM = { + SUCCESS: 0, + SUCCESS_WITHOUT_DATA: 1, + XHRTIMEOUT: 2, + ERROR: 3, +}; + +/** + * Checks the value of the SEARCH_SERVICE_COUNTRY_FETCH_RESULT probe. + * + * @param aExpectedValue + * If a value from TELEMETRY_RESULT_ENUM, we expect to see this value + * recorded exactly once in the probe. If |null|, we expect to see + * nothing recorded in the probe at all. + */ +function checkCountryResultTelemetry(aExpectedValue) { + let histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_RESULT"); + let snapshot = histogram.snapshot(); + // The probe is declared with 8 values, but we get 9 back from .counts + let expectedCounts = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + if (aExpectedValue != null) { + expectedCounts[aExpectedValue] = 1; + } + deepEqual(snapshot.counts, expectedCounts); +} diff --git a/toolkit/components/search/tests/xpcshell/test_645970.js b/toolkit/components/search/tests/xpcshell/test_645970.js new file mode 100644 index 000000000..3204e03d9 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_645970.js @@ -0,0 +1,22 @@ +/* -*- 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/. */ + +/** + * Test nsSearchService with nested jar: uris, without async initialization + */ +function run_test() { + updateAppInfo(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + + // The search service needs to be started after the jarURIs pref has been + // set in order to initiate it correctly + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + Services.obs.notifyObservers(null, "quit-application", null); +} diff --git a/toolkit/components/search/tests/xpcshell/test_SearchStaticData.js b/toolkit/components/search/tests/xpcshell/test_SearchStaticData.js new file mode 100644 index 000000000..4e50ed2a9 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_SearchStaticData.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Tests the SearchStaticData module. + */ + +"use strict"; + +Cu.import("resource://gre/modules/SearchStaticData.jsm", this); + +function run_test() { + do_check_true(SearchStaticData.getAlternateDomains("www.google.com") + .indexOf("www.google.fr") != -1); + do_check_true(SearchStaticData.getAlternateDomains("www.google.fr") + .indexOf("www.google.com") != -1); + do_check_true(SearchStaticData.getAlternateDomains("www.google.com") + .every(d => d.startsWith("www.google."))); + do_check_true(SearchStaticData.getAlternateDomains("google.com").length == 0); + + // Test that methods from SearchStaticData module can be overwritten, + // needed for hotfixing. + let backup = SearchStaticData.getAlternateDomains; + SearchStaticData.getAlternateDomains = () => ["www.bing.fr"]; + do_check_matches(SearchStaticData.getAlternateDomains("www.bing.com"), ["www.bing.fr"]); + SearchStaticData.getAlternateDomains = backup; +} diff --git a/toolkit/components/search/tests/xpcshell/test_addEngineWithDetails.js b/toolkit/components/search/tests/xpcshell/test_addEngineWithDetails.js new file mode 100644 index 000000000..14411eaaa --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_addEngineWithDetails.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const kSearchEngineID = "addEngineWithDetails_test_engine"; +const kSearchEngineURL = "http://example.com/?search={searchTerms}"; +const kSearchTerm = "foo"; + +add_task(function* test_addEngineWithDetails() { + do_check_false(Services.search.isInitialized); + + Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF) + .setBoolPref("reset.enabled", true); + + yield asyncInit(); + + Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get", + kSearchEngineURL); + + // An engine added with addEngineWithDetails should have a load path, even + // though we can't point to a specific file. + let engine = Services.search.getEngineByName(kSearchEngineID); + do_check_eq(engine.wrappedJSObject._loadPath, "[other]addEngineWithDetails"); + + // Set the engine as default; this should set a loadPath verification hash, + // which should ensure we don't show the search reset prompt. + Services.search.currentEngine = engine; + + let expectedURL = kSearchEngineURL.replace("{searchTerms}", kSearchTerm); + let submission = + Services.search.currentEngine.getSubmission(kSearchTerm, null, "searchbar"); + do_check_eq(submission.uri.spec, expectedURL); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_addEngine_callback.js b/toolkit/components/search/tests/xpcshell/test_addEngine_callback.js new file mode 100644 index 000000000..07eaf38bb --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_addEngine_callback.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Tests covering nsIBrowserSearchService::addEngine's optional callback. + */ + +Components.utils.import("resource://testing-common/MockRegistrar.jsm"); + +"use strict"; + +// Only need to stub the methods actually called by nsSearchService +var promptService = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptService]), + confirmEx: function() {} +}; +var prompt = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]), + alert: function() {} +}; +// Override the prompt service and nsIPrompt, since the search service currently +// prompts in response to certain installation failures we test here +// XXX this should disappear once bug 863474 is fixed +MockRegistrar.register("@mozilla.org/embedcomp/prompt-service;1", promptService); +MockRegistrar.register("@mozilla.org/prompter;1", prompt); + + +// First test inits the search service +add_test(function init_search_service() { + Services.search.init(function (status) { + if (!Components.isSuccessCode(status)) + do_throw("Failed to initialize search service"); + + run_next_test(); + }); +}); + +// Simple test of the search callback +add_test(function simple_callback_test() { + let searchCallback = { + onSuccess: function (engine) { + do_check_true(!!engine); + do_check_neq(engine.name, Services.search.defaultEngine.name); + do_check_eq(engine.wrappedJSObject._loadPath, + "[http]localhost/test-search-engine.xml"); + run_next_test(); + }, + onError: function (errorCode) { + do_throw("search callback returned error: " + errorCode); + } + } + Services.search.addEngine(gDataUrl + "engine.xml", null, + null, false, searchCallback); +}); + +// Test of the search callback on duplicate engine failures +add_test(function duplicate_failure_test() { + let searchCallback = { + onSuccess: function (engine) { + do_throw("this addition should not have succeeded"); + }, + onError: function (errorCode) { + do_check_true(!!errorCode); + do_check_eq(errorCode, Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE); + run_next_test(); + } + } + // Re-add the same engine added in the previous test + Services.search.addEngine(gDataUrl + "engine.xml", null, + null, false, searchCallback); +}); + +// Test of the search callback on failure to load the engine failures +add_test(function load_failure_test() { + let searchCallback = { + onSuccess: function (engine) { + do_throw("this addition should not have succeeded"); + }, + onError: function (errorCode) { + do_check_true(!!errorCode); + do_check_eq(errorCode, Ci.nsISearchInstallCallback.ERROR_UNKNOWN_FAILURE); + run_next_test(); + } + } + // Try adding an engine that doesn't exist + Services.search.addEngine("http://invalid/data/engine.xml", null, + null, false, searchCallback); +}); + +function run_test() { + updateAppInfo(); + useHttpServer(); + + run_next_test(); +} diff --git a/toolkit/components/search/tests/xpcshell/test_async.js b/toolkit/components/search/tests/xpcshell/test_async.js new file mode 100644 index 000000000..58b530464 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_async.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + do_test_pending(); + + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + + do_check_false(Services.search.isInitialized); + + Services.search.init(function search_initialized(aStatus) { + do_check_true(Components.isSuccessCode(aStatus)); + do_check_true(Services.search.isInitialized); + + // test engines from dir are not loaded. + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + // test jar engine is loaded ok. + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + // Check the hidden engine is not loaded. + engine = Services.search.getEngineByName("hidden"); + do_check_eq(engine, null); + + do_test_finished(); + }); +} diff --git a/toolkit/components/search/tests/xpcshell/test_async_addon.js b/toolkit/components/search/tests/xpcshell/test_async_addon.js new file mode 100644 index 000000000..af488f301 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_async_addon.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + do_test_pending(); + + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + installAddonEngine(); + + do_check_false(Services.search.isInitialized); + + Services.search.init(function search_initialized(aStatus) { + do_check_true(Components.isSuccessCode(aStatus)); + do_check_true(Services.search.isInitialized); + + // test the add-on engine is loaded in addition to our jar engine + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 2); + + // test jar engine is loaded ok. + let engine = Services.search.getEngineByName("addon"); + do_check_neq(engine, null); + + do_check_eq(engine.description, "addon"); + + do_test_finished(); + }); +} diff --git a/toolkit/components/search/tests/xpcshell/test_async_addon_no_override.js b/toolkit/components/search/tests/xpcshell/test_async_addon_no_override.js new file mode 100644 index 000000000..5c48c108a --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_async_addon_no_override.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + do_test_pending(); + + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + installAddonEngine("engine-override"); + + do_check_false(Services.search.isInitialized); + + Services.search.init(function search_initialized(aStatus) { + do_check_true(Components.isSuccessCode(aStatus)); + do_check_true(Services.search.isInitialized); + + // test the add-on engine isn't overriding our jar engine + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + // test jar engine is loaded ok. + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + do_check_eq(engine.description, "bug645970"); + + do_test_finished(); + }); +} diff --git a/toolkit/components/search/tests/xpcshell/test_async_distribution.js b/toolkit/components/search/tests/xpcshell/test_async_distribution.js new file mode 100644 index 000000000..4f3af0419 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_async_distribution.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + do_test_pending(); + + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + installDistributionEngine(); + + do_check_false(Services.search.isInitialized); + + Services.search.init(function search_initialized(aStatus) { + do_check_true(Components.isSuccessCode(aStatus)); + do_check_true(Services.search.isInitialized); + + // test that the engine from the distribution overrides our jar engine + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + // check the engine we have is actually the one from the distribution + do_check_eq(engine.description, "override"); + + do_test_finished(); + }); +} diff --git a/toolkit/components/search/tests/xpcshell/test_async_migration.js b/toolkit/components/search/tests/xpcshell/test_async_migration.js new file mode 100644 index 000000000..4d0335c45 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_async_migration.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Test that legacy metadata from search-metadata.json is correctly + * transferred to the new metadata storage. */ + +function run_test() { + updateAppInfo(); + installTestEngine(); + + do_get_file("data/metadata.json").copyTo(gProfD, "search-metadata.json"); + + run_next_test(); +} + +add_task(function* test_async_metadata_migration() { + yield asyncInit(); + yield promiseAfterCache(); + + // Check that the entries are placed as specified correctly + let metadata = yield promiseEngineMetadata(); + do_check_eq(metadata["engine"].order, 1); + do_check_eq(metadata["engine"].alias, "foo"); + + metadata = yield promiseGlobalMetadata(); + do_check_eq(metadata["searchDefaultExpir"], 1471013469846); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_async_profile_engine.js b/toolkit/components/search/tests/xpcshell/test_async_profile_engine.js new file mode 100644 index 000000000..cbcdbdcb0 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_async_profile_engine.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const NS_APP_USER_SEARCH_DIR = "UsrSrchPlugns"; + +function run_test() { + do_test_pending(); + + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + + // Copy an engine in [profile]/searchplugin/ and ensure it's not + // overriding the same file from a jar. + // The description in the file we are copying is 'profile'. + let dir = Services.dirsvc.get(NS_APP_USER_SEARCH_DIR, Ci.nsIFile); + if (!dir.exists()) + dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + do_get_file("data/engine-override.xml").copyTo(dir, "bug645970.xml"); + + do_check_false(Services.search.isInitialized); + + Services.search.init(function search_initialized(aStatus) { + do_check_true(Components.isSuccessCode(aStatus)); + do_check_true(Services.search.isInitialized); + + // test engines from dir are not loaded. + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + // test jar engine is loaded ok. + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + do_check_eq(engine.description, "bug645970"); + + do_test_finished(); + }); +} diff --git a/toolkit/components/search/tests/xpcshell/test_bug930456.js b/toolkit/components/search/tests/xpcshell/test_bug930456.js new file mode 100644 index 000000000..1dbb06c59 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_bug930456.js @@ -0,0 +1,11 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() +{ + if (isChild) { + do_check_false("@mozilla.org/browser/search-service;1" in Cc); + } else { + do_check_true("@mozilla.org/browser/search-service;1" in Cc); + } +} diff --git a/toolkit/components/search/tests/xpcshell/test_bug930456_child.js b/toolkit/components/search/tests/xpcshell/test_bug930456_child.js new file mode 100644 index 000000000..8540a37f4 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_bug930456_child.js @@ -0,0 +1,3 @@ +function run_test() { + run_test_in_child("test_bug930456.js"); +} diff --git a/toolkit/components/search/tests/xpcshell/test_chromeresource_icon1.js b/toolkit/components/search/tests/xpcshell/test_chromeresource_icon1.js new file mode 100644 index 000000000..7d3b1698a --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_chromeresource_icon1.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Test that resource URLs can be used in default engines */ + +"use strict"; + +function run_test() { + updateAppInfo(); + + // The test engines used in this test need to be recognized as 'default' + // engines or the resource URL won't be used + let url = "resource://test/data/"; + let resProt = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + resProt.setSubstitution("search-plugins", + Services.io.newURI(url, null, null)); + + run_next_test(); +} + +add_task(function* test_defaultresourceicon() { + yield asyncInit(); + + let engine1 = Services.search.getEngineByName("engine-resourceicon"); + do_check_eq(engine1.iconURI.spec, "resource://search-plugins/icon16.png"); + do_check_eq(engine1.getIconURLBySize(32, 32), "resource://search-plugins/icon32.png"); + let engine2 = Services.search.getEngineByName("engine-chromeicon"); + do_check_eq(engine2.iconURI.spec, "chrome://branding/content/icon16.png"); + do_check_eq(engine2.getIconURLBySize(32, 32), "chrome://branding/content/icon32.png"); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_chromeresource_icon2.js b/toolkit/components/search/tests/xpcshell/test_chromeresource_icon2.js new file mode 100644 index 000000000..52aff1168 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_chromeresource_icon2.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Test that an installed engine can't use a resource URL for an icon */ + +"use strict"; + +function run_test() { + removeMetadata(); + updateAppInfo(); + useHttpServer(); + + run_next_test(); +} + +add_task(function* test_installedresourceicon() { + let [engine1, engine2] = yield addTestEngines([ + { name: "engine-resourceicon", xmlFileName: "engine-resourceicon.xml" }, + { name: "engine-chromeicon", xmlFileName: "engine-chromeicon.xml" }, + ]); + do_check_null(engine1.iconURI); + do_check_null(engine2.iconURI); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_currentEngine_fallback.js b/toolkit/components/search/tests/xpcshell/test_currentEngine_fallback.js new file mode 100644 index 000000000..d4c699d97 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_currentEngine_fallback.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + do_check_true(Services.search.getVisibleEngines().length > 1); + do_check_true(Services.search.isInitialized); + + // Remove the current engine... + let currentEngine = Services.search.currentEngine; + Services.search.removeEngine(currentEngine); + + // ... and verify a new current engine has been set. + do_check_neq(Services.search.currentEngine.name, currentEngine.name); + do_check_true(currentEngine.hidden); + + // Remove all the other engines. + Services.search.getVisibleEngines().forEach(Services.search.removeEngine); + do_check_eq(Services.search.getVisibleEngines().length, 0); + + // Verify the original default engine is used as a fallback and no + // longer hidden. + do_check_eq(Services.search.currentEngine.name, currentEngine.name); + do_check_false(currentEngine.hidden); + do_check_eq(Services.search.getVisibleEngines().length, 1); +} diff --git a/toolkit/components/search/tests/xpcshell/test_defaultEngine.js b/toolkit/components/search/tests/xpcshell/test_defaultEngine.js new file mode 100644 index 000000000..13d9922de --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_defaultEngine.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Test that currentEngine and defaultEngine properties can be set and yield the + * proper events and behavior (search results) + */ + +"use strict"; + +function run_test() { + removeMetadata(); + updateAppInfo(); + useHttpServer(); + + run_next_test(); +} + +add_task(function* test_defaultEngine() { + let search = Services.search; + + let originalDefault = search.defaultEngine; + + let [engine1, engine2] = yield addTestEngines([ + { name: "Test search engine", xmlFileName: "engine.xml" }, + { name: "A second test engine", xmlFileName: "engine2.xml" }, + ]); + + search.defaultEngine = engine1; + do_check_eq(search.defaultEngine, engine1); + search.defaultEngine = engine2 + do_check_eq(search.defaultEngine, engine2); + search.defaultEngine = engine1; + do_check_eq(search.defaultEngine, engine1); + + // Test that hiding the currently-default engine affects the defaultEngine getter + // We fallback first to the original default... + engine1.hidden = true; + do_check_eq(search.defaultEngine, originalDefault); + + // ... and then to the first visible engine in the list, so move our second + // engine to that position. + search.moveEngine(engine2, 0); + originalDefault.hidden = true; + do_check_eq(search.defaultEngine, engine2); + + // Test that setting defaultEngine to an already-hidden engine works, but + // doesn't change the return value of the getter + search.defaultEngine = engine1; + do_check_eq(search.defaultEngine, engine2); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_engineUpdate.js b/toolkit/components/search/tests/xpcshell/test_engineUpdate.js new file mode 100644 index 000000000..adff41ffb --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_engineUpdate.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Test that user-set metadata isn't lost on engine update */ + +"use strict"; + +function run_test() { + updateAppInfo(); + useHttpServer(); + + run_next_test(); +} + +add_task(function* test_engineUpdate() { + const KEYWORD = "keyword"; + const FILENAME = "engine.xml" + const TOPIC = "browser-search-engine-modified"; + const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000; + + yield asyncInit(); + + let [engine] = yield addTestEngines([ + { name: "Test search engine", xmlFileName: FILENAME }, + ]); + + engine.alias = KEYWORD; + Services.search.moveEngine(engine, 0); + // can't have an accurate updateURL in the file since we can't know the test + // server origin, so manually set it + engine.wrappedJSObject._updateURL = gDataUrl + FILENAME; + + yield new Promise(resolve => { + Services.obs.addObserver(function obs(subject, topic, data) { + if (data == "engine-loaded") { + let loadedEngine = subject.QueryInterface(Ci.nsISearchEngine); + let rawEngine = loadedEngine.wrappedJSObject; + equal(loadedEngine.alias, KEYWORD, "Keyword not cleared by update"); + equal(rawEngine.getAttr("order"), 1, "Order not cleared by update"); + Services.obs.removeObserver(obs, TOPIC, false); + resolve(); + } + }, TOPIC, false); + + // set last update to 8 days ago, since the default interval is 7, then + // trigger an update + engine.wrappedJSObject.setAttr("updateexpir", Date.now() - (ONE_DAY_IN_MS * 8)); + Services.search.QueryInterface(Components.interfaces.nsITimerCallback).notify(null); + }); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_engine_set_alias.js b/toolkit/components/search/tests/xpcshell/test_engine_set_alias.js new file mode 100644 index 000000000..b3c51caa5 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_engine_set_alias.js @@ -0,0 +1,80 @@ +"use strict"; + +function run_test() { + useHttpServer(); + + run_next_test(); +} + +add_task(function* test_engine_set_alias() { + yield asyncInit(); + do_print("Set engine alias"); + let [engine1] = yield addTestEngines([ + { + name: "bacon", + details: ["", "b", "Search Bacon", "GET", "http://www.bacon.test/find"] + } + ]); + Assert.equal(engine1.alias, "b"); + engine1.alias = "a"; + Assert.equal(engine1.alias, "a"); + Services.search.removeEngine(engine1); +}); + +add_task(function* test_engine_set_alias_with_left_space() { + do_print("Set engine alias with left space"); + let [engine2] = yield addTestEngines([ + { + name: "bacon", + details: ["", " a", "Search Bacon", "GET", "http://www.bacon.test/find"] + } + ]); + Assert.equal(engine2.alias, "a"); + engine2.alias = " c"; + Assert.equal(engine2.alias, "c"); + Services.search.removeEngine(engine2); +}); + +add_task(function* test_engine_set_alias_with_right_space() { + do_print("Set engine alias with right space"); + let [engine3] = yield addTestEngines([ + { + name: "bacon", + details: ["", "c ", "Search Bacon", "GET", "http://www.bacon.test/find"] + } + ]); + Assert.equal(engine3.alias, "c"); + engine3.alias = "o "; + Assert.equal(engine3.alias, "o"); + Services.search.removeEngine(engine3); +}); + +add_task(function* test_engine_set_alias_with_right_left_space() { + do_print("Set engine alias with left and right space"); + let [engine4] = yield addTestEngines([ + { + name: "bacon", + details: ["", " o ", "Search Bacon", "GET", "http://www.bacon.test/find"] + } + ]); + Assert.equal(engine4.alias, "o"); + engine4.alias = " n "; + Assert.equal(engine4.alias, "n"); + Services.search.removeEngine(engine4); +}); + +add_task(function* test_engine_set_alias_with_space() { + do_print("Set engine alias with space"); + let [engine5] = yield addTestEngines([ + { + name: "bacon", + details: ["", " ", "Search Bacon", "GET", "http://www.bacon.test/find"] + } + ]); + Assert.equal(engine5.alias, null); + engine5.alias = "b"; + Assert.equal(engine5.alias, "b"); + engine5.alias = " "; + Assert.equal(engine5.alias, null); + Services.search.removeEngine(engine5); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_geodefaults.js b/toolkit/components/search/tests/xpcshell/test_geodefaults.js new file mode 100644 index 000000000..2367bbbc2 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_geodefaults.js @@ -0,0 +1,253 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var requests = []; +var gServerCohort = ""; + +const kUrlPref = "geoSpecificDefaults.url"; + +const kDayInSeconds = 86400; +const kYearInSeconds = kDayInSeconds * 365; + +function run_test() { + updateAppInfo(); + installTestEngine(); + + let srv = new HttpServer(); + + srv.registerPathHandler("/lookup_defaults", (metadata, response) => { + response.setStatusLine("1.1", 200, "OK"); + let data = {interval: kYearInSeconds, + settings: {searchDefault: "Test search engine"}}; + if (gServerCohort) + data.cohort = gServerCohort; + response.write(JSON.stringify(data)); + requests.push(metadata); + }); + + srv.registerPathHandler("/lookup_fail", (metadata, response) => { + response.setStatusLine("1.1", 404, "Not Found"); + requests.push(metadata); + }); + + srv.registerPathHandler("/lookup_unavailable", (metadata, response) => { + response.setStatusLine("1.1", 503, "Service Unavailable"); + response.setHeader("Retry-After", kDayInSeconds.toString()); + requests.push(metadata); + }); + + srv.start(-1); + do_register_cleanup(() => srv.stop(() => {})); + + let url = "http://localhost:" + srv.identity.primaryPort + "/lookup_defaults?"; + Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, url); + // Set a bogus user value so that running the test ensures we ignore it. + Services.prefs.setCharPref(BROWSER_SEARCH_PREF + kUrlPref, "about:blank"); + Services.prefs.setCharPref("browser.search.geoip.url", + 'data:application/json,{"country_code": "FR"}'); + + run_next_test(); +} + +function checkNoRequest() { + do_check_eq(requests.length, 0); +} + +function checkRequest(cohort = "") { + do_check_eq(requests.length, 1); + let req = requests.pop(); + do_check_eq(req._method, "GET"); + do_check_eq(req._queryString, cohort ? "/" + cohort : ""); +} + +add_task(function* no_request_if_prefed_off() { + // Disable geoSpecificDefaults and check no HTTP request is made. + Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false); + yield asyncInit(); + checkNoRequest(); + yield promiseAfterCache(); + + // The default engine should be set based on the prefs. + do_check_eq(Services.search.currentEngine.name, getDefaultEngineName(false)); + + // Ensure nothing related to geoSpecificDefaults has been written in the metadata. + let metadata = yield promiseGlobalMetadata(); + do_check_eq(typeof metadata.searchDefaultExpir, "undefined"); + do_check_eq(typeof metadata.searchDefault, "undefined"); + do_check_eq(typeof metadata.searchDefaultHash, "undefined"); + + Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", true); +}); + +add_task(function* should_get_geo_defaults_only_once() { + // (Re)initializing the search service should trigger a request, + // and set the default engine based on it. + // Due to the previous initialization, we expect the countryCode to already be set. + do_check_true(Services.prefs.prefHasUserValue("browser.search.countryCode")); + do_check_eq(Services.prefs.getCharPref("browser.search.countryCode"), "FR"); + yield asyncReInit(); + checkRequest(); + do_check_eq(Services.search.currentEngine.name, kTestEngineName); + yield promiseAfterCache(); + + // Verify the metadata was written correctly. + let metadata = yield promiseGlobalMetadata(); + do_check_eq(typeof metadata.searchDefaultExpir, "number"); + do_check_true(metadata.searchDefaultExpir > Date.now()); + do_check_eq(typeof metadata.searchDefault, "string"); + do_check_eq(metadata.searchDefault, "Test search engine"); + do_check_eq(typeof metadata.searchDefaultHash, "string"); + do_check_eq(metadata.searchDefaultHash.length, 44); + + // The next restart shouldn't trigger a request. + yield asyncReInit(); + checkNoRequest(); + do_check_eq(Services.search.currentEngine.name, kTestEngineName); +}); + +add_task(function* should_request_when_countryCode_not_set() { + Services.prefs.clearUserPref("browser.search.countryCode"); + yield asyncReInit(); + checkRequest(); + yield promiseAfterCache(); +}); + +add_task(function* should_recheck_if_interval_expired() { + yield forceExpiration(); + + let date = Date.now(); + yield asyncReInit(); + checkRequest(); + yield promiseAfterCache(); + + // Check that the expiration timestamp has been updated. + let metadata = yield promiseGlobalMetadata(); + do_check_eq(typeof metadata.searchDefaultExpir, "number"); + do_check_true(metadata.searchDefaultExpir >= date + kYearInSeconds * 1000); + do_check_true(metadata.searchDefaultExpir < date + (kYearInSeconds + 3600) * 1000); +}); + +add_task(function* should_recheck_when_broken_hash() { + // This test verifies both that we ignore saved geo-defaults if the + // hash is invalid, and that we keep the local preferences-based + // default for all of the session in case a synchronous + // initialization was triggered before our HTTP request completed. + + let metadata = yield promiseGlobalMetadata(); + + // Break the hash. + let hash = metadata.searchDefaultHash; + metadata.searchDefaultHash = "broken"; + yield promiseSaveGlobalMetadata(metadata); + + let commitPromise = promiseAfterCache(); + let unInitPromise = waitForSearchNotification("uninit-complete"); + let reInitPromise = asyncReInit(); + yield unInitPromise; + + // Synchronously check the current default engine, to force a sync init. + // The hash is wrong, so we should fallback to the default engine from prefs. + do_check_false(Services.search.isInitialized) + do_check_eq(Services.search.currentEngine.name, getDefaultEngineName(false)); + do_check_true(Services.search.isInitialized) + + yield reInitPromise; + checkRequest(); + yield commitPromise; + + // Check that the hash is back to its previous value. + metadata = yield promiseGlobalMetadata(); + do_check_eq(typeof metadata.searchDefaultHash, "string"); + if (metadata.searchDefaultHash == "broken") { + // If the server takes more than 1000ms to return the result, + // the commitPromise was resolved by a first save of the cache + // that saved the engines, but not the request's results. + do_print("waiting for the cache to be saved a second time"); + yield promiseAfterCache(); + metadata = yield promiseGlobalMetadata(); + } + do_check_eq(metadata.searchDefaultHash, hash); + + // The current default engine shouldn't change during a session. + do_check_eq(Services.search.currentEngine.name, getDefaultEngineName(false)); + + // After another restart, the current engine should be back to the geo default, + // without doing yet another request. + yield asyncReInit(); + checkNoRequest(); + do_check_eq(Services.search.currentEngine.name, kTestEngineName); +}); + +add_task(function* should_remember_cohort_id() { + // Check that initially the cohort pref doesn't exist. + const cohortPref = "browser.search.cohort"; + do_check_eq(Services.prefs.getPrefType(cohortPref), Services.prefs.PREF_INVALID); + + // Make the server send a cohort id. + let cohort = gServerCohort = "xpcshell"; + + // Trigger a new request. + yield forceExpiration(); + let commitPromise = promiseAfterCache(); + yield asyncReInit(); + checkRequest(); + yield commitPromise; + + // Check that the cohort was saved. + do_check_eq(Services.prefs.getPrefType(cohortPref), Services.prefs.PREF_STRING); + do_check_eq(Services.prefs.getCharPref(cohortPref), cohort); + + // Make the server stop sending the cohort. + gServerCohort = ""; + + // Check that the next request sends the previous cohort id, and + // will remove it from the prefs due to the server no longer sending it. + yield forceExpiration(); + commitPromise = promiseAfterCache(); + yield asyncReInit(); + checkRequest(cohort); + yield commitPromise; + do_check_eq(Services.prefs.getPrefType(cohortPref), Services.prefs.PREF_INVALID); +}); + +add_task(function* should_retry_after_failure() { + let defaultBranch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF); + let originalUrl = defaultBranch.getCharPref(kUrlPref); + defaultBranch.setCharPref(kUrlPref, originalUrl.replace("defaults", "fail")); + + // Trigger a new request. + yield forceExpiration(); + yield asyncReInit(); + checkRequest(); + + // After another restart, a new request should be triggered automatically without + // the test having to call forceExpiration again. + yield asyncReInit(); + checkRequest(); +}); + +add_task(function* should_honor_retry_after_header() { + let defaultBranch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF); + let originalUrl = defaultBranch.getCharPref(kUrlPref); + defaultBranch.setCharPref(kUrlPref, originalUrl.replace("fail", "unavailable")); + + // Trigger a new request. + yield forceExpiration(); + let date = Date.now(); + let commitPromise = promiseAfterCache(); + yield asyncReInit(); + checkRequest(); + yield commitPromise; + + // Check that the expiration timestamp has been updated. + let metadata = yield promiseGlobalMetadata(); + do_check_eq(typeof metadata.searchDefaultExpir, "number"); + do_check_true(metadata.searchDefaultExpir >= date + kDayInSeconds * 1000); + do_check_true(metadata.searchDefaultExpir < date + (kDayInSeconds + 3600) * 1000); + + // After another restart, a new request should not be triggered. + yield asyncReInit(); + checkNoRequest(); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_hasEngineWithURL.js b/toolkit/components/search/tests/xpcshell/test_hasEngineWithURL.js new file mode 100644 index 000000000..e48b1673c --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_hasEngineWithURL.js @@ -0,0 +1,135 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests the hasEngineWithURL() method of the nsIBrowserSearchService. + */ +function run_test() { + do_print("Setting up test"); + + updateAppInfo(); + useHttpServer(); + + do_print("Test starting"); + run_next_test(); +} + + +// Return a discreet, cloned copy of an (engine) object. +function getEngineClone(engine) { + return JSON.parse(JSON.stringify(engine)); +} + +// Check whether and engine does or doesn't exist. +function checkEngineState(exists, engine) { + do_check_eq(exists, Services.search.hasEngineWithURL(engine.method, + engine.formURL, + engine.queryParams)); +} + +// Add a search engine for testing. +function addEngineWithParams(engine) { + Services.search.addEngineWithDetails(engine.name, null, null, null, + engine.method, engine.formURL); + + let addedEngine = Services.search.getEngineByName(engine.name); + for (let param of engine.queryParams) { + addedEngine.addParam(param.name, param.value, null); + } +} + +// Main test. +add_task(function* test_hasEngineWithURL() { + // Avoid deprecated synchronous initialization. + yield asyncInit(); + + // Setup various Engine definitions for method tests. + let UNSORTED_ENGINE = { + name: "mySearch Engine", + method: "GET", + formURL: "https://totallyNotRealSearchEngine.com/", + queryParams: [ + { name: "DDs", value: "38s" }, + { name: "DCs", value: "39s" }, + { name: "DDs", value: "39s" }, + { name: "DDs", value: "38s" }, + { name: "DDs", value: "37s" }, + { name: "DDs", value: "38s" }, + { name: "DEs", value: "38s" }, + { name: "DCs", value: "38s" }, + { name: "DEs", value: "37s" }, + ], + }; + + // Same as UNSORTED_ENGINE, but sorted. + let SORTED_ENGINE = { + name: "mySearch Engine", + method: "GET", + formURL: "https://totallyNotRealSearchEngine.com/", + queryParams: [ + { name: "DCs", value: "38s" }, + { name: "DCs", value: "39s" }, + { name: "DDs", value: "37s" }, + { name: "DDs", value: "38s" }, + { name: "DDs", value: "38s" }, + { name: "DDs", value: "38s" }, + { name: "DDs", value: "39s" }, + { name: "DEs", value: "37s" }, + { name: "DEs", value: "38s" }, + ], + }; + + // Unique variations of the SORTED_ENGINE. + let SORTED_ENGINE_METHOD_CHANGE = getEngineClone(SORTED_ENGINE); + SORTED_ENGINE_METHOD_CHANGE.method = "PoST"; + + let SORTED_ENGINE_FORMURL_CHANGE = getEngineClone(SORTED_ENGINE); + SORTED_ENGINE_FORMURL_CHANGE.formURL = "http://www.ahighrpowr.com/" + + let SORTED_ENGINE_QUERYPARM_CHANGE = getEngineClone(SORTED_ENGINE); + SORTED_ENGINE_QUERYPARM_CHANGE.queryParams = []; + + let SORTED_ENGINE_NAME_CHANGE = getEngineClone(SORTED_ENGINE); + SORTED_ENGINE_NAME_CHANGE.name += " 2"; + + + // First ensure neither the unsorted engine, nor the same engine + // with a pre-sorted list of query parms matches. + checkEngineState(false, UNSORTED_ENGINE); + do_print("The unsorted version of the test engine does not exist."); + checkEngineState(false, SORTED_ENGINE); + do_print("The sorted version of the test engine does not exist."); + + // Ensure variations of the engine definition do not match. + checkEngineState(false, SORTED_ENGINE_METHOD_CHANGE); + checkEngineState(false, SORTED_ENGINE_FORMURL_CHANGE); + checkEngineState(false, SORTED_ENGINE_QUERYPARM_CHANGE); + do_print("There are no modified versions of the sorted test engine."); + + // Note that this method doesn't check name variations. + checkEngineState(false, SORTED_ENGINE_NAME_CHANGE); + do_print("There is no NAME modified version of the sorted test engine."); + + + // Add the unsorted engine and it's queryParams. + addEngineWithParams(UNSORTED_ENGINE); + do_print("The unsorted engine has been added."); + + + // Then, ensure we find a match for the unsorted engine, and for the + // same engine with a pre-sorted list of query parms. + checkEngineState(true, UNSORTED_ENGINE); + do_print("The unsorted version of the test engine now exists."); + checkEngineState(true, SORTED_ENGINE); + do_print("The sorted version of the same test engine also now exists."); + + // Ensure variations of the engine definition still do not match. + checkEngineState(false, SORTED_ENGINE_METHOD_CHANGE); + checkEngineState(false, SORTED_ENGINE_FORMURL_CHANGE); + checkEngineState(false, SORTED_ENGINE_QUERYPARM_CHANGE); + do_print("There are still no modified versions of the sorted test engine."); + + // Note that this method still doesn't check name variations. + checkEngineState(true, SORTED_ENGINE_NAME_CHANGE); + do_print("There IS now a NAME modified version of the sorted test engine."); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_hidden.js b/toolkit/components/search/tests/xpcshell/test_hidden.js new file mode 100644 index 000000000..b784f3624 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_hidden.js @@ -0,0 +1,93 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const kUrlPref = "geoSpecificDefaults.url"; + +function run_test() { + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + + // Geo specific defaults won't be fetched if there's no country code. + Services.prefs.setCharPref("browser.search.geoip.url", + 'data:application/json,{"country_code": "US"}'); + + // Make 'hidden' the only visible engine. + let url = "data:application/json,{\"interval\": 31536000, \"settings\": {\"searchDefault\": \"hidden\", \"visibleDefaultEngines\": [\"hidden\"]}}"; + Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, url); + + do_check_false(Services.search.isInitialized); + + run_next_test(); +} + +add_task(function* async_init() { + let commitPromise = promiseAfterCache() + yield asyncInit(); + + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + // The default test jar engine has been hidden. + let engine = Services.search.getEngineByName("bug645970"); + do_check_eq(engine, null); + + // The hidden engine is visible. + engine = Services.search.getEngineByName("hidden"); + do_check_neq(engine, null); + + // The next test does a sync init, which won't do the geoSpecificDefaults XHR, + // so it depends on the metadata having been written to disk. + yield commitPromise; +}); + +add_task(function* sync_init() { + let unInitPromise = waitForSearchNotification("uninit-complete"); + let reInitPromise = asyncReInit(); + yield unInitPromise; + do_check_false(Services.search.isInitialized); + + // Synchronously check the current default engine, to force a sync init. + do_check_eq(Services.search.currentEngine.name, "hidden"); + do_check_true(Services.search.isInitialized); + + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + // The default test jar engine has been hidden. + let engine = Services.search.getEngineByName("bug645970"); + do_check_eq(engine, null); + + // The hidden engine is visible. + engine = Services.search.getEngineByName("hidden"); + do_check_neq(engine, null); + + yield reInitPromise; +}); + +add_task(function* invalid_engine() { + // Trigger a new request. + yield forceExpiration(); + + // Set the visibleDefaultEngines list to something that contains a non-existent engine. + // This should cause the search service to ignore the list altogether and fallback to + // local defaults. + let url = "data:application/json,{\"interval\": 31536000, \"settings\": {\"searchDefault\": \"hidden\", \"visibleDefaultEngines\": [\"hidden\", \"bogus\"]}}"; + Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, url); + + yield asyncReInit(); + + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + // The default test jar engine is visible. + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + // The hidden engine is... hidden. + engine = Services.search.getEngineByName("hidden"); + do_check_eq(engine, null); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_identifiers.js b/toolkit/components/search/tests/xpcshell/test_identifiers.js new file mode 100644 index 000000000..0d5ca5b90 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_identifiers.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Test that a search engine's identifier can be extracted from the filename. + */ + +"use strict"; + +const SEARCH_APP_DIR = 1; + +function run_test() { + removeMetadata(); + removeCacheFile(); + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + + updateAppInfo(); + + run_next_test(); +} + +add_test(function test_identifier() { + let engineFile = gProfD.clone(); + engineFile.append("searchplugins"); + engineFile.append("test-search-engine.xml"); + engineFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + // Copy the test engine to the test profile. + let engineTemplateFile = do_get_file("data/engine.xml"); + engineTemplateFile.copyTo(engineFile.parent, "test-search-engine.xml"); + + Services.search.init(function initComplete(aResult) { + do_print("init'd search service"); + do_check_true(Components.isSuccessCode(aResult)); + + let profileEngine = Services.search.getEngineByName("Test search engine"); + let jarEngine = Services.search.getEngineByName("bug645970"); + + do_check_true(profileEngine instanceof Ci.nsISearchEngine); + do_check_true(jarEngine instanceof Ci.nsISearchEngine); + + // An engine loaded from the profile directory won't have an identifier, + // because it's not built-in. + do_check_eq(profileEngine.identifier, null); + + // An engine loaded from a JAR will have an identifier corresponding to + // the filename inside the JAR. (In this case it's the same as the name.) + do_check_eq(jarEngine.identifier, "bug645970"); + + removeMetadata(); + removeCacheFile(); + run_next_test(); + }); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_init_async_multiple.js b/toolkit/components/search/tests/xpcshell/test_init_async_multiple.js new file mode 100644 index 000000000..3aa3353ce --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_init_async_multiple.js @@ -0,0 +1,55 @@ +/* -*- 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/. */ + +/** + * Test nsSearchService with with the following initialization scenario: + * - launch asynchronous initialization several times; + * - all asynchronous initializations must complete. + * + * Test case comes from test_645970.js + */ +function run_test() { + do_print("Setting up test"); + + do_test_pending(); + updateAppInfo(); + + do_print("Test starting"); + let numberOfInitializers = 4; + let pending = []; + let numberPending = numberOfInitializers; + + // Start asynchronous initializations + for (let i = 0; i < numberOfInitializers; ++i) { + let me = i; + pending[me] = true; + Services.search.init(function search_initialized_0(aStatus) { + do_check_true(Components.isSuccessCode(aStatus)); + init_complete(me); + }); + } + + // Wait until all initializers have completed + let init_complete = function init_complete(i) { + do_check_true(pending[i]); + pending[i] = false; + numberPending--; + do_check_true(numberPending >= 0); + do_check_true(Services.search.isInitialized); + if (numberPending == 0) { + // Just check that we can access a list of engines. + let engines = Services.search.getEngines(); + do_check_neq(engines, null); + + // Wait a little before quitting: if some initializer is + // triggered twice, we want to catch that error. + do_timeout(1000, function() { + do_test_finished(); + }); + } + }; +} + diff --git a/toolkit/components/search/tests/xpcshell/test_init_async_multiple_then_sync.js b/toolkit/components/search/tests/xpcshell/test_init_async_multiple_then_sync.js new file mode 100644 index 000000000..ed4ecdcd8 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_init_async_multiple_then_sync.js @@ -0,0 +1,68 @@ +/* -*- 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/. */ + +/** + * Test nsSearchService with with the following initialization scenario: + * - launch asynchronous initialization several times; + * - force fallback to synchronous initialization. + * - all asynchronous initializations must complete; + * - no asynchronous initialization must complete more than once. + * + * Test case comes from test_645970.js + */ +function run_test() { + do_print("Setting up test"); + do_test_pending(); + updateAppInfo(); + + do_print("Test starting"); + + let numberOfInitializers = 4; + let pending = []; + let numberPending = numberOfInitializers; + + // Start asynchronous initializations + for (let i = 0; i < numberOfInitializers; ++i) { + let me = i; + pending[me] = true; + Services.search.init(function search_initialized(aStatus) { + do_check_true(Components.isSuccessCode(aStatus)); + init_complete(me); + }); + } + + // Ensure that all asynchronous initializers eventually complete + let init_complete = function init_complete(i) { + do_print("init complete " + i); + do_check_true(pending[i]); + pending[i] = false; + numberPending--; + do_check_true(numberPending >= 0); + do_check_true(Services.search.isInitialized); + if (numberPending != 0) { + do_print("Still waiting for the following initializations: " + JSON.stringify(pending)); + return; + } + do_print("All initializations have completed"); + // Just check that we can access a list of engines. + let engines = Services.search.getEngines(); + do_check_neq(engines, null); + + do_print("Waiting a second before quitting"); + // Wait a little before quitting: if some initializer is + // triggered twice, we want to catch that error. + do_timeout(1000, function() { + do_print("Test is complete"); + do_test_finished(); + }); + }; + + // ... but don't wait for asynchronous initializations to complete + let engines = Services.search.getEngines(); + do_check_neq(engines, null); + do_print("Synchronous part of the test complete"); +} + diff --git a/toolkit/components/search/tests/xpcshell/test_invalid_engine_from_dir.js b/toolkit/components/search/tests/xpcshell/test_invalid_engine_from_dir.js new file mode 100644 index 000000000..c6455735a --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_invalid_engine_from_dir.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Test that invalid engine files with xml extensions will not break + * initialization. See Bug 940446. + */ +function run_test() { + do_test_pending(); + + removeMetadata(); + removeCacheFile(); + + do_check_false(Services.search.isInitialized); + + let engineFile = gProfD.clone(); + engineFile.append("searchplugins"); + engineFile.append("test-search-engine.xml"); + engineFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + // Copy the invalid engine to the test profile. + let engineTemplateFile = do_get_file("data/invalid-engine.xml"); + engineTemplateFile.copyTo(engineFile.parent, "test-search-engine.xml"); + + Services.search.init(function search_initialized(aStatus) { + // The invalid engine should have been skipped and should not + // have caused an exception. + do_check_true(Components.isSuccessCode(aStatus)); + do_check_true(Services.search.isInitialized); + + removeMetadata(); + removeCacheFile(); + do_test_finished(); + }); +} diff --git a/toolkit/components/search/tests/xpcshell/test_json_cache.js b/toolkit/components/search/tests/xpcshell/test_json_cache.js new file mode 100644 index 000000000..c804b0bca --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_json_cache.js @@ -0,0 +1,227 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Test initializing from the search cache. + */ + +"use strict"; + +/** + * Gets a directory from the directory service. + * @param aKey + * The directory service key indicating the directory to get. + */ +var _dirSvc = null; +function getDir(aKey, aIFace) { + if (!aKey) { + FAIL("getDir requires a directory key!"); + } + + if (!_dirSvc) { + _dirSvc = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties); + } + return _dirSvc.get(aKey, aIFace || Ci.nsIFile); +} + +function makeURI(uri) { + return Services.io.newURI(uri, null, null); +} + +var cacheTemplate, appPluginsPath, profPlugins; + +/** + * Test reading from search.json.mozlz4 + */ +function run_test() { + removeMetadata(); + removeCacheFile(); + + updateAppInfo(); + + let cacheTemplateFile = do_get_file("data/search.json"); + cacheTemplate = readJSONFile(cacheTemplateFile); + cacheTemplate.buildID = getAppInfo().platformBuildID; + + let engineFile = gProfD.clone(); + engineFile.append("searchplugins"); + engineFile.append("test-search-engine.xml"); + engineFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + // Copy the test engine to the test profile. + let engineTemplateFile = do_get_file("data/engine.xml"); + engineTemplateFile.copyTo(engineFile.parent, "test-search-engine.xml"); + + // The list of visibleDefaultEngines needs to match or the cache will be ignored. + let chan = NetUtil.newChannel({ + uri: "resource://search-plugins/list.json", + loadUsingSystemPrincipal: true + }); + let sis = Cc["@mozilla.org/scriptableinputstream;1"]. + createInstance(Ci.nsIScriptableInputStream); + sis.init(chan.open2()); + let list = sis.read(sis.available()); + let searchSettings = JSON.parse(list); + + cacheTemplate.visibleDefaultEngines = searchSettings["default"]["visibleDefaultEngines"]; + + run_next_test(); +} + +add_test(function prepare_test_data() { + OS.File.writeAtomic(OS.Path.join(OS.Constants.Path.profileDir, CACHE_FILENAME), + new TextEncoder().encode(JSON.stringify(cacheTemplate)), + {compression: "lz4"}) + .then(run_next_test); +}); + +/** + * Start the search service and confirm the engine properties match the expected values. + */ +add_test(function test_cached_engine_properties() { + do_print("init search service"); + + Services.search.init(function initComplete(aResult) { + do_print("init'd search service"); + do_check_true(Components.isSuccessCode(aResult)); + + let engines = Services.search.getEngines({}); + let engine = engines[0]; + + do_check_true(engine instanceof Ci.nsISearchEngine); + isSubObjectOf(EXPECTED_ENGINE.engine, engine); + + let engineFromSS = Services.search.getEngineByName(EXPECTED_ENGINE.engine.name); + do_check_true(!!engineFromSS); + isSubObjectOf(EXPECTED_ENGINE.engine, engineFromSS); + + removeMetadata(); + removeCacheFile(); + run_next_test(); + }); +}); + +/** + * Test that the JSON cache written in the profile is correct. + */ +add_test(function test_cache_write() { + do_print("test cache writing"); + + let cache = gProfD.clone(); + cache.append(CACHE_FILENAME); + do_check_false(cache.exists()); + + do_print("Next step is forcing flush"); + do_timeout(0, function forceFlush() { + do_print("Forcing flush"); + // Force flush + // Note: the timeout is needed, to avoid some reentrency + // issues in nsSearchService. + + let cacheWriteObserver = { + observe: function cacheWriteObserver_observe(aEngine, aTopic, aVerb) { + if (aTopic != "browser-search-service" || aVerb != "write-cache-to-disk-complete") { + return; + } + Services.obs.removeObserver(cacheWriteObserver, "browser-search-service"); + do_print("Cache write complete"); + do_check_true(cache.exists()); + // Check that the search.json.mozlz4 cache matches the template + + promiseCacheData().then(cacheWritten => { + do_print("Check search.json.mozlz4"); + isSubObjectOf(cacheTemplate, cacheWritten); + + run_next_test(); + }); + } + }; + Services.obs.addObserver(cacheWriteObserver, "browser-search-service", false); + + Services.search.QueryInterface(Ci.nsIObserver).observe(null, "browser-search-engine-modified", "engine-removed"); + }); +}); + +var EXPECTED_ENGINE = { + engine: { + name: "Test search engine", + alias: null, + description: "A test search engine (based on Google search)", + searchForm: "http://www.google.com/", + wrappedJSObject: { + _extensionID: "test-addon-id@mozilla.org", + "_iconURL": "data:image/png;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA", + _urls : [ + { + type: "application/x-suggestions+json", + method: "GET", + template: "http://suggestqueries.google.com/complete/search?output=firefox&client=firefox" + + "&hl={moz:locale}&q={searchTerms}", + params: "", + }, + { + type: "text/html", + method: "GET", + template: "http://www.google.com/search", + resultDomain: "google.com", + params: [ + { + "name": "q", + "value": "{searchTerms}", + "purpose": undefined, + }, + { + "name": "ie", + "value": "utf-8", + "purpose": undefined, + }, + { + "name": "oe", + "value": "utf-8", + "purpose": undefined, + }, + { + "name": "aq", + "value": "t", + "purpose": undefined, + }, + { + "name": "channel", + "value": "fflb", + "purpose": "keyword", + }, + { + "name": "channel", + "value": "rcs", + "purpose": "contextmenu", + }, + ], + }, + { + type: "application/x-moz-default-purpose", + method: "GET", + template: "http://www.google.com/search", + resultDomain: "purpose.google.com", + params: [ + { + "name": "q", + "value": "{searchTerms}", + "purpose": undefined, + }, + { + "name": "channel", + "value": "fflb", + "purpose": "keyword", + }, + { + "name": "channel", + "value": "rcs", + "purpose": "contextmenu", + }, + ], + }, + ], + }, + }, +}; diff --git a/toolkit/components/search/tests/xpcshell/test_location.js b/toolkit/components/search/tests/xpcshell/test_location.js new file mode 100644 index 000000000..93e6139f6 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_location.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + installTestEngine(); + + Services.prefs.setCharPref("browser.search.geoip.url", 'data:application/json,{"country_code": "AU"}'); + Services.search.init(() => { + equal(Services.prefs.getCharPref("browser.search.countryCode"), "AU", "got the correct country code."); + equal(Services.prefs.getCharPref("browser.search.region"), "AU", "region pref also set to the countryCode.") + // No isUS pref is ever written + ok(!Services.prefs.prefHasUserValue("browser.search.isUS"), "no isUS pref") + // check we have "success" recorded in telemetry + checkCountryResultTelemetry(TELEMETRY_RESULT_ENUM.SUCCESS); + // a false value for each of SEARCH_SERVICE_COUNTRY_TIMEOUT and SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT + for (let hid of ["SEARCH_SERVICE_COUNTRY_TIMEOUT", + "SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT"]) { + let histogram = Services.telemetry.getHistogramById(hid); + let snapshot = histogram.snapshot(); + deepEqual(snapshot.counts, [1, 0, 0]); // boolean probe so 3 buckets, expect 1 result for |0|. + + } + + // simple checks for our platform-specific telemetry. We can't influence + // what they return (as we can't influence the countryCode the platform + // thinks we are in), but we can check the values are correct given reality. + let probeUSMismatched, probeNonUSMismatched; + switch (Services.appinfo.OS) { + case "Darwin": + probeUSMismatched = "SEARCH_SERVICE_US_COUNTRY_MISMATCHED_PLATFORM_OSX"; + probeNonUSMismatched = "SEARCH_SERVICE_NONUS_COUNTRY_MISMATCHED_PLATFORM_OSX"; + break; + case "WINNT": + probeUSMismatched = "SEARCH_SERVICE_US_COUNTRY_MISMATCHED_PLATFORM_WIN"; + probeNonUSMismatched = "SEARCH_SERVICE_NONUS_COUNTRY_MISMATCHED_PLATFORM_WIN"; + break; + default: + break; + } + + if (probeUSMismatched && probeNonUSMismatched) { + let countryCode = Services.sysinfo.get("countryCode"); + print("Platform says the country-code is", countryCode); + let expectedResult; + let hid; + // We know geoip said AU - if the platform thinks US then we expect + // probeUSMismatched with true (ie, a mismatch) + if (countryCode == "US") { + hid = probeUSMismatched; + expectedResult = [0, 1, 0]; // boolean probe so 3 buckets, expect 1 result for |1|. + } else { + // We are expecting probeNonUSMismatched with false if the platform + // says AU (not a mismatch) and true otherwise. + hid = probeNonUSMismatched; + expectedResult = countryCode == "AU" ? [1, 0, 0] : [0, 1, 0]; + } + + let histogram = Services.telemetry.getHistogramById(hid); + let snapshot = histogram.snapshot(); + deepEqual(snapshot.counts, expectedResult); + } + do_test_finished(); + run_next_test(); + }); + do_test_pending(); +} diff --git a/toolkit/components/search/tests/xpcshell/test_location_error.js b/toolkit/components/search/tests/xpcshell/test_location_error.js new file mode 100644 index 000000000..049189351 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_location_error.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + installTestEngine(); + + // We use an invalid port that parses but won't open + let url = "http://localhost:0"; + + Services.prefs.setCharPref("browser.search.geoip.url", url); + Services.search.init(() => { + try { + Services.prefs.getCharPref("browser.search.countryCode"); + ok(false, "not expecting countryCode to be set"); + } catch (ex) {} + // should have an error recorded. + checkCountryResultTelemetry(TELEMETRY_RESULT_ENUM.ERROR); + // but false values for timeout and forced-sync-init. + for (let hid of ["SEARCH_SERVICE_COUNTRY_TIMEOUT", + "SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT"]) { + let histogram = Services.telemetry.getHistogramById(hid); + let snapshot = histogram.snapshot(); + deepEqual(snapshot.counts, [1, 0, 0]); // boolean probe so 3 buckets, expect 1 result for |0|. + } + + do_test_finished(); + run_next_test(); + }); + do_test_pending(); +} diff --git a/toolkit/components/search/tests/xpcshell/test_location_funnelcake.js b/toolkit/components/search/tests/xpcshell/test_location_funnelcake.js new file mode 100644 index 000000000..970ba5521 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_location_funnelcake.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + Services.prefs.setCharPref("browser.search.geoip.url", 'data:application/json,{"country_code": "US"}'); + // funnelcake builds start with "mozilla" + Services.prefs.setCharPref("distribution.id", 'mozilla38'); + setUpGeoDefaults(); + + Services.search.init(() => { + equal(Services.search.defaultEngine.name, "A second test engine"); + + do_test_finished(); + run_next_test(); + }); + do_test_pending(); +} diff --git a/toolkit/components/search/tests/xpcshell/test_location_malformed_json.js b/toolkit/components/search/tests/xpcshell/test_location_malformed_json.js new file mode 100644 index 000000000..b1f30ad7c --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_location_malformed_json.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// A console listener so we can listen for a log message from nsSearchService. +function promiseTimezoneMessage() { + return new Promise(resolve => { + let listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]), + observe : function (msg) { + if (msg.message.startsWith("getIsUS() fell back to a timezone check with the result=")) { + Services.console.unregisterListener(listener); + resolve(msg); + } + } + }; + Services.console.registerListener(listener); + }); +} + +function run_test() { + installTestEngine(); + + // setup a console listener for the timezone fallback message. + let promiseTzMessage = promiseTimezoneMessage(); + + // Here we have malformed JSON + Services.prefs.setCharPref("browser.search.geoip.url", 'data:application/json,{"country_code"'); + Services.search.init(() => { + ok(!Services.prefs.prefHasUserValue("browser.search.countryCode"), "should be no countryCode pref"); + ok(!Services.prefs.prefHasUserValue("browser.search.region"), "should be no region pref"); + ok(!Services.prefs.prefHasUserValue("browser.search.isUS"), "should never be an isUS pref"); + // fetch the engines - this should force the timezone check, but still + // doesn't persist any prefs. + Services.search.getEngines(); + ok(!Services.prefs.prefHasUserValue("browser.search.countryCode"), "should be no countryCode pref"); + ok(!Services.prefs.prefHasUserValue("browser.search.region"), "should be no region pref"); + ok(!Services.prefs.prefHasUserValue("browser.search.isUS"), "should never be an isUS pref"); + // should have recorded SUCCESS_WITHOUT_DATA + checkCountryResultTelemetry(TELEMETRY_RESULT_ENUM.SUCCESS_WITHOUT_DATA); + // and false values for timeout and forced-sync-init. + for (let hid of ["SEARCH_SERVICE_COUNTRY_TIMEOUT", + "SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT"]) { + let histogram = Services.telemetry.getHistogramById(hid); + let snapshot = histogram.snapshot(); + deepEqual(snapshot.counts, [1, 0, 0]); // boolean probe so 3 buckets, expect 1 result for |0|. + } + + // Check we saw the timezone fallback message. + promiseTzMessage.then(msg => { + print("Timezone message:", msg.message); + ok(msg.message.endsWith(isUSTimezone().toString()), "fell back to timezone and it matches our timezone"); + do_test_finished(); + run_next_test(); + }); + }); + do_test_pending(); +} diff --git a/toolkit/components/search/tests/xpcshell/test_location_migrate_countrycode_isUS.js b/toolkit/components/search/tests/xpcshell/test_location_migrate_countrycode_isUS.js new file mode 100644 index 000000000..9e1019761 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_location_migrate_countrycode_isUS.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Here we are testing the "migration" when both isUS and countryCode are +// set. +function run_test() { + installTestEngine(); + + // Set the prefs we care about. + Services.prefs.setBoolPref("browser.search.isUS", true); + Services.prefs.setCharPref("browser.search.countryCode", "US"); + // And the geoip request that will return AU - it shouldn't be used. + Services.prefs.setCharPref("browser.search.geoip.url", 'data:application/json,{"country_code": "AU"}'); + Services.search.init(() => { + // "region" and countryCode should still reflect US. + equal(Services.prefs.getCharPref("browser.search.region"), "US", "got the correct region."); + equal(Services.prefs.getCharPref("browser.search.countryCode"), "US", "got the correct country code."); + // should be no geoip evidence. + checkCountryResultTelemetry(null); + do_test_finished(); + run_next_test(); + }); + do_test_pending(); +} diff --git a/toolkit/components/search/tests/xpcshell/test_location_migrate_no_countrycode_isUS.js b/toolkit/components/search/tests/xpcshell/test_location_migrate_no_countrycode_isUS.js new file mode 100644 index 000000000..b294b038b --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_location_migrate_no_countrycode_isUS.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Here we are testing the "migration" from the isUS pref being true but when +// no country-code exists. +function run_test() { + installTestEngine(); + + // Set the pref we care about. + Services.prefs.setBoolPref("browser.search.isUS", true); + // And the geoip request that will return *not* US. + Services.prefs.setCharPref("browser.search.geoip.url", 'data:application/json,{"country_code": "AU"}'); + Services.search.init(() => { + // "region" should be set to US, but countryCode to AU. + equal(Services.prefs.getCharPref("browser.search.region"), "US", "got the correct region."); + equal(Services.prefs.getCharPref("browser.search.countryCode"), "AU", "got the correct country code."); + // check we have "success" recorded in telemetry + checkCountryResultTelemetry(TELEMETRY_RESULT_ENUM.SUCCESS); + // a false value for each of SEARCH_SERVICE_COUNTRY_TIMEOUT and SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT + for (let hid of ["SEARCH_SERVICE_COUNTRY_TIMEOUT", + "SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT"]) { + let histogram = Services.telemetry.getHistogramById(hid); + let snapshot = histogram.snapshot(); + deepEqual(snapshot.counts, [1, 0, 0]); // boolean probe so 3 buckets, expect 1 result for |0|. + } + do_test_finished(); + run_next_test(); + }); + do_test_pending(); +} diff --git a/toolkit/components/search/tests/xpcshell/test_location_migrate_no_countrycode_notUS.js b/toolkit/components/search/tests/xpcshell/test_location_migrate_no_countrycode_notUS.js new file mode 100644 index 000000000..9c0b810d3 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_location_migrate_no_countrycode_notUS.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Here we are testing the "migration" from the isUS pref being false but when +// no country-code exists. +function run_test() { + installTestEngine(); + + // Set the pref we care about. + Services.prefs.setBoolPref("browser.search.isUS", false); + // And the geoip request that will return US. + Services.prefs.setCharPref("browser.search.geoip.url", 'data:application/json,{"country_code": "US"}'); + Services.search.init(() => { + // "region" and countryCode should reflect US. + equal(Services.prefs.getCharPref("browser.search.region"), "US", "got the correct region."); + equal(Services.prefs.getCharPref("browser.search.countryCode"), "US", "got the correct country code."); + // check we have "success" recorded in telemetry + checkCountryResultTelemetry(TELEMETRY_RESULT_ENUM.SUCCESS); + // a false value for each of SEARCH_SERVICE_COUNTRY_TIMEOUT and SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT + for (let hid of ["SEARCH_SERVICE_COUNTRY_TIMEOUT", + "SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT"]) { + let histogram = Services.telemetry.getHistogramById(hid); + let snapshot = histogram.snapshot(); + deepEqual(snapshot.counts, [1, 0, 0]); // boolean probe so 3 buckets, expect 1 result for |0|. + } + do_test_finished(); + run_next_test(); + }); + do_test_pending(); +} diff --git a/toolkit/components/search/tests/xpcshell/test_location_partner.js b/toolkit/components/search/tests/xpcshell/test_location_partner.js new file mode 100644 index 000000000..9151add9a --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_location_partner.js @@ -0,0 +1,16 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + Services.prefs.setCharPref("browser.search.geoip.url", 'data:application/json,{"country_code": "US"}'); + Services.prefs.setCharPref("distribution.id", 'partner-1'); + setUpGeoDefaults(); + + Services.search.init(() => { + equal(Services.search.defaultEngine.name, "Test search engine"); + + do_test_finished(); + run_next_test(); + }); + do_test_pending(); +} diff --git a/toolkit/components/search/tests/xpcshell/test_location_sync.js b/toolkit/components/search/tests/xpcshell/test_location_sync.js new file mode 100644 index 000000000..524a440fb --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_location_sync.js @@ -0,0 +1,101 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function getCountryCodePref() { + try { + return Services.prefs.getCharPref("browser.search.countryCode"); + } catch (_) { + return undefined; + } +} + +function getIsUSPref() { + try { + return Services.prefs.getBoolPref("browser.search.isUS"); + } catch (_) { + return undefined; + } +} + +// A console listener so we can listen for a log message from nsSearchService. +function promiseTimezoneMessage() { + return new Promise(resolve => { + let listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]), + observe : function (msg) { + if (msg.message.startsWith("getIsUS() fell back to a timezone check with the result=")) { + Services.console.unregisterListener(listener); + resolve(msg); + } + } + }; + Services.console.registerListener(listener); + }); +} + +function run_test() { + installTestEngine(); + + run_next_test(); +} + +// Force a sync init and ensure the right thing happens (ie, that no xhr +// request is made and we fall back to the timezone-only trick) +add_task(function* test_simple() { + deepEqual(getCountryCodePref(), undefined, "no countryCode pref"); + deepEqual(getIsUSPref(), undefined, "no isUS pref"); + + // Still set a geoip pref so we can (indirectly) check it wasn't used. + Services.prefs.setCharPref("browser.search.geoip.url", 'data:application/json,{"country_code": "AU"}'); + + ok(!Services.search.isInitialized); + + // setup a console listener for the timezone fallback message. + let promiseTzMessage = promiseTimezoneMessage(); + + // fetching the engines forces a sync init, and should have caused us to + // check the timezone. + Services.search.getEngines(); + ok(Services.search.isInitialized); + + // a little wait to check we didn't do the xhr thang. + yield new Promise(resolve => { + do_timeout(500, resolve); + }); + + let msg = yield promiseTzMessage; + print("Timezone message:", msg.message); + ok(msg.message.endsWith(isUSTimezone().toString()), "fell back to timezone and it matches our timezone"); + + deepEqual(getCountryCodePref(), undefined, "didn't do the geoip xhr"); + // and no telemetry evidence of geoip. + for (let hid of [ + "SEARCH_SERVICE_COUNTRY_FETCH_RESULT", + "SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS", + "SEARCH_SERVICE_COUNTRY_TIMEOUT", + "SEARCH_SERVICE_US_COUNTRY_MISMATCHED_TIMEZONE", + "SEARCH_SERVICE_US_TIMEZONE_MISMATCHED_COUNTRY", + "SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT", + ]) { + let histogram = Services.telemetry.getHistogramById(hid); + let snapshot = histogram.snapshot(); + equal(snapshot.sum, 0, hid); + switch (snapshot.histogram_type) { + case Ci.nsITelemetry.HISTOGRAM_FLAG: + // flags are a special case in that they are initialized with a default + // of one |0|. + deepEqual(snapshot.counts, [1, 0, 0], hid); + break; + case Ci.nsITelemetry.HISTOGRAM_BOOLEAN: + // booleans aren't initialized at all, so should have all zeros. + deepEqual(snapshot.counts, [0, 0, 0], hid); + break; + case Ci.nsITelemetry.HISTOGRAM_EXPONENTIAL: + case Ci.nsITelemetry.HISTOGRAM_LINEAR: + equal(snapshot.counts.reduce((a, b) => a+b), 0, hid); + break; + default: + ok(false, "unknown histogram type " + snapshot.histogram_type + " for " + hid); + } + } +}); diff --git a/toolkit/components/search/tests/xpcshell/test_location_timeout.js b/toolkit/components/search/tests/xpcshell/test_location_timeout.js new file mode 100644 index 000000000..c1d5270e5 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_location_timeout.js @@ -0,0 +1,78 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This is testing the "normal" timer-based timeout for the location search. + +function startServer(continuePromise) { + let srv = new HttpServer(); + function lookupCountry(metadata, response) { + response.processAsync(); + // wait for our continuePromise to resolve before writing a valid + // response. + // This will be resolved after the timeout period, so we can check + // the behaviour in that case. + continuePromise.then(() => { + response.setStatusLine("1.1", 200, "OK"); + response.write('{"country_code" : "AU"}'); + response.finish(); + }); + } + srv.registerPathHandler("/lookup_country", lookupCountry); + srv.start(-1); + return srv; +} + +function getProbeSum(probe, sum) { + let histogram = Services.telemetry.getHistogramById(probe); + return histogram.snapshot().sum; +} + +function run_test() { + installTestEngine(); + + let resolveContinuePromise; + let continuePromise = new Promise(resolve => { + resolveContinuePromise = resolve; + }); + + let server = startServer(continuePromise); + let url = "http://localhost:" + server.identity.primaryPort + "/lookup_country"; + Services.prefs.setCharPref("browser.search.geoip.url", url); + Services.prefs.setIntPref("browser.search.geoip.timeout", 50); + Services.search.init(() => { + ok(!Services.prefs.prefHasUserValue("browser.search.countryCode"), "should be no countryCode pref"); + ok(!Services.prefs.prefHasUserValue("browser.search.region"), "should be no region pref"); + // should be no result recorded at all. + checkCountryResultTelemetry(null); + + // should have set the flag indicating we saw a timeout. + let histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT"); + let snapshot = histogram.snapshot(); + deepEqual(snapshot.counts, [0, 1, 0]); + // should not yet have SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS recorded as our + // test server is still blocked on our promise. + equal(getProbeSum("SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS"), 0); + + waitForSearchNotification("geoip-lookup-xhr-complete").then(() => { + // now we *should* have a report of how long the response took even though + // it timed out. + // The telemetry "sum" will be the actual time in ms - just check it's non-zero. + ok(getProbeSum("SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS") != 0); + // should have reported the fetch ended up being successful + checkCountryResultTelemetry(TELEMETRY_RESULT_ENUM.SUCCESS); + + // and should have the result of the response that finally came in, and + // everything dependent should also be updated. + equal(Services.prefs.getCharPref("browser.search.countryCode"), "AU"); + equal(Services.prefs.getCharPref("browser.search.region"), "AU"); + ok(!Services.prefs.prefHasUserValue("browser.search.isUS"), "should never have an isUS pref"); + + do_test_finished(); + server.stop(run_next_test); + }); + // now tell the server to send its response. That will end up causing the + // search service to notify of that the response was received. + resolveContinuePromise(); + }); + do_test_pending(); +} diff --git a/toolkit/components/search/tests/xpcshell/test_location_timeout_xhr.js b/toolkit/components/search/tests/xpcshell/test_location_timeout_xhr.js new file mode 100644 index 000000000..4054cf0c2 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_location_timeout_xhr.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This is testing the long, last-resort XHR-based timeout for the location +// search. + +function startServer(continuePromise) { + let srv = new HttpServer(); + function lookupCountry(metadata, response) { + response.processAsync(); + // wait for our continuePromise to resolve before writing a valid + // response. + // This will be resolved after the timeout period, so we can check + // the behaviour in that case. + continuePromise.then(() => { + response.setStatusLine("1.1", 200, "OK"); + response.write('{"country_code" : "AU"}'); + response.finish(); + }); + } + srv.registerPathHandler("/lookup_country", lookupCountry); + srv.start(-1); + return srv; +} + +function verifyProbeSum(probe, sum) { + let histogram = Services.telemetry.getHistogramById(probe); + let snapshot = histogram.snapshot(); + equal(snapshot.sum, sum, probe); +} + +function run_test() { + installTestEngine(); + + let resolveContinuePromise; + let continuePromise = new Promise(resolve => { + resolveContinuePromise = resolve; + }); + + let server = startServer(continuePromise); + let url = "http://localhost:" + server.identity.primaryPort + "/lookup_country"; + Services.prefs.setCharPref("browser.search.geoip.url", url); + // The timeout for the timer. + Services.prefs.setIntPref("browser.search.geoip.timeout", 10); + let promiseXHRStarted = waitForSearchNotification("geoip-lookup-xhr-starting"); + Services.search.init(() => { + ok(!Services.prefs.prefHasUserValue("browser.search.countryCode"), "should be no countryCode pref"); + ok(!Services.prefs.prefHasUserValue("browser.search.region"), "should be no region pref"); + // should be no result recorded at all. + checkCountryResultTelemetry(null); + + // should have set the flag indicating we saw a timeout. + let histogram = Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT"); + let snapshot = histogram.snapshot(); + deepEqual(snapshot.counts, [0, 1, 0]); + + // should not have SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS recorded as our + // test server is still blocked on our promise. + verifyProbeSum("SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS", 0); + + promiseXHRStarted.then(xhr => { + // Set the timeout on the xhr object to an extremely low value, so it + // should timeout immediately. + xhr.timeout = 10; + // wait for the xhr timeout to fire. + waitForSearchNotification("geoip-lookup-xhr-complete").then(() => { + // should have the XHR timeout recorded. + checkCountryResultTelemetry(TELEMETRY_RESULT_ENUM.XHRTIMEOUT); + // still should not have a report of how long the response took as we + // only record that on success responses. + verifyProbeSum("SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS", 0); + // and we still don't know the country code or region. + ok(!Services.prefs.prefHasUserValue("browser.search.countryCode"), "should be no countryCode pref"); + ok(!Services.prefs.prefHasUserValue("browser.search.region"), "should be no region pref"); + + // unblock the server even though nothing is listening. + resolveContinuePromise(); + + do_test_finished(); + server.stop(run_next_test); + }); + }); + }); + do_test_pending(); +} diff --git a/toolkit/components/search/tests/xpcshell/test_migration_langpack.js b/toolkit/components/search/tests/xpcshell/test_migration_langpack.js new file mode 100644 index 000000000..8cb2014bd --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_migration_langpack.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + + // Unless we unset the XPCSHELL_TEST_PROFILE_DIR environment variable, + // engine._isDefault will be true for engines from the resource:// scheme, + // bypassing the codepath we want to test. + let env = Cc["@mozilla.org/process/environment;1"] + .getService(Ci.nsIEnvironment); + env.set("XPCSHELL_TEST_PROFILE_DIR", ""); + + do_get_file("data/langpack-metadata.json").copyTo(gProfD, "search-metadata.json"); + + do_check_false(Services.search.isInitialized); + + run_next_test(); +} + +add_task(function* async_init() { + let commitPromise = promiseAfterCache() + yield asyncInit(); + + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + do_check_eq(engine.wrappedJSObject._id, "[app]/bug645970.xml"); + + yield commitPromise; + let metadata = yield promiseEngineMetadata(); + do_check_eq(metadata["bug645970"].alias, "lp"); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_multipleIcons.js b/toolkit/components/search/tests/xpcshell/test_multipleIcons.js new file mode 100644 index 000000000..314515f6d --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_multipleIcons.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Tests getIcons() and getIconURLBySize() on engine with multiple icons. + */ + +"use strict"; + +function run_test() { + removeMetadata(); + updateAppInfo(); + useHttpServer(); + + run_next_test(); +} + +add_task(function* test_multipleIcons() { + let [engine] = yield addTestEngines([ + { name: "IconsTest", xmlFileName: "engineImages.xml" }, + ]); + + do_print("The default should be the 16x16 icon"); + do_check_true(engine.iconURI.spec.includes("ico16")); + + do_check_true(engine.getIconURLBySize(16, 16).includes("ico16")); + do_check_true(engine.getIconURLBySize(32, 32).includes("ico32")); + do_check_true(engine.getIconURLBySize(74, 74).includes("ico74")); + + do_print("Invalid dimensions should return null."); + do_check_null(engine.getIconURLBySize(50, 50)); + + let allIcons = engine.getIcons(); + + do_print("Check that allIcons contains expected icon sizes"); + do_check_eq(allIcons.length, 3); + let expectedWidths = [16, 32, 74]; + do_check_true(allIcons.every((item) => { + let width = item.width; + do_check_neq(expectedWidths.indexOf(width), -1); + do_check_eq(width, item.height); + + let icon = item.url.split(",").pop(); + do_check_eq(icon, "ico" + width); + + return true; + })); +}); + +add_task(function* test_icon_not_in_file() { + let engineUrl = gDataUrl + "engine-fr.xml"; + let engine = yield new Promise((resolve, reject) => { + Services.search.addEngine(engineUrl, null, "data:image/x-icon;base64,ico16", + false, {onSuccess: resolve, onError: reject}); + }); + + // Even though the icon wasn't specified inside the XML file, it should be + // available both in the iconURI attribute and with getIconURLBySize. + do_check_true(engine.iconURI.spec.includes("ico16")); + do_check_true(engine.getIconURLBySize(16, 16).includes("ico16")); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_nocache.js b/toolkit/components/search/tests/xpcshell/test_nocache.js new file mode 100644 index 000000000..42776aef0 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_nocache.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * test_nocache: Start search engine + * - without search.json.mozlz4 + * + * Ensure that : + * - nothing explodes; + * - search.json.mozlz4 is created. + */ + +function run_test() +{ + removeCacheFile(); + updateAppInfo(); + do_load_manifest("data/chrome.manifest"); + useHttpServer(); + + run_next_test(); +} + +add_task(function* test_nocache() { + let search = Services.search; + + let afterCachePromise = promiseAfterCache(); + + yield new Promise((resolve, reject) => search.init(rv => { + Components.isSuccessCode(rv) ? resolve() : reject(); + })); + + // Check that the cache is created at startup + yield afterCachePromise; + + // Check that search.json has been created. + let cacheFile = gProfD.clone(); + cacheFile.append(CACHE_FILENAME); + do_check_true(cacheFile.exists()); + + // Add engine and wait for cache update + yield addTestEngines([ + { name: "Test search engine", xmlFileName: "engine.xml" }, + ]); + + do_print("Engine has been added, let's wait for the cache to be built"); + yield promiseAfterCache(); + + do_print("Searching test engine in cache"); + let cache = yield promiseCacheData(); + let found = false; + for (let engine of cache.engines) { + if (engine._shortName == "test-search-engine") { + found = true; + break; + } + } + do_check_true(found); + + removeCacheFile(); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_nodb.js b/toolkit/components/search/tests/xpcshell/test_nodb.js new file mode 100644 index 000000000..66a003a5d --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_nodb.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * test_nodb: Start search engine + * - without search-metadata.json + * - without search.sqlite + * + * Ensure that : + * - nothing explodes; + * - no search-metadata.json is created. + */ + + +function run_test() +{ + removeMetadata(); + updateAppInfo(); + + let search = Services.search; + + do_test_pending(); + search.init(function ss_initialized(rv) { + do_check_true(Components.isSuccessCode(rv)); + do_timeout(500, function() { + // Check that search-metadata.json has not been + // created. Note that we cannot do much better + // than a timeout for checking a non-event. + let metadata = gProfD.clone(); + metadata.append("search-metadata.json"); + do_check_true(!metadata.exists()); + removeMetadata(); + + do_test_finished(); + }); + }); +} diff --git a/toolkit/components/search/tests/xpcshell/test_nodb_pluschanges.js b/toolkit/components/search/tests/xpcshell/test_nodb_pluschanges.js new file mode 100644 index 000000000..e6789e964 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_nodb_pluschanges.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + + +/* + * test_nodb: Start search engine + * - without search-metadata.json + * - without search.sqlite + * + * Ensure that : + * - nothing explodes; + * - if we change the order, search.json.mozlz4 is updated; + * - this search.json.mozlz4 can be parsed; + * - the order stored in search.json.mozlz4 is consistent. + * + * Notes: + * - we install the search engines of test "test_downloadAndAddEngines.js" + * to ensure that this test is independent from locale, commercial agreements + * and configuration of Firefox. + */ + +function run_test() { + updateAppInfo(); + do_load_manifest("data/chrome.manifest"); + useHttpServer(); + + run_next_test(); +} + +add_task(function* test_nodb_pluschanges() { + let [engine1, engine2] = yield addTestEngines([ + { name: "Test search engine", xmlFileName: "engine.xml" }, + { name: "A second test engine", xmlFileName: "engine2.xml"}, + ]); + yield promiseAfterCache(); + + let search = Services.search; + + search.moveEngine(engine1, 0); + search.moveEngine(engine2, 1); + + // This is needed to avoid some reentrency issues in nsSearchService. + do_print("Next step is forcing flush"); + yield new Promise(resolve => do_execute_soon(resolve)); + + do_print("Forcing flush"); + let promiseCommit = promiseAfterCache(); + search.QueryInterface(Ci.nsIObserver) + .observe(null, "quit-application", ""); + yield promiseCommit; + do_print("Commit complete"); + + // Check that the entries are placed as specified correctly + let metadata = yield promiseEngineMetadata(); + do_check_eq(metadata["test-search-engine"].order, 1); + do_check_eq(metadata["a-second-test-engine"].order, 2); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_notifications.js b/toolkit/components/search/tests/xpcshell/test_notifications.js new file mode 100644 index 000000000..3eecbf8b1 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_notifications.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var gTestLog = []; + +/** + * The order of notifications expected for this test is: + * - engine-changed (while we're installing the engine, we modify it, which notifies - bug 606886) + * - engine-added (engine was added to the store by the search service) + * -> our search observer is called, which sets: + * - .defaultEngine, triggering engine-default + * - .currentEngine, triggering engine-current (after bug 493051 - for now the search service sets this after engine-added) + * ...and then schedules a removal + * - engine-loaded (the search service's observer is garanteed to fire first, which is what causes engine-added to fire) + * - engine-removed (due to the removal schedule above) + */ +var expectedLog = [ + "engine-changed", // XXX bug 606886 + "engine-added", + "engine-default", + "engine-current", + "engine-loaded", + "engine-removed" +]; + +function search_observer(subject, topic, data) { + let engine = subject.QueryInterface(Ci.nsISearchEngine); + gTestLog.push(data + " for " + engine.name); + + do_print("Observer: " + data + " for " + engine.name); + + switch (data) { + case "engine-added": + let retrievedEngine = Services.search.getEngineByName("Test search engine"); + do_check_eq(engine, retrievedEngine); + Services.search.defaultEngine = engine; + Services.search.currentEngine = engine; + do_execute_soon(function () { + Services.search.removeEngine(engine); + }); + break; + case "engine-removed": + let engineNameOutput = " for Test search engine"; + expectedLog = expectedLog.map(logLine => logLine + engineNameOutput); + do_print("expectedLog:\n" + expectedLog.join("\n")) + do_print("gTestLog:\n" + gTestLog.join("\n")) + for (let i = 0; i < expectedLog.length; i++) { + do_check_eq(gTestLog[i], expectedLog[i]); + } + do_check_eq(gTestLog.length, expectedLog.length); + do_test_finished(); + break; + } +} + +function run_test() { + removeMetadata(); + updateAppInfo(); + useHttpServer(); + + do_register_cleanup(function cleanup() { + Services.obs.removeObserver(search_observer, "browser-search-engine-modified"); + }); + + do_test_pending(); + + Services.obs.addObserver(search_observer, "browser-search-engine-modified", false); + + Services.search.addEngine(gDataUrl + "engine.xml", null, null, false); +} diff --git a/toolkit/components/search/tests/xpcshell/test_parseSubmissionURL.js b/toolkit/components/search/tests/xpcshell/test_parseSubmissionURL.js new file mode 100644 index 000000000..d6e21fc61 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_parseSubmissionURL.js @@ -0,0 +1,148 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Tests getAlternateDomains API. + */ + +"use strict"; + +function run_test() { + removeMetadata(); + updateAppInfo(); + useHttpServer(); + + run_next_test(); +} + +add_task(function* test_parseSubmissionURL() { + // Hide the default engines to prevent them from being used in the search. + for (let engine of Services.search.getEngines()) { + Services.search.removeEngine(engine); + } + + let [engine1, engine2, engine3, engine4] = yield addTestEngines([ + { name: "Test search engine", xmlFileName: "engine.xml" }, + { name: "Test search engine (fr)", xmlFileName: "engine-fr.xml" }, + { name: "bacon_addParam", details: ["", "bacon_addParam", "Search Bacon", + "GET", "http://www.bacon.test/find"] }, + { name: "idn_addParam", details: ["", "idn_addParam", "Search IDN", + "GET", "http://www.xn--bcher-kva.ch/search"] }, + // The following engines cannot identify the search parameter. + { name: "A second test engine", xmlFileName: "engine2.xml" }, + { name: "bacon", details: ["", "bacon", "Search Bacon", "GET", + "http://www.bacon.moz/search?q={searchTerms}"] }, + ]); + + engine3.addParam("q", "{searchTerms}", null); + engine4.addParam("q", "{searchTerms}", null); + + // Test the first engine, whose URLs use UTF-8 encoding. + let url = "http://www.google.com/search?foo=bar&q=caff%C3%A8"; + let result = Services.search.parseSubmissionURL(url); + do_check_eq(result.engine, engine1); + do_check_eq(result.terms, "caff\u00E8"); + do_check_true(url.slice(result.termsOffset).startsWith("caff%C3%A8")); + do_check_eq(result.termsLength, "caff%C3%A8".length); + + // The second engine uses a locale-specific domain that is an alternate domain + // of the first one, but the second engine should get priority when matching. + // The URL used with this engine uses ISO-8859-1 encoding instead. + url = "http://www.google.fr/search?q=caff%E8"; + result = Services.search.parseSubmissionURL(url); + do_check_eq(result.engine, engine2); + do_check_eq(result.terms, "caff\u00E8"); + do_check_true(url.slice(result.termsOffset).startsWith("caff%E8")); + do_check_eq(result.termsLength, "caff%E8".length); + + // Test a domain that is an alternate domain of those defined. In this case, + // the first matching engine from the ordered list should be returned. + url = "http://www.google.co.uk/search?q=caff%C3%A8"; + result = Services.search.parseSubmissionURL(url); + do_check_eq(result.engine, engine1); + do_check_eq(result.terms, "caff\u00E8"); + do_check_true(url.slice(result.termsOffset).startsWith("caff%C3%A8")); + do_check_eq(result.termsLength, "caff%C3%A8".length); + + // We support parsing URLs from a dynamically added engine. Those engines use + // windows-1252 encoding by default. + url = "http://www.bacon.test/find?q=caff%E8"; + result = Services.search.parseSubmissionURL(url); + do_check_eq(result.engine, engine3); + do_check_eq(result.terms, "caff\u00E8"); + do_check_true(url.slice(result.termsOffset).startsWith("caff%E8")); + do_check_eq(result.termsLength, "caff%E8".length); + + // Test URLs with unescaped unicode characters. + url = "http://www.google.com/search?q=foo+b\u00E4r"; + result = Services.search.parseSubmissionURL(url); + do_check_eq(result.engine, engine1); + do_check_eq(result.terms, "foo b\u00E4r"); + do_check_true(url.slice(result.termsOffset).startsWith("foo+b\u00E4r")); + do_check_eq(result.termsLength, "foo+b\u00E4r".length); + + // Test search engines with unescaped IDNs. + url = "http://www.b\u00FCcher.ch/search?q=foo+bar"; + result = Services.search.parseSubmissionURL(url); + do_check_eq(result.engine, engine4); + do_check_eq(result.terms, "foo bar"); + do_check_true(url.slice(result.termsOffset).startsWith("foo+bar")); + do_check_eq(result.termsLength, "foo+bar".length); + + // Test search engines with escaped IDNs. + url = "http://www.xn--bcher-kva.ch/search?q=foo+bar"; + result = Services.search.parseSubmissionURL(url); + do_check_eq(result.engine, engine4); + do_check_eq(result.terms, "foo bar"); + do_check_true(url.slice(result.termsOffset).startsWith("foo+bar")); + do_check_eq(result.termsLength, "foo+bar".length); + + // Parsing of parameters from an engine template URL is not supported. + do_check_eq(Services.search.parseSubmissionURL( + "http://www.bacon.moz/search?q=").engine, null); + do_check_eq(Services.search.parseSubmissionURL( + "https://duckduckgo.com?q=test").engine, null); + do_check_eq(Services.search.parseSubmissionURL( + "https://duckduckgo.com/?q=test").engine, null); + + // HTTP and HTTPS schemes are interchangeable. + url = "https://www.google.com/search?q=caff%C3%A8"; + result = Services.search.parseSubmissionURL(url); + do_check_eq(result.engine, engine1); + do_check_eq(result.terms, "caff\u00E8"); + do_check_true(url.slice(result.termsOffset).startsWith("caff%C3%A8")); + + // Decoding search terms with multiple spaces should work. + result = Services.search.parseSubmissionURL( + "http://www.google.com/search?q=+with++spaces+"); + do_check_eq(result.engine, engine1); + do_check_eq(result.terms, " with spaces "); + + // An empty query parameter should work the same. + url = "http://www.google.com/search?q="; + result = Services.search.parseSubmissionURL(url); + do_check_eq(result.engine, engine1); + do_check_eq(result.terms, ""); + do_check_eq(result.termsOffset, url.length); + + // There should be no match when the path is different. + result = Services.search.parseSubmissionURL( + "http://www.google.com/search/?q=test"); + do_check_eq(result.engine, null); + do_check_eq(result.terms, ""); + do_check_eq(result.termsOffset, -1); + + // There should be no match when the argument is different. + result = Services.search.parseSubmissionURL( + "http://www.google.com/search?q2=test"); + do_check_eq(result.engine, null); + do_check_eq(result.terms, ""); + do_check_eq(result.termsOffset, -1); + + // There should be no match for URIs that are not HTTP or HTTPS. + result = Services.search.parseSubmissionURL( + "file://localhost/search?q=test"); + do_check_eq(result.engine, null); + do_check_eq(result.terms, ""); + do_check_eq(result.termsOffset, -1); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_pref.js b/toolkit/components/search/tests/xpcshell/test_pref.js new file mode 100644 index 000000000..f51ab4ee8 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_pref.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Test that MozParam condition="pref" values used in search URLs are from the + * default branch, and that their special characters are URL encoded. */ + +"use strict"; + +function run_test() { + updateAppInfo(); + + // The test engines used in this test need to be recognized as 'default' + // engines, or their MozParams will be ignored. + let url = "resource://test/data/"; + let resProt = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + resProt.setSubstitution("search-plugins", + Services.io.newURI(url, null, null)); + + run_next_test(); +} + +add_task(function* test_pref() { + let defaultBranch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF); + defaultBranch.setCharPref("param.code", "good&id=unique"); + Services.prefs.setCharPref(BROWSER_SEARCH_PREF + "param.code", "bad"); + + yield asyncInit(); + + let engine = Services.search.getEngineByName("engine-pref"); + let base = "http://www.google.com/search?q=foo&code="; + do_check_eq(engine.getSubmission("foo").uri.spec, + base + "good%26id%3Dunique"); + + do_test_finished(); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_purpose.js b/toolkit/components/search/tests/xpcshell/test_purpose.js new file mode 100644 index 000000000..46465e0a3 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_purpose.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Test that a search purpose can be specified and that query parameters for + * that purpose are included in the search URL. + */ + +"use strict"; + +function run_test() { + removeMetadata(); + updateAppInfo(); + + // The test engines used in this test need to be recognized as 'default' + // engines, or their MozParams used to set the purpose will be ignored. + let url = "resource://test/data/"; + let resProt = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + resProt.setSubstitution("search-plugins", + Services.io.newURI(url, null, null)); + + run_next_test(); +} + +add_task(function* test_purpose() { + let engine = Services.search.getEngineByName("Test search engine"); + + function check_submission(aExpected, aSearchTerm, aType, aPurpose) { + do_check_eq(engine.getSubmission(aSearchTerm, aType, aPurpose).uri.spec, + base + aExpected); + } + + let base = "http://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&aq=t"; + check_submission("", "foo"); + check_submission("", "foo", null); + check_submission("", "foo", "text/html"); + check_submission("&channel=rcs", "foo", null, "contextmenu"); + check_submission("&channel=rcs", "foo", "text/html", "contextmenu"); + check_submission("&channel=fflb", "foo", null, "keyword"); + check_submission("&channel=fflb", "foo", "text/html", "keyword"); + check_submission("", "foo", "text/html", "invalid"); + + // Tests for a param that varies with a purpose but has a default value. + base = "http://www.google.com/search?q=foo"; + check_submission("&channel=ffsb", "foo", "application/x-moz-default-purpose"); + check_submission("&channel=ffsb", "foo", "application/x-moz-default-purpose", null); + check_submission("&channel=ffsb", "foo", "application/x-moz-default-purpose", ""); + check_submission("&channel=rcs", "foo", "application/x-moz-default-purpose", "contextmenu"); + check_submission("&channel=fflb", "foo", "application/x-moz-default-purpose", "keyword"); + check_submission("&channel=ffsb", "foo", "application/x-moz-default-purpose", "searchbar"); + check_submission("&channel=ffsb", "foo", "application/x-moz-default-purpose", "invalid"); + + // Tests for a purpose on the search form (ie. empty query). + engine = Services.search.getEngineByName("engine-rel-searchform-purpose"); + base = "http://www.google.com/search?q="; + check_submission("&channel=sb", "", null, "searchbar"); + check_submission("&channel=sb", "", "text/html", "searchbar"); + + // verify that the 'system' purpose falls back to the 'searchbar' purpose. + base = "http://www.google.com/search?q=foo"; + check_submission("&channel=sb", "foo", "text/html", "system"); + check_submission("&channel=sb", "foo", "text/html", "searchbar"); + // Use an engine that actually defines the 'system' purpose... + engine = Services.search.getEngineByName("engine-system-purpose"); + // ... and check that the system purpose is used correctly. + check_submission("&channel=sys", "foo", "text/html", "system"); + + do_test_finished(); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_rel_searchform.js b/toolkit/components/search/tests/xpcshell/test_rel_searchform.js new file mode 100644 index 000000000..79f217e0d --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_rel_searchform.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Tests that <Url rel="searchform"/> is properly recognized as a searchForm. + */ + +"use strict"; + +function run_test() { + removeMetadata(); + updateAppInfo(); + useHttpServer(); + + run_next_test(); +} + +add_task(function* test_rel_searchform() { + let engineNames = [ + "engine-rel-searchform.xml", + "engine-rel-searchform-post.xml", + ]; + + // The final searchForm of the engine should be a URL whose domain is the + // <ShortName> in the engine's XML and that has a ?search parameter. The + // point of the ?search parameter is to avoid accidentally matching the value + // returned as a last resort by Engine's searchForm getter, which is simply + // the prePath of the engine's first HTML <Url>. + let items = engineNames.map(e => ({ name: e, xmlFileName: e })); + for (let engine of yield addTestEngines(items)) { + do_check_eq(engine.searchForm, "http://" + engine.name + "/?search"); + } +}); diff --git a/toolkit/components/search/tests/xpcshell/test_remove_profile_engine.js b/toolkit/components/search/tests/xpcshell/test_remove_profile_engine.js new file mode 100644 index 000000000..3a985db9e --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_remove_profile_engine.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const NS_APP_USER_SEARCH_DIR = "UsrSrchPlugns"; + +function run_test() { + do_test_pending(); + + // Copy an engine to [profile]/searchplugin/ + let dir = Services.dirsvc.get(NS_APP_USER_SEARCH_DIR, Ci.nsIFile); + if (!dir.exists()) + dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + do_get_file("data/engine-override.xml").copyTo(dir, "bug645970.xml"); + + let file = dir.clone(); + file.append("bug645970.xml"); + do_check_true(file.exists()); + + do_check_false(Services.search.isInitialized); + + Services.search.init(function search_initialized(aStatus) { + do_check_true(Components.isSuccessCode(aStatus)); + do_check_true(Services.search.isInitialized); + + // test the engine is loaded ok. + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + // remove the engine and verify the file has been removed too. + Services.search.removeEngine(engine); + do_check_false(file.exists()); + + do_test_finished(); + }); +} diff --git a/toolkit/components/search/tests/xpcshell/test_require_engines_in_cache.js b/toolkit/components/search/tests/xpcshell/test_require_engines_in_cache.js new file mode 100644 index 000000000..299121c4f --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_require_engines_in_cache.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + do_check_false(Services.search.isInitialized); + + run_next_test(); +} + +add_task(function* ignore_cache_files_without_engines() { + let commitPromise = promiseAfterCache() + yield asyncInit(); + + let engineCount = Services.search.getEngines().length; + do_check_eq(engineCount, 1); + + // Wait for the file to be saved to disk, so that we can mess with it. + yield commitPromise; + + // Remove all engines from the cache file. + let cache = yield promiseCacheData(); + cache.engines = []; + yield promiseSaveCacheData(cache); + + // Check that after an async re-initialization, we still have the same engine count. + commitPromise = promiseAfterCache() + yield asyncReInit(); + do_check_eq(engineCount, Services.search.getEngines().length); + yield commitPromise; + + // Check that after a sync re-initialization, we still have the same engine count. + yield promiseSaveCacheData(cache); + let unInitPromise = waitForSearchNotification("uninit-complete"); + let reInitPromise = asyncReInit(); + yield unInitPromise; + do_check_false(Services.search.isInitialized); + // Synchronously check the engine count; will force a sync init. + do_check_eq(engineCount, Services.search.getEngines().length); + do_check_true(Services.search.isInitialized); + yield reInitPromise; +}); + +add_task(function* skip_writing_cache_without_engines() { + let unInitPromise = waitForSearchNotification("uninit-complete"); + let reInitPromise = asyncReInit(); + yield unInitPromise; + + // Configure so that no engines will be found. + do_check_true(removeCacheFile()); + let resProt = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + resProt.setSubstitution("search-plugins", + Services.io.newURI("about:blank", null, null)); + + // Let the async-reInit happen. + yield reInitPromise; + do_check_eq(0, Services.search.getEngines().length); + + // Trigger yet another re-init, to flush of any pending cache writing task. + unInitPromise = waitForSearchNotification("uninit-complete"); + reInitPromise = asyncReInit(); + yield unInitPromise; + + // Now check that a cache file doesn't exist. + do_check_false(removeCacheFile()); + + yield reInitPromise; +}); diff --git a/toolkit/components/search/tests/xpcshell/test_resultDomain.js b/toolkit/components/search/tests/xpcshell/test_resultDomain.js new file mode 100644 index 000000000..d7458a923 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_resultDomain.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Tests getResultDomain API. + */ + +"use strict"; + +function run_test() { + removeMetadata(); + updateAppInfo(); + useHttpServer(); + + run_next_test(); +} + +add_task(function* test_resultDomain() { + let [engine1, engine2, engine3] = yield addTestEngines([ + { name: "Test search engine", xmlFileName: "engine.xml" }, + { name: "A second test engine", xmlFileName: "engine2.xml" }, + { name: "bacon", details: ["", "bacon", "Search Bacon", "GET", + "http://www.bacon.moz/?search={searchTerms}"] }, + ]); + + do_check_eq(engine1.getResultDomain(), "google.com"); + do_check_eq(engine1.getResultDomain("text/html"), "google.com"); + do_check_eq(engine1.getResultDomain("application/x-moz-default-purpose"), + "purpose.google.com"); + do_check_eq(engine1.getResultDomain("fake-response-type"), ""); + do_check_eq(engine2.getResultDomain(), "duckduckgo.com"); + do_check_eq(engine3.getResultDomain(), "bacon.moz"); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_save_sorted_engines.js b/toolkit/components/search/tests/xpcshell/test_save_sorted_engines.js new file mode 100644 index 000000000..c509c5f77 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_save_sorted_engines.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * test_save_sorted_engines: Start search engine + * - without search-metadata.json + * - without search.sqlite + * + * Ensure that search-metadata.json is correct after: + * - moving an engine + * - removing an engine + * - adding a new engine + * + * Notes: + * - we install the search engines of test "test_downloadAndAddEngines.js" + * to ensure that this test is independent from locale, commercial agreements + * and configuration of Firefox. + */ + +function run_test() { + updateAppInfo(); + useHttpServer(); + + run_next_test(); +} + +add_task(function* test_save_sorted_engines() { + let [engine1, engine2] = yield addTestEngines([ + { name: "Test search engine", xmlFileName: "engine.xml" }, + { name: "A second test engine", xmlFileName: "engine2.xml"}, + ]); + yield promiseAfterCache(); + + let search = Services.search; + + // Test moving the engines + search.moveEngine(engine1, 0); + search.moveEngine(engine2, 1); + + // Changes should be commited immediately + yield promiseAfterCache(); + do_print("Commit complete after moveEngine"); + + // Check that the entries are placed as specified correctly + let metadata = yield promiseEngineMetadata(); + do_check_eq(metadata["test-search-engine"].order, 1); + do_check_eq(metadata["a-second-test-engine"].order, 2); + + // Test removing an engine + search.removeEngine(engine1); + yield promiseAfterCache(); + do_print("Commit complete after removeEngine"); + + // Check that the order of the remaining engine was updated correctly + metadata = yield promiseEngineMetadata(); + do_check_eq(metadata["a-second-test-engine"].order, 1); + + // Test adding a new engine + search.addEngineWithDetails("foo", "", "foo", "", "GET", + "http://searchget/?search={searchTerms}"); + yield promiseAfterCache(); + do_print("Commit complete after addEngineWithDetails"); + + metadata = yield promiseEngineMetadata(); + do_check_eq(metadata["foo"].alias, "foo"); + do_check_true(metadata["foo"].order > 0); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_searchReset.js b/toolkit/components/search/tests/xpcshell/test_searchReset.js new file mode 100644 index 000000000..316069c95 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_searchReset.js @@ -0,0 +1,137 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const NS_APP_USER_SEARCH_DIR = "UsrSrchPlugns"; + +const kTestEngineShortName = "engine"; +const kWhiteListPrefName = "reset.whitelist"; + +function run_test() { + // Copy an engine to [profile]/searchplugin/ + let dir = Services.dirsvc.get(NS_APP_USER_SEARCH_DIR, Ci.nsIFile); + if (!dir.exists()) + dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + do_get_file("data/engine.xml").copyTo(dir, kTestEngineShortName + ".xml"); + + let file = dir.clone(); + file.append(kTestEngineShortName + ".xml"); + do_check_true(file.exists()); + + do_check_false(Services.search.isInitialized); + + Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF) + .setBoolPref("reset.enabled", true); + + run_next_test(); +} + +function* removeLoadPathHash() { + // Remove the loadPathHash and re-initialize the search service. + let cache = yield promiseCacheData(); + for (let engine of cache.engines) { + if (engine._shortName == kTestEngineShortName) { + delete engine._metaData["loadPathHash"]; + break; + } + } + yield promiseSaveCacheData(cache); + yield asyncReInit(); +} + +add_task(function* test_no_prompt_when_valid_loadPathHash() { + yield asyncInit(); + + // test the engine is loaded ok. + let engine = Services.search.getEngineByName(kTestEngineName); + do_check_neq(engine, null); + + yield promiseAfterCache(); + + // The test engine has been found in the profile directory and imported, + // so it shouldn't have a loadPathHash. + let metadata = yield promiseEngineMetadata(); + do_check_true(kTestEngineShortName in metadata); + do_check_false("loadPathHash" in metadata[kTestEngineShortName]); + + // After making it the currentEngine with the search service API, + // the test engine should have a valid loadPathHash. + Services.search.currentEngine = engine; + yield promiseAfterCache(); + metadata = yield promiseEngineMetadata(); + do_check_true("loadPathHash" in metadata[kTestEngineShortName]); + let loadPathHash = metadata[kTestEngineShortName].loadPathHash; + do_check_eq(typeof loadPathHash, "string"); + do_check_eq(loadPathHash.length, 44); + + // A search should not cause the search reset prompt. + let submission = + Services.search.currentEngine.getSubmission("foo", null, "searchbar"); + do_check_eq(submission.uri.spec, + "http://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&aq=t"); +}); + +add_task(function* test_promptURLs() { + yield removeLoadPathHash(); + + // The default should still be the test engine. + let currentEngine = Services.search.currentEngine; + do_check_eq(currentEngine.name, kTestEngineName); + // but the submission url should be about:searchreset + let url = (data, purpose) => + currentEngine.getSubmission(data, null, purpose).uri.spec; + do_check_eq(url("foo", "searchbar"), + "about:searchreset?data=foo&purpose=searchbar"); + do_check_eq(url("foo"), "about:searchreset?data=foo"); + do_check_eq(url("", "searchbar"), "about:searchreset?purpose=searchbar"); + do_check_eq(url(""), "about:searchreset"); + do_check_eq(url("", ""), "about:searchreset"); + + // Calling the currentEngine setter for the same engine should + // prevent further prompts. + Services.search.currentEngine = Services.search.currentEngine; + do_check_eq(url("foo", "searchbar"), + "http://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&aq=t"); + + // And the loadPathHash should be back. + yield promiseAfterCache(); + let metadata = yield promiseEngineMetadata(); + do_check_true("loadPathHash" in metadata[kTestEngineShortName]); + let loadPathHash = metadata[kTestEngineShortName].loadPathHash; + do_check_eq(typeof loadPathHash, "string"); + do_check_eq(loadPathHash.length, 44); +}); + +add_task(function* test_whitelist() { + yield removeLoadPathHash(); + + // The default should still be the test engine. + let currentEngine = Services.search.currentEngine; + do_check_eq(currentEngine.name, kTestEngineName); + let expectPrompt = shouldPrompt => { + let expectedURL = + shouldPrompt ? "about:searchreset?data=foo&purpose=searchbar" + : "http://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&aq=t"; + let url = currentEngine.getSubmission("foo", null, "searchbar").uri.spec; + do_check_eq(url, expectedURL); + }; + expectPrompt(true); + + // Unless we whitelist our test engine. + let branch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF); + let initialWhiteList = branch.getCharPref(kWhiteListPrefName); + branch.setCharPref(kWhiteListPrefName, "example.com,test.tld"); + expectPrompt(true); + branch.setCharPref(kWhiteListPrefName, "www.google.com"); + expectPrompt(false); + branch.setCharPref(kWhiteListPrefName, "example.com,www.google.com,test.tld"); + expectPrompt(false); + + // The loadPathHash should not be back after the prompt was skipped due to the + // whitelist. + yield asyncReInit(); + let metadata = yield promiseEngineMetadata(); + do_check_false("loadPathHash" in metadata[kTestEngineShortName]); + + branch.setCharPref(kWhiteListPrefName, initialWhiteList); + expectPrompt(true); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_searchSuggest.js b/toolkit/components/search/tests/xpcshell/test_searchSuggest.js new file mode 100644 index 000000000..9de2967fc --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_searchSuggest.js @@ -0,0 +1,572 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Testing search suggestions from SearchSuggestionController.jsm. + */ + +"use strict"; + +Cu.import("resource://gre/modules/FormHistory.jsm"); +Cu.import("resource://gre/modules/SearchSuggestionController.jsm"); +Cu.import("resource://gre/modules/Timer.jsm"); + +// We must make sure the FormHistoryStartup component is +// initialized in order for it to respond to FormHistory +// requests from nsFormAutoComplete.js. +var formHistoryStartup = Cc["@mozilla.org/satchel/form-history-startup;1"]. + getService(Ci.nsIObserver); +formHistoryStartup.observe(null, "profile-after-change", null); + +var httpServer = new HttpServer(); +var getEngine, postEngine, unresolvableEngine; + +function run_test() { + Services.prefs.setBoolPref("browser.search.suggest.enabled", true); + + removeMetadata(); + updateAppInfo(); + + let server = useHttpServer(); + server.registerContentType("sjs", "sjs"); + + do_register_cleanup(() => Task.spawn(function* cleanup() { + // Remove added form history entries + yield updateSearchHistory("remove", null); + FormHistory.shutdown(); + Services.prefs.clearUserPref("browser.search.suggest.enabled"); + })); + + run_next_test(); +} + +add_task(function* add_test_engines() { + let getEngineData = { + baseURL: gDataUrl, + name: "GET suggestion engine", + method: "GET", + }; + + let postEngineData = { + baseURL: gDataUrl, + name: "POST suggestion engine", + method: "POST", + }; + + let unresolvableEngineData = { + baseURL: "http://example.invalid/", + name: "Offline suggestion engine", + method: "GET", + }; + + [getEngine, postEngine, unresolvableEngine] = yield addTestEngines([ + { + name: getEngineData.name, + xmlFileName: "engineMaker.sjs?" + JSON.stringify(getEngineData), + }, + { + name: postEngineData.name, + xmlFileName: "engineMaker.sjs?" + JSON.stringify(postEngineData), + }, + { + name: unresolvableEngineData.name, + xmlFileName: "engineMaker.sjs?" + JSON.stringify(unresolvableEngineData), + }, + ]); +}); + + +// Begin tests + +add_task(function* simple_no_result_callback() { + let deferred = Promise.defer(); + let controller = new SearchSuggestionController((result) => { + do_check_eq(result.term, "no remote"); + do_check_eq(result.local.length, 0); + do_check_eq(result.remote.length, 0); + deferred.resolve(); + }); + + controller.fetch("no remote", false, getEngine); + yield deferred.promise; +}); + +add_task(function* simple_no_result_callback_and_promise() { + // Make sure both the callback and promise get results + let deferred = Promise.defer(); + let controller = new SearchSuggestionController((result) => { + do_check_eq(result.term, "no results"); + do_check_eq(result.local.length, 0); + do_check_eq(result.remote.length, 0); + deferred.resolve(); + }); + + let result = yield controller.fetch("no results", false, getEngine); + do_check_eq(result.term, "no results"); + do_check_eq(result.local.length, 0); + do_check_eq(result.remote.length, 0); + + yield deferred.promise; +}); + +add_task(function* simple_no_result_promise() { + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("no remote", false, getEngine); + do_check_eq(result.term, "no remote"); + do_check_eq(result.local.length, 0); + do_check_eq(result.remote.length, 0); +}); + +add_task(function* simple_remote_no_local_result() { + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("mo", false, getEngine); + do_check_eq(result.term, "mo"); + do_check_eq(result.local.length, 0); + do_check_eq(result.remote.length, 3); + do_check_eq(result.remote[0], "Mozilla"); + do_check_eq(result.remote[1], "modern"); + do_check_eq(result.remote[2], "mom"); +}); + +add_task(function* remote_term_case_mismatch() { + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("Query Case Mismatch", false, getEngine); + do_check_eq(result.term, "Query Case Mismatch"); + do_check_eq(result.remote.length, 1); + do_check_eq(result.remote[0], "Query Case Mismatch"); +}); + +add_task(function* simple_local_no_remote_result() { + yield updateSearchHistory("bump", "no remote entries"); + + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("no remote", false, getEngine); + do_check_eq(result.term, "no remote"); + do_check_eq(result.local.length, 1); + do_check_eq(result.local[0], "no remote entries"); + do_check_eq(result.remote.length, 0); + + yield updateSearchHistory("remove", "no remote entries"); +}); + +add_task(function* simple_non_ascii() { + yield updateSearchHistory("bump", "I ❤️ XUL"); + + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("I ❤️", false, getEngine); + do_check_eq(result.term, "I ❤️"); + do_check_eq(result.local.length, 1); + do_check_eq(result.local[0], "I ❤️ XUL"); + do_check_eq(result.remote.length, 1); + do_check_eq(result.remote[0], "I ❤️ Mozilla"); +}); + +add_task(function* both_local_remote_result_dedupe() { + yield updateSearchHistory("bump", "Mozilla"); + + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("mo", false, getEngine); + do_check_eq(result.term, "mo"); + do_check_eq(result.local.length, 1); + do_check_eq(result.local[0], "Mozilla"); + do_check_eq(result.remote.length, 2); + do_check_eq(result.remote[0], "modern"); + do_check_eq(result.remote[1], "mom"); +}); + +add_task(function* POST_both_local_remote_result_dedupe() { + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("mo", false, postEngine); + do_check_eq(result.term, "mo"); + do_check_eq(result.local.length, 1); + do_check_eq(result.local[0], "Mozilla"); + do_check_eq(result.remote.length, 2); + do_check_eq(result.remote[0], "modern"); + do_check_eq(result.remote[1], "mom"); +}); + +add_task(function* both_local_remote_result_dedupe2() { + yield updateSearchHistory("bump", "mom"); + + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("mo", false, getEngine); + do_check_eq(result.term, "mo"); + do_check_eq(result.local.length, 2); + do_check_eq(result.local[0], "mom"); + do_check_eq(result.local[1], "Mozilla"); + do_check_eq(result.remote.length, 1); + do_check_eq(result.remote[0], "modern"); +}); + +add_task(function* both_local_remote_result_dedupe3() { + // All of the server entries also exist locally + yield updateSearchHistory("bump", "modern"); + + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("mo", false, getEngine); + do_check_eq(result.term, "mo"); + do_check_eq(result.local.length, 3); + do_check_eq(result.local[0], "modern"); + do_check_eq(result.local[1], "mom"); + do_check_eq(result.local[2], "Mozilla"); + do_check_eq(result.remote.length, 0); +}); + +add_task(function* fetch_twice_in_a_row() { + // Two entries since the first will match the first fetch but not the second. + yield updateSearchHistory("bump", "delay local"); + yield updateSearchHistory("bump", "delayed local"); + + let controller = new SearchSuggestionController(); + let resultPromise1 = controller.fetch("delay", false, getEngine); + + // A second fetch while the server is still waiting to return results leads to an abort. + let resultPromise2 = controller.fetch("delayed ", false, getEngine); + yield resultPromise1.then((results) => do_check_null(results)); + + let result = yield resultPromise2; + do_check_eq(result.term, "delayed "); + do_check_eq(result.local.length, 1); + do_check_eq(result.local[0], "delayed local"); + do_check_eq(result.remote.length, 1); + do_check_eq(result.remote[0], "delayed "); +}); + +add_task(function* fetch_twice_subset_reuse_formHistoryResult() { + // This tests if we mess up re-using the cached form history result. + // Two entries since the first will match the first fetch but not the second. + yield updateSearchHistory("bump", "delay local"); + yield updateSearchHistory("bump", "delayed local"); + + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("delay", false, getEngine); + do_check_eq(result.term, "delay"); + do_check_eq(result.local.length, 2); + do_check_eq(result.local[0], "delay local"); + do_check_eq(result.local[1], "delayed local"); + do_check_eq(result.remote.length, 1); + do_check_eq(result.remote[0], "delay"); + + // Remove the entry from the DB but it should remain in the cached formHistoryResult. + yield updateSearchHistory("remove", "delayed local"); + + let result2 = yield controller.fetch("delayed ", false, getEngine); + do_check_eq(result2.term, "delayed "); + do_check_eq(result2.local.length, 1); + do_check_eq(result2.local[0], "delayed local"); + do_check_eq(result2.remote.length, 1); + do_check_eq(result2.remote[0], "delayed "); +}); + +add_task(function* both_identical_with_more_than_max_results() { + // Add letters A through Z to form history which will match the server + for (let charCode = "A".charCodeAt(); charCode <= "Z".charCodeAt(); charCode++) { + yield updateSearchHistory("bump", "letter " + String.fromCharCode(charCode)); + } + + let controller = new SearchSuggestionController(); + controller.maxLocalResults = 7; + controller.maxRemoteResults = 10; + let result = yield controller.fetch("letter ", false, getEngine); + do_check_eq(result.term, "letter "); + do_check_eq(result.local.length, 7); + for (let i = 0; i < controller.maxLocalResults; i++) { + do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i)); + } + do_check_eq(result.local.length + result.remote.length, 10); + for (let i = 0; i < result.remote.length; i++) { + do_check_eq(result.remote[i], + "letter " + String.fromCharCode("A".charCodeAt() + controller.maxLocalResults + i)); + } +}); + +add_task(function* noremote_maxLocal() { + let controller = new SearchSuggestionController(); + controller.maxLocalResults = 2; // (should be ignored because no remote results) + controller.maxRemoteResults = 0; + let result = yield controller.fetch("letter ", false, getEngine); + do_check_eq(result.term, "letter "); + do_check_eq(result.local.length, 26); + for (let i = 0; i < result.local.length; i++) { + do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i)); + } + do_check_eq(result.remote.length, 0); +}); + +add_task(function* someremote_maxLocal() { + let controller = new SearchSuggestionController(); + controller.maxLocalResults = 2; + controller.maxRemoteResults = 4; + let result = yield controller.fetch("letter ", false, getEngine); + do_check_eq(result.term, "letter "); + do_check_eq(result.local.length, 2); + for (let i = 0; i < result.local.length; i++) { + do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i)); + } + do_check_eq(result.remote.length, 2); + // "A" and "B" will have been de-duped, start at C for remote results + for (let i = 0; i < result.remote.length; i++) { + do_check_eq(result.remote[i], "letter " + String.fromCharCode("C".charCodeAt() + i)); + } +}); + +add_task(function* one_of_each() { + let controller = new SearchSuggestionController(); + controller.maxLocalResults = 1; + controller.maxRemoteResults = 2; + let result = yield controller.fetch("letter ", false, getEngine); + do_check_eq(result.term, "letter "); + do_check_eq(result.local.length, 1); + do_check_eq(result.local[0], "letter A"); + do_check_eq(result.remote.length, 1); + do_check_eq(result.remote[0], "letter B"); +}); + +add_task(function* local_result_returned_remote_result_disabled() { + Services.prefs.setBoolPref("browser.search.suggest.enabled", false); + let controller = new SearchSuggestionController(); + controller.maxLocalResults = 1; + controller.maxRemoteResults = 1; + let result = yield controller.fetch("letter ", false, getEngine); + do_check_eq(result.term, "letter "); + do_check_eq(result.local.length, 26); + for (let i = 0; i < 26; i++) { + do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i)); + } + do_check_eq(result.remote.length, 0); + Services.prefs.setBoolPref("browser.search.suggest.enabled", true); +}); + +add_task(function* local_result_returned_remote_result_disabled_after_creation_of_controller() { + let controller = new SearchSuggestionController(); + controller.maxLocalResults = 1; + controller.maxRemoteResults = 1; + Services.prefs.setBoolPref("browser.search.suggest.enabled", false); + let result = yield controller.fetch("letter ", false, getEngine); + do_check_eq(result.term, "letter "); + do_check_eq(result.local.length, 26); + for (let i = 0; i < 26; i++) { + do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i)); + } + do_check_eq(result.remote.length, 0); + Services.prefs.setBoolPref("browser.search.suggest.enabled", true); +}); + +add_task(function* one_of_each_disabled_before_creation_enabled_after_creation_of_controller() { + Services.prefs.setBoolPref("browser.search.suggest.enabled", false); + let controller = new SearchSuggestionController(); + controller.maxLocalResults = 1; + controller.maxRemoteResults = 2; + Services.prefs.setBoolPref("browser.search.suggest.enabled", true); + let result = yield controller.fetch("letter ", false, getEngine); + do_check_eq(result.term, "letter "); + do_check_eq(result.local.length, 1); + do_check_eq(result.local[0], "letter A"); + do_check_eq(result.remote.length, 1); + do_check_eq(result.remote[0], "letter B"); +}); + +add_task(function* reset_suggestions_pref() { + Services.prefs.setBoolPref("browser.search.suggest.enabled", true); +}); + +add_task(function* one_local_zero_remote() { + let controller = new SearchSuggestionController(); + controller.maxLocalResults = 1; + controller.maxRemoteResults = 0; + let result = yield controller.fetch("letter ", false, getEngine); + do_check_eq(result.term, "letter "); + do_check_eq(result.local.length, 26); + for (let i = 0; i < 26; i++) { + do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i)); + } + do_check_eq(result.remote.length, 0); +}); + +add_task(function* zero_local_one_remote() { + let controller = new SearchSuggestionController(); + controller.maxLocalResults = 0; + controller.maxRemoteResults = 1; + let result = yield controller.fetch("letter ", false, getEngine); + do_check_eq(result.term, "letter "); + do_check_eq(result.local.length, 0); + do_check_eq(result.remote.length, 1); + do_check_eq(result.remote[0], "letter A"); +}); + +add_task(function* stop_search() { + let controller = new SearchSuggestionController((result) => { + do_throw("The callback shouldn't be called after stop()"); + }); + let resultPromise = controller.fetch("mo", false, getEngine); + controller.stop(); + yield resultPromise.then((result) => { + do_check_null(result); + }); +}); + +add_task(function* empty_searchTerm() { + // Empty searches don't go to the server but still get form history. + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("", false, getEngine); + do_check_eq(result.term, ""); + do_check_true(result.local.length > 0); + do_check_eq(result.remote.length, 0); +}); + +add_task(function* slow_timeout() { + let d = Promise.defer(); + function check_result(result) { + do_check_eq(result.term, "slow "); + do_check_eq(result.local.length, 1); + do_check_eq(result.local[0], "slow local result"); + do_check_eq(result.remote.length, 0); + } + yield updateSearchHistory("bump", "slow local result"); + + let controller = new SearchSuggestionController(); + setTimeout(function check_timeout() { + // The HTTP response takes 10 seconds so check that we already have results after 2 seconds. + check_result(result); + d.resolve(); + }, 2000); + let result = yield controller.fetch("slow ", false, getEngine); + check_result(result); + yield d.promise; +}); + +add_task(function* slow_stop() { + let d = Promise.defer(); + let controller = new SearchSuggestionController(); + let resultPromise = controller.fetch("slow ", false, getEngine); + setTimeout(function check_timeout() { + // The HTTP response takes 10 seconds but we timeout in less than a second so just use 0. + controller.stop(); + d.resolve(); + }, 0); + yield resultPromise.then((result) => { + do_check_null(result); + }); + + yield d.promise; +}); + + +// Error handling + +add_task(function* remote_term_mismatch() { + yield updateSearchHistory("bump", "Query Mismatch Entry"); + + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("Query Mismatch", false, getEngine); + do_check_eq(result.term, "Query Mismatch"); + do_check_eq(result.local.length, 1); + do_check_eq(result.local[0], "Query Mismatch Entry"); + do_check_eq(result.remote.length, 0); +}); + +add_task(function* http_404() { + yield updateSearchHistory("bump", "HTTP 404 Entry"); + + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("HTTP 404", false, getEngine); + do_check_eq(result.term, "HTTP 404"); + do_check_eq(result.local.length, 1); + do_check_eq(result.local[0], "HTTP 404 Entry"); + do_check_eq(result.remote.length, 0); +}); + +add_task(function* http_500() { + yield updateSearchHistory("bump", "HTTP 500 Entry"); + + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("HTTP 500", false, getEngine); + do_check_eq(result.term, "HTTP 500"); + do_check_eq(result.local.length, 1); + do_check_eq(result.local[0], "HTTP 500 Entry"); + do_check_eq(result.remote.length, 0); +}); + +add_task(function* unresolvable_server() { + yield updateSearchHistory("bump", "Unresolvable Server Entry"); + + let controller = new SearchSuggestionController(); + let result = yield controller.fetch("Unresolvable Server", false, unresolvableEngine); + do_check_eq(result.term, "Unresolvable Server"); + do_check_eq(result.local.length, 1); + do_check_eq(result.local[0], "Unresolvable Server Entry"); + do_check_eq(result.remote.length, 0); +}); + + +// Exception handling + +add_task(function* missing_pb() { + Assert.throws(() => { + let controller = new SearchSuggestionController(); + controller.fetch("No privacy"); + }, /priva/i); +}); + +add_task(function* missing_engine() { + Assert.throws(() => { + let controller = new SearchSuggestionController(); + controller.fetch("No engine", false); + }, /engine/i); +}); + +add_task(function* invalid_engine() { + Assert.throws(() => { + let controller = new SearchSuggestionController(); + controller.fetch("invalid engine", false, {}); + }, /engine/i); +}); + +add_task(function* no_results_requested() { + Assert.throws(() => { + let controller = new SearchSuggestionController(); + controller.maxLocalResults = 0; + controller.maxRemoteResults = 0; + controller.fetch("No results requested", false, getEngine); + }, /result/i); +}); + +add_task(function* minus_one_results_requested() { + Assert.throws(() => { + let controller = new SearchSuggestionController(); + controller.maxLocalResults = -1; + controller.fetch("-1 results requested", false, getEngine); + }, /result/i); +}); + +add_task(function* test_userContextId() { + let controller = new SearchSuggestionController(); + controller._fetchRemote = function(searchTerm, engine, privateMode, userContextId) { + Assert.equal(userContextId, 1); + return Promise.defer(); + }; + + controller.fetch("test", false, getEngine, 1); +}); + +// Helpers + +function updateSearchHistory(operation, value) { + let deferred = Promise.defer(); + FormHistory.update({ + op: operation, + fieldname: "searchbar-history", + value: value, + }, + { + handleError: function (error) { + do_throw("Error occurred updating form history: " + error); + deferred.reject(error); + }, + handleCompletion: function (reason) { + if (!reason) + deferred.resolve(); + } + }); + return deferred.promise; +} diff --git a/toolkit/components/search/tests/xpcshell/test_selectedEngine.js b/toolkit/components/search/tests/xpcshell/test_selectedEngine.js new file mode 100644 index 000000000..a1c0f363e --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_selectedEngine.js @@ -0,0 +1,165 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Components.utils.import("resource://gre/modules/osfile.jsm"); + +const kSelectedEnginePref = "browser.search.selectedEngine"; + +// Check that the default engine matches the defaultenginename pref +add_task(function* test_defaultEngine() { + yield asyncInit(); + + do_check_eq(Services.search.currentEngine.name, getDefaultEngineName()); +}); + +// Giving prefs a user value shouldn't change the selected engine. +add_task(function* test_selectedEngine() { + let defaultEngineName = getDefaultEngineName(); + // Test the selectedEngine pref. + Services.prefs.setCharPref(kSelectedEnginePref, kTestEngineName); + + yield asyncReInit(); + do_check_eq(Services.search.currentEngine.name, defaultEngineName); + + Services.prefs.clearUserPref(kSelectedEnginePref); + + // Test the defaultenginename pref. + Services.prefs.setCharPref(kDefaultenginenamePref, kTestEngineName); + + yield asyncReInit(); + do_check_eq(Services.search.currentEngine.name, defaultEngineName); + + Services.prefs.clearUserPref(kDefaultenginenamePref); +}); + +// Setting the search engine should be persisted across restarts. +add_task(function* test_persistAcrossRestarts() { + // Set the engine through the API. + Services.search.currentEngine = Services.search.getEngineByName(kTestEngineName); + do_check_eq(Services.search.currentEngine.name, kTestEngineName); + yield promiseAfterCache(); + + // Check that the a hash was saved. + let metadata = yield promiseGlobalMetadata(); + do_check_eq(metadata.hash.length, 44); + + // Re-init and check the engine is still the same. + yield asyncReInit(); + do_check_eq(Services.search.currentEngine.name, kTestEngineName); + + // Cleanup (set the engine back to default). + Services.search.resetToOriginalDefaultEngine(); + do_check_eq(Services.search.currentEngine.name, getDefaultEngineName()); +}); + +// An engine set without a valid hash should be ignored. +add_task(function* test_ignoreInvalidHash() { + // Set the engine through the API. + Services.search.currentEngine = Services.search.getEngineByName(kTestEngineName); + do_check_eq(Services.search.currentEngine.name, kTestEngineName); + yield promiseAfterCache(); + + // Then mess with the file (make the hash invalid). + let metadata = yield promiseGlobalMetadata(); + metadata.hash = "invalid"; + yield promiseSaveGlobalMetadata(metadata); + + // Re-init the search service, and check that the json file is ignored. + yield asyncReInit(); + do_check_eq(Services.search.currentEngine.name, getDefaultEngineName()); +}); + +// Resetting the engine to the default should remove the saved value. +add_task(function* test_settingToDefault() { + // Set the engine through the API. + Services.search.currentEngine = Services.search.getEngineByName(kTestEngineName); + do_check_eq(Services.search.currentEngine.name, kTestEngineName); + yield promiseAfterCache(); + + // Check that the current engine was saved. + let metadata = yield promiseGlobalMetadata(); + do_check_eq(metadata.current, kTestEngineName); + + // Then set the engine back to the default through the API. + Services.search.currentEngine = + Services.search.getEngineByName(getDefaultEngineName()); + yield promiseAfterCache(); + + // Check that the current engine is no longer saved in the JSON file. + metadata = yield promiseGlobalMetadata(); + do_check_eq(metadata.current, ""); +}); + +add_task(function* test_resetToOriginalDefaultEngine() { + let defaultName = getDefaultEngineName(); + do_check_eq(Services.search.currentEngine.name, defaultName); + + Services.search.currentEngine = + Services.search.getEngineByName(kTestEngineName); + do_check_eq(Services.search.currentEngine.name, kTestEngineName); + yield promiseAfterCache(); + + Services.search.resetToOriginalDefaultEngine(); + do_check_eq(Services.search.currentEngine.name, defaultName); + yield promiseAfterCache(); +}); + +add_task(function* test_fallback_kept_after_restart() { + // Set current engine to a default engine that isn't the original default. + let builtInEngines = Services.search.getDefaultEngines(); + let defaultName = getDefaultEngineName(); + let nonDefaultBuiltInEngine; + for (let engine of builtInEngines) { + if (engine.name != defaultName) { + nonDefaultBuiltInEngine = engine; + break; + } + } + Services.search.currentEngine = nonDefaultBuiltInEngine; + do_check_eq(Services.search.currentEngine.name, nonDefaultBuiltInEngine.name); + yield promiseAfterCache(); + + // Remove that engine... + Services.search.removeEngine(nonDefaultBuiltInEngine); + // The engine being a default (built-in) one, it should be hidden + // rather than actually removed. + do_check_true(nonDefaultBuiltInEngine.hidden); + + // Using the currentEngine getter should force a fallback to the + // original default engine. + do_check_eq(Services.search.currentEngine.name, defaultName); + + // Restoring the default engines should unhide our built-in test + // engine, but not change the value of currentEngine. + Services.search.restoreDefaultEngines(); + do_check_false(nonDefaultBuiltInEngine.hidden); + do_check_eq(Services.search.currentEngine.name, defaultName); + yield promiseAfterCache(); + + // After a restart, the currentEngine value should still be unchanged. + yield asyncReInit(); + do_check_eq(Services.search.currentEngine.name, defaultName); +}); + + +function run_test() { + removeMetadata(); + removeCacheFile(); + + do_check_false(Services.search.isInitialized); + + let engineDummyFile = gProfD.clone(); + engineDummyFile.append("searchplugins"); + engineDummyFile.append("test-search-engine.xml"); + let engineDir = engineDummyFile.parent; + engineDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + + do_get_file("data/engine.xml").copyTo(engineDir, "engine.xml"); + + do_register_cleanup(function() { + removeMetadata(); + removeCacheFile(); + }); + + run_next_test(); +} diff --git a/toolkit/components/search/tests/xpcshell/test_svg_icon.js b/toolkit/components/search/tests/xpcshell/test_svg_icon.js new file mode 100644 index 000000000..5fd4781a1 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_svg_icon.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var url; +var requestHandled; + +const icon = + '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' + + '<svg xmlns="http://www.w3.org/2000/svg" ' + + 'width="16" height="16" viewBox="0 0 16 16">' + + '<rect x="4" y="4" width="8px" height="8px" style="fill: blue"/>' + + '</svg>'; + +function run_test() { + updateAppInfo(); + useHttpServer(); // Unused, but required to call addTestEngines. + + requestHandled = new Promise(resolve => { + let srv = new HttpServer(); + srv.registerPathHandler("/icon.svg", (metadata, response) => { + response.setStatusLine("1.0", 200, "OK"); + response.setHeader("Content-Type", "image/svg+xml", false); + + response.write(icon); + resolve(); + }); + srv.start(-1); + do_register_cleanup(() => srv.stop(() => {})); + + url = "http://localhost:" + srv.identity.primaryPort + "/icon.svg"; + }); + + run_next_test(); +} + +add_task(function* test_svg_icon() { + yield asyncInit(); + + let [engine] = yield addTestEngines([ + { name: "SVGIcon", details: [url, "", "SVG icon", "GET", + "http://icon.svg/search?q={searchTerms}"] }, + ]); + + yield requestHandled; + yield promiseAfterCache(); + + ok(engine.iconURI, "the engine has an icon"); + ok(engine.iconURI.spec.startsWith("data:image/svg+xml"), + "the icon is saved as an SVG data url"); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_sync.js b/toolkit/components/search/tests/xpcshell/test_sync.js new file mode 100644 index 000000000..8f4eb22ee --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_sync.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + + do_check_false(Services.search.isInitialized); + + // test engines from dir are not loaded. + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + do_check_true(Services.search.isInitialized); + + // test jar engine is loaded ok. + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + // Check the hidden engine is not loaded. + engine = Services.search.getEngineByName("hidden"); + do_check_eq(engine, null); +} diff --git a/toolkit/components/search/tests/xpcshell/test_sync_addon.js b/toolkit/components/search/tests/xpcshell/test_sync_addon.js new file mode 100644 index 000000000..7af957506 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_sync_addon.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + installAddonEngine(); + + do_check_false(Services.search.isInitialized); + + // test the add-on engine is loaded in addition to our jar engine + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 2); + + do_check_true(Services.search.isInitialized); + + // test jar engine is loaded ok. + let engine = Services.search.getEngineByName("addon"); + do_check_neq(engine, null); + + do_check_eq(engine.description, "addon"); +} diff --git a/toolkit/components/search/tests/xpcshell/test_sync_addon_no_override.js b/toolkit/components/search/tests/xpcshell/test_sync_addon_no_override.js new file mode 100644 index 000000000..3f4494905 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_sync_addon_no_override.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + installAddonEngine("engine-override"); + + do_check_false(Services.search.isInitialized); + + // test the add-on engine isn't overriding our jar engine + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + do_check_true(Services.search.isInitialized); + + // test jar engine is loaded ok. + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + do_check_eq(engine.description, "bug645970"); +} diff --git a/toolkit/components/search/tests/xpcshell/test_sync_delay_fallback.js b/toolkit/components/search/tests/xpcshell/test_sync_delay_fallback.js new file mode 100644 index 000000000..1b41a71bf --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_sync_delay_fallback.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + do_test_pending(); + + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + + do_check_false(Services.search.isInitialized); + let fallback = false; + + Services.search.init(function search_initialized(aStatus) { + do_check_true(fallback); + do_check_true(Components.isSuccessCode(aStatus)); + do_check_true(Services.search.isInitialized); + + // test engines from dir are not loaded. + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + // test jar engine is loaded ok. + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + do_test_finished(); + }); + + // Execute test for the sync fallback while the async code is being executed. + Services.obs.addObserver(function searchServiceObserver(aResult, aTopic, aVerb) { + if (aVerb == "find-jar-engines") { + Services.obs.removeObserver(searchServiceObserver, aTopic); + fallback = true; + + do_check_false(Services.search.isInitialized); + + // test engines from dir are not loaded. + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + // test jar engine is loaded ok. + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + do_check_true(Services.search.isInitialized); + } + }, "browser-search-service", false); +} diff --git a/toolkit/components/search/tests/xpcshell/test_sync_distribution.js b/toolkit/components/search/tests/xpcshell/test_sync_distribution.js new file mode 100644 index 000000000..63a8b66f0 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_sync_distribution.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + installDistributionEngine(); + + do_check_false(Services.search.isInitialized); + + // test that the engine from the distribution overrides our jar engine + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + do_check_true(Services.search.isInitialized); + + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + // check the engine we have is actually the one from the distribution + do_check_eq(engine.description, "override"); +} diff --git a/toolkit/components/search/tests/xpcshell/test_sync_fallback.js b/toolkit/components/search/tests/xpcshell/test_sync_fallback.js new file mode 100644 index 000000000..dad73fabc --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_sync_fallback.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + do_test_pending(); + + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + + do_check_false(Services.search.isInitialized); + + Services.search.init(function search_initialized(aStatus) { + do_check_true(Components.isSuccessCode(aStatus)); + do_check_true(Services.search.isInitialized); + + // test engines from dir are not loaded. + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + // test jar engine is loaded ok. + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + do_test_finished(); + }); + + do_check_false(Services.search.isInitialized); + + // test engines from dir are not loaded. + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + do_check_true(Services.search.isInitialized); + + // test jar engine is loaded ok. + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); +} diff --git a/toolkit/components/search/tests/xpcshell/test_sync_migration.js b/toolkit/components/search/tests/xpcshell/test_sync_migration.js new file mode 100644 index 000000000..53e945dfd --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_sync_migration.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Test that legacy metadata from search-metadata.json is correctly + * transferred to the new metadata storage. */ + +function run_test() { + updateAppInfo(); + installTestEngine(); + + do_get_file("data/metadata.json").copyTo(gProfD, "search-metadata.json"); + + run_next_test(); +} + +add_task(function* test_sync_metadata_migration() { + do_check_false(Services.search.isInitialized); + Services.search.getEngines(); + do_check_true(Services.search.isInitialized); + yield promiseAfterCache(); + + // Check that the entries are placed as specified correctly + let metadata = yield promiseEngineMetadata(); + do_check_eq(metadata["engine"].order, 1); + do_check_eq(metadata["engine"].alias, "foo"); + + metadata = yield promiseGlobalMetadata(); + do_check_eq(metadata["searchDefaultExpir"], 1471013469846); +}); diff --git a/toolkit/components/search/tests/xpcshell/test_sync_profile_engine.js b/toolkit/components/search/tests/xpcshell/test_sync_profile_engine.js new file mode 100644 index 000000000..f8d38e571 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_sync_profile_engine.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const NS_APP_USER_SEARCH_DIR = "UsrSrchPlugns"; + +function run_test() { + removeMetadata(); + removeCacheFile(); + + do_load_manifest("data/chrome.manifest"); + + configureToLoadJarEngines(); + + // Copy an engine in [profile]/searchplugin/ and ensure it's not + // overriding the same file from a jar. + // The description in the file we are copying is 'profile'. + let dir = Services.dirsvc.get(NS_APP_USER_SEARCH_DIR, Ci.nsIFile); + if (!dir.exists()) + dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + do_get_file("data/engine-override.xml").copyTo(dir, "bug645970.xml"); + + do_check_false(Services.search.isInitialized); + + // test engines from dir are not loaded. + let engines = Services.search.getEngines(); + do_check_eq(engines.length, 1); + + do_check_true(Services.search.isInitialized); + + // test jar engine is loaded ok. + let engine = Services.search.getEngineByName("bug645970"); + do_check_neq(engine, null); + + do_check_eq(engine.description, "bug645970"); +} diff --git a/toolkit/components/search/tests/xpcshell/test_update_telemetry.js b/toolkit/components/search/tests/xpcshell/test_update_telemetry.js new file mode 100644 index 000000000..f73e765c6 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/test_update_telemetry.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + do_check_false(Services.search.isInitialized); + + useHttpServer(); + run_next_test(); +} + +function checkTelemetry(histogramName, expected) { + let histogram = Services.telemetry.getHistogramById(histogramName); + let snapshot = histogram.snapshot(); + let expectedCounts = [0, 0, 0]; + expectedCounts[expected ? 1 : 0] = 1; + Assert.deepEqual(snapshot.counts, expectedCounts, + "histogram has expected content"); + histogram.clear(); +} + +add_task(function* ignore_cache_files_without_engines() { + yield asyncInit(); + + checkTelemetry("SEARCH_SERVICE_HAS_UPDATES", false); + checkTelemetry("SEARCH_SERVICE_HAS_ICON_UPDATES", false); + + // Add an engine with update urls and re-init, as we record the presence of + // engine update urls only while initializing the search service. + yield addTestEngines([ + { name: "update", xmlFileName: "engine-update.xml" }, + ]); + yield asyncReInit(); + + checkTelemetry("SEARCH_SERVICE_HAS_UPDATES", true); + checkTelemetry("SEARCH_SERVICE_HAS_ICON_UPDATES", true); +}); diff --git a/toolkit/components/search/tests/xpcshell/xpcshell.ini b/toolkit/components/search/tests/xpcshell/xpcshell.ini new file mode 100644 index 000000000..1fb5a3423 --- /dev/null +++ b/toolkit/components/search/tests/xpcshell/xpcshell.ini @@ -0,0 +1,102 @@ +[DEFAULT] +head = head_search.js +tail = +firefox-appdir = browser +skip-if = toolkit == 'android' +support-files = + data/chrome.manifest + data/engine.xml + data/engine2.xml + data/engine-addon.xml + data/engine-override.xml + data/engine-app.xml + data/engine-fr.xml + data/engineMaker.sjs + data/engine-pref.xml + data/engine-rel-searchform.xml + data/engine-rel-searchform-post.xml + data/engine-rel-searchform-purpose.xml + data/engine-system-purpose.xml + data/engine-update.xml + data/engineImages.xml + data/engine-chromeicon.xml + data/engine-resourceicon.xml + data/ico-size-16x16-png.ico + data/invalid-engine.xml + data/install.rdf + data/list.json + data/langpack-metadata.json + data/metadata.json + data/search.json + data/search.sqlite + data/searchSuggestions.sjs + data/searchTest.jar + +[test_nocache.js] +[test_645970.js] +[test_bug930456.js] +[test_bug930456_child.js] +[test_engine_set_alias.js] +[test_hasEngineWithURL.js] +[test_identifiers.js] +[test_invalid_engine_from_dir.js] +[test_init_async_multiple.js] +[test_init_async_multiple_then_sync.js] +[test_json_cache.js] +[test_location.js] +[test_location_error.js] +[test_location_malformed_json.js] +[test_location_migrate_countrycode_isUS.js] +[test_location_migrate_no_countrycode_isUS.js] +[test_location_migrate_no_countrycode_notUS.js] +[test_location_partner.js] +[test_location_funnelcake.js] +[test_location_sync.js] +[test_location_timeout.js] +[test_location_timeout_xhr.js] +[test_nodb.js] +[test_nodb_pluschanges.js] +[test_save_sorted_engines.js] +[test_pref.js] +[test_purpose.js] +[test_defaultEngine.js] +[test_notifications.js] +[test_parseSubmissionURL.js] +[test_SearchStaticData.js] +[test_addEngine_callback.js] +[test_migration_langpack.js] +[test_multipleIcons.js] +[test_resultDomain.js] +[test_searchSuggest.js] +[test_async.js] +[test_async_addon.js] +tags = addons +[test_async_addon_no_override.js] +tags = addons +[test_async_distribution.js] +[test_async_migration.js] +[test_async_profile_engine.js] +[test_sync.js] +[test_sync_addon.js] +tags = addons +[test_sync_addon_no_override.js] +tags = addons +[test_sync_distribution.js] +[test_sync_fallback.js] +[test_sync_delay_fallback.js] +[test_sync_migration.js] +[test_sync_profile_engine.js] +[test_rel_searchform.js] +[test_remove_profile_engine.js] +[test_selectedEngine.js] +[test_geodefaults.js] +[test_hidden.js] +[test_currentEngine_fallback.js] +[test_require_engines_in_cache.js] +[test_update_telemetry.js] +[test_svg_icon.js] +[test_searchReset.js] +[test_addEngineWithDetails.js] +[test_chromeresource_icon1.js] +[test_chromeresource_icon2.js] +[test_engineUpdate.js] |