1 # ***** BEGIN LICENSE BLOCK
*****
2 # Version
: MPL
1.1/GPL 2.0/LGPL
2.1
4 # The contents
of this file are subject to the Mozilla Public License Version
5 # 1.1 (the
"License"); you may not
use this file except
in compliance
with
6 # the License
. You may obtain a copy
of the License at
7 # http
://www.mozilla.org/MPL/
9 # Software distributed under the License is distributed on an
"AS IS" basis
,
10 # WITHOUT WARRANTY OF ANY KIND
, either express or implied
. See the License
11 # for the specific language governing rights and limitations under the
14 # The Original Code is the Browser Search Service
.
16 # The Initial Developer
of the Original Code is
18 # Portions created by the Initial Developer are
Copyright (C
) 2005-2006
19 # the Initial Developer
. All Rights Reserved
.
22 # Ben Goodger
<beng
@google
.com
> (Original author
)
23 # Gavin Sharp
<gavin
@gavinsharp
.com
>
24 # Joe Hughes
<joe
@retrovirus
.com
>
25 # Pamela Greene
<pamg
.bugs
@gmail
.com
>
27 # Alternatively
, the contents
of this file may be used under the terms
of
28 # either the GNU General Public License Version
2 or
later (the
"GPL"), or
29 # the GNU Lesser General Public License Version
2.1 or
later (the
"LGPL"),
30 # in which
case the provisions
of the GPL or the LGPL are applicable instead
31 # of those above
. If you wish to allow
use of your version
of this file only
32 # under the terms
of either the GPL or the LGPL
, and not to allow others to
33 # use your version
of this file under the terms
of the MPL
, indicate your
34 # decision by deleting the provisions above and replace them
with the notice
35 # and other provisions required by the GPL or the LGPL
. If you
do not
delete
36 # the provisions above
, a recipient may
use your version
of this file under
37 # the terms
of any one
of the MPL
, the GPL or the LGPL
.
39 # ***** END LICENSE BLOCK
*****
41 const Ci
= Components
.interfaces
;
42 const Cc
= Components
.classes
;
43 const Cr
= Components
.results
;
45 const PERMS_FILE
= 0644;
46 const PERMS_DIRECTORY
= 0755;
48 const MODE_RDONLY
= 0x01;
49 const MODE_WRONLY
= 0x02;
50 const MODE_CREATE
= 0x08;
51 const MODE_APPEND
= 0x10;
52 const MODE_TRUNCATE
= 0x20;
54 // Directory service keys
55 const NS_APP_SEARCH_DIR_LIST
= "SrchPluginsDL";
56 const NS_APP_USER_SEARCH_DIR
= "UsrSrchPlugns";
57 const NS_APP_SEARCH_DIR
= "SrchPlugns";
58 const NS_APP_USER_PROFILE_50_DIR
= "ProfD";
60 // Search engine "locations". If this list is changed, be sure to update
61 // the engine's _isDefault function accordingly.
62 const SEARCH_APP_DIR
= 1;
63 const SEARCH_PROFILE_DIR
= 2;
64 const SEARCH_IN_EXTENSION
= 3;
66 // See documentation in nsIBrowserSearchService.idl.
67 const SEARCH_ENGINE_TOPIC
= "browser-search-engine-modified";
68 const QUIT_APPLICATION_TOPIC
= "quit-application";
70 const SEARCH_ENGINE_REMOVED
= "engine-removed";
71 const SEARCH_ENGINE_ADDED
= "engine-added";
72 const SEARCH_ENGINE_CHANGED
= "engine-changed";
73 const SEARCH_ENGINE_LOADED
= "engine-loaded";
74 const SEARCH_ENGINE_CURRENT
= "engine-current";
76 const SEARCH_TYPE_MOZSEARCH
= Ci
.nsISearchEngine
.TYPE_MOZSEARCH
;
77 const SEARCH_TYPE_OPENSEARCH
= Ci
.nsISearchEngine
.TYPE_OPENSEARCH
;
78 const SEARCH_TYPE_SHERLOCK
= Ci
.nsISearchEngine
.TYPE_SHERLOCK
;
80 const SEARCH_DATA_XML
= Ci
.nsISearchEngine
.DATA_XML
;
81 const SEARCH_DATA_TEXT
= Ci
.nsISearchEngine
.DATA_TEXT
;
83 // File extensions for search plugin description files
84 const XML_FILE_EXT
= "xml";
85 const SHERLOCK_FILE_EXT
= "src";
87 // Delay for lazy serialization (ms)
88 const LAZY_SERIALIZE_DELAY
= 100;
90 const ICON_DATAURL_PREFIX
= "data:image/x-icon;base64,";
92 // Supported extensions for Sherlock plugin icons
93 const SHERLOCK_ICON_EXTENSIONS
= [".gif", ".png", ".jpg", ".jpeg"];
95 const NEW_LINES
= /(\r\n|\r|\n)/;
97 // Set an arbitrary cap on the maximum icon size. Without this, large icons can
98 // cause big delays when loading them at startup.
99 const MAX_ICON_SIZE
= 10000;
101 // Default charset to use for sending search parameters. ISO-8859-1 is used to
102 // match previous nsInternetSearchService behavior.
103 const DEFAULT_QUERY_CHARSET
= "ISO-8859-1";
105 const SEARCH_BUNDLE
= "chrome://browser/locale/search.properties";
106 const BRAND_BUNDLE
= "chrome://branding/locale/brand.properties";
108 const OPENSEARCH_NS_10
= "http://a9.com/-/spec/opensearch/1.0/";
109 const OPENSEARCH_NS_11
= "http://a9.com/-/spec/opensearch/1.1/";
111 // Although the specification at http://opensearch.a9.com/spec/1.1/description/
112 // gives the namespace names defined above, many existing OpenSearch engines
113 // are using the following versions. We therefore allow either.
114 const OPENSEARCH_NAMESPACES
= [
115 OPENSEARCH_NS_11
, OPENSEARCH_NS_10
,
116 "http://a9.com/-/spec/opensearchdescription/1.1/",
117 "http://a9.com/-/spec/opensearchdescription/1.0/"
120 const OPENSEARCH_LOCALNAME
= "OpenSearchDescription";
122 const MOZSEARCH_NS_10
= "http://www.mozilla.org/2006/browser/search/";
123 const MOZSEARCH_LOCALNAME
= "SearchPlugin";
125 const URLTYPE_SUGGEST_JSON
= "application/x-suggestions+json";
126 const URLTYPE_SEARCH_HTML
= "text/html";
128 // Empty base document used to serialize engines to file.
129 const EMPTY_DOC
= "<?xml version=\"1.0\"?>\n" +
130 "<" + MOZSEARCH_LOCALNAME
+
131 " xmlns=\"" + MOZSEARCH_NS_10
+ "\"" +
132 " xmlns:os=\"" + OPENSEARCH_NS_11
+ "\"" +
135 const BROWSER_SEARCH_PREF
= "browser.search.";
137 const USER_DEFINED
= "{searchTerms}";
139 // Custom search parameters
140 #ifdef OFFICIAL_BUILD
141 const MOZ_OFFICIAL
= "official";
143 const MOZ_OFFICIAL
= "unofficial";
145 #expand
const MOZ_DISTRIBUTION_ID
= __MOZ_DISTRIBUTION_ID__
;
147 const MOZ_PARAM_LOCALE
= /\{moz:locale\}/g;
148 const MOZ_PARAM_DIST_ID
= /\{moz:distributionID\}/g;
149 const MOZ_PARAM_OFFICIAL
= /\{moz:official\}/g;
151 // Supported OpenSearch parameters
152 // See http://opensearch.a9.com/spec/1.1/querysyntax/#core
153 const OS_PARAM_USER_DEFINED
= /\{searchTerms\??\}/g;
154 const OS_PARAM_INPUT_ENCODING
= /\{inputEncoding\??\}/g;
155 const OS_PARAM_LANGUAGE
= /\{language\??\}/g;
156 const OS_PARAM_OUTPUT_ENCODING
= /\{outputEncoding\??\}/g;
159 const OS_PARAM_LANGUAGE_DEF
= "*";
160 const OS_PARAM_OUTPUT_ENCODING_DEF
= "UTF-8";
161 const OS_PARAM_INPUT_ENCODING_DEF
= "UTF-8";
163 // "Unsupported" OpenSearch parameters. For example, we don't support
164 // page-based results, so if the engine requires that we send the "page index"
165 // parameter, we'll always send "1".
166 const OS_PARAM_COUNT
= /\{count\??\}/g;
167 const OS_PARAM_START_INDEX
= /\{startIndex\??\}/g;
168 const OS_PARAM_START_PAGE
= /\{startPage\??\}/g;
171 const OS_PARAM_COUNT_DEF
= "20"; // 20 results
172 const OS_PARAM_START_INDEX_DEF
= "1"; // start at 1st result
173 const OS_PARAM_START_PAGE_DEF
= "1"; // 1st page
175 // Optional parameter
176 const OS_PARAM_OPTIONAL
= /\{(?:\w+:)?\w+\?\}/g;
178 // A array of arrays containing parameters that we don't fully support, and
179 // their default values. We will only send values for these parameters if
180 // required, since our values are just really arbitrary "guesses" that should
181 // give us the output we want.
182 var OS_UNSUPPORTED_PARAMS
= [
183 [OS_PARAM_COUNT
, OS_PARAM_COUNT_DEF
],
184 [OS_PARAM_START_INDEX
, OS_PARAM_START_INDEX_DEF
],
185 [OS_PARAM_START_PAGE
, OS_PARAM_START_PAGE_DEF
],
188 // The default engine update interval, in days. This is only used if an engine
189 // specifies an updateURL, but not an updateInterval.
190 const SEARCH_DEFAULT_UPDATE_INTERVAL
= 7;
192 // Returns false for whitespace-only or commented out lines in a
193 // Sherlock file, true otherwise.
194 function isUsefulLine(aLine
) {
195 return !(/^\s*($|#)/i.test(aLine
));
199 * Prefixed to all search debug output.
201 const SEARCH_LOG_PREFIX
= "*** Search: ";
204 * Outputs aText to the JavaScript console as well as to stdout.
206 function DO_LOG(aText
) {
207 dump(SEARCH_LOG_PREFIX
+ aText
+ "\n");
208 var consoleService
= Cc
["@mozilla.org/consoleservice;1"].
209 getService(Ci
.nsIConsoleService
);
210 consoleService
.logStringMessage(aText
);
215 * In debug builds, use a live, pref-based (browser.search.log) LOG function
216 * to allow enabling/disabling without a restart.
218 function PREF_LOG(aText
) {
219 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
220 getService(Ci
.nsIPrefBranch
);
221 var shouldLog
= false;
223 shouldLog
= prefB
.getBoolPref(BROWSER_SEARCH_PREF
+ "log");
235 * Otherwise, don't log at all by default. This can be overridden at startup
236 * by the pref, see SearchService's _init method.
238 var LOG = function(){};
242 function ERROR(message
, resultCode
) {
243 NS_ASSERT(false, SEARCH_LOG_PREFIX
+ message
);
248 * Ensures an assertion is met before continuing. Should be used to indicate
251 * An assertion that must be met
253 * A message to display if the assertion is not met
255 * The NS_ERROR_* value to throw if the assertion is not met
258 function ENSURE_WARN(assertion
, message
, resultCode
) {
259 NS_ASSERT(assertion
, SEARCH_LOG_PREFIX
+ message
);
265 * Ensures an assertion is met before continuing, but does not warn the user.
266 * Used to handle normal failure conditions.
268 * An assertion that must be met
270 * A message to display if the assertion is not met
272 * The NS_ERROR_* value to throw if the assertion is not met
275 function ENSURE(assertion
, message
, resultCode
) {
283 * Ensures an argument assertion is met before continuing.
285 * An argument assertion that must be met
287 * A message to display if the assertion is not met
288 * @throws NS_ERROR_INVALID_ARG for invalid arguments
290 function ENSURE_ARG(assertion
, message
) {
291 ENSURE(assertion
, message
, Cr
.NS_ERROR_INVALID_ARG
);
294 function loadListener(aChannel
, aEngine
, aCallback
) {
295 this._channel
= aChannel
;
297 this._engine
= aEngine
;
298 this._callback
= aCallback
;
300 loadListener
.prototype = {
307 QueryInterface
: function SRCH_loadQI(aIID
) {
308 if (aIID
.equals(Ci
.nsISupports
) ||
309 aIID
.equals(Ci
.nsIRequestObserver
) ||
310 aIID
.equals(Ci
.nsIStreamListener
) ||
311 aIID
.equals(Ci
.nsIChannelEventSink
) ||
312 aIID
.equals(Ci
.nsIInterfaceRequestor
) ||
313 aIID
.equals(Ci
.nsIBadCertListener2
) ||
314 aIID
.equals(Ci
.nsISSLErrorListener
) ||
315 // See FIXME comment below
316 aIID
.equals(Ci
.nsIHttpEventSink
) ||
317 aIID
.equals(Ci
.nsIProgressEventSink
) ||
321 throw Cr
.NS_ERROR_NO_INTERFACE
;
324 // nsIRequestObserver
325 onStartRequest
: function SRCH_loadStartR(aRequest
, aContext
) {
326 LOG("loadListener: Starting request: " + aRequest
.name
);
327 this._stream
= Cc
["@mozilla.org/binaryinputstream;1"].
328 createInstance(Ci
.nsIBinaryInputStream
);
331 onStopRequest
: function SRCH_loadStopR(aRequest
, aContext
, aStatusCode
) {
332 LOG("loadListener: Stopping request: " + aRequest
.name
);
334 var requestFailed
= !Components
.isSuccessCode(aStatusCode
);
335 if (!requestFailed
&& (aRequest
instanceof Ci
.nsIHttpChannel
))
336 requestFailed
= !aRequest
.requestSucceeded
;
338 if (requestFailed
|| this._countRead
== 0) {
339 LOG("loadListener: request failed!");
340 // send null so the callback can deal with the failure
341 this._callback(null, this._engine
);
343 this._callback(this._bytes
, this._engine
);
344 this._channel
= null;
349 onDataAvailable
: function SRCH_loadDAvailable(aRequest
, aContext
,
350 aInputStream
, aOffset
,
352 this._stream
.setInputStream(aInputStream
);
354 // Get a byte array of the data
355 this._bytes
= this._bytes
.concat(this._stream
.readByteArray(aCount
));
356 this._countRead
+= aCount
;
359 // nsIChannelEventSink
360 onChannelRedirect
: function SRCH_loadCRedirect(aOldChannel
, aNewChannel
,
362 this._channel
= aNewChannel
;
365 // nsIInterfaceRequestor
366 getInterface
: function SRCH_load_GI(aIID
) {
367 return this.QueryInterface(aIID
);
370 // nsIBadCertListener2
371 notifyCertProblem
: function SRCH_certProblem(socketInfo
, status
, targetSite
) {
375 // nsISSLErrorListener
376 notifySSLError
: function SRCH_SSLError(socketInfo
, error
, targetSite
) {
382 onRedirect: function (aChannel
, aNewChannel
) {},
383 // nsIProgressEventSink
384 onProgress: function (aRequest
, aContext
, aProgress
, aProgressMax
) {},
385 onStatus: function (aRequest
, aContext
, aStatus
, aStatusArg
) {}
390 * Used to verify a given DOM node's localName and namespaceURI.
392 * The element to verify.
393 * @param aLocalNameArray
394 * An array of strings to compare against aElement's localName.
395 * @param aNameSpaceArray
396 * An array of strings to compare against aElement's namespaceURI.
398 * @returns false if aElement is null, or if its localName or namespaceURI
399 * does not match one of the elements in the aLocalNameArray or
400 * aNameSpaceArray arrays, respectively.
401 * @throws NS_ERROR_INVALID_ARG if aLocalNameArray or aNameSpaceArray are null.
403 function checkNameSpace(aElement
, aLocalNameArray
, aNameSpaceArray
) {
404 ENSURE_ARG(aLocalNameArray
&& aNameSpaceArray
, "missing aLocalNameArray or \
405 aNameSpaceArray for checkNameSpace");
407 (aLocalNameArray
.indexOf(aElement
.localName
) != -1) &&
408 (aNameSpaceArray
.indexOf(aElement
.namespaceURI
) != -1));
412 * Safely close a nsISafeOutputStream.
414 * The file output stream to close.
416 function closeSafeOutputStream(aFOS
) {
417 if (aFOS
instanceof Ci
.nsISafeOutputStream
) {
427 * Wrapper function for nsIIOService::newURI.
429 * The URL string from which to create an nsIURI.
430 * @returns an nsIURI object, or null if the creation of the URI failed.
432 function makeURI(aURLSpec
, aCharset
) {
433 var ios
= Cc
["@mozilla.org/network/io-service;1"].
434 getService(Ci
.nsIIOService
);
436 return ios
.newURI(aURLSpec
, aCharset
, null);
443 * Gets a directory from the directory service.
445 * The directory service key indicating the directory to get.
447 function getDir(aKey
) {
448 ENSURE_ARG(aKey
, "getDir requires a directory key!");
450 var fileLocator
= Cc
["@mozilla.org/file/directory_service;1"].
451 getService(Ci
.nsIProperties
);
452 var dir
= fileLocator
.get(aKey
, Ci
.nsIFile
);
457 * The following two functions are essentially copied from
458 * nsInternetSearchService. They are required for backwards compatibility.
460 function queryCharsetFromCode(aCode
) {
462 codes
[0] = "x-mac-roman";
463 codes
[6] = "x-mac-greek";
464 codes
[35] = "x-mac-turkish";
465 codes
[513] = "ISO-8859-1";
466 codes
[514] = "ISO-8859-2";
467 codes
[517] = "ISO-8859-5";
468 codes
[518] = "ISO-8859-6";
469 codes
[519] = "ISO-8859-7";
470 codes
[520] = "ISO-8859-8";
471 codes
[521] = "ISO-8859-9";
472 codes
[1049] = "IBM864";
473 codes
[1280] = "windows-1252";
474 codes
[1281] = "windows-1250";
475 codes
[1282] = "windows-1251";
476 codes
[1283] = "windows-1253";
477 codes
[1284] = "windows-1254";
478 codes
[1285] = "windows-1255";
479 codes
[1286] = "windows-1256";
480 codes
[1536] = "us-ascii";
481 codes
[1584] = "GB2312";
482 codes
[1585] = "x-gbk";
483 codes
[1600] = "EUC-KR";
484 codes
[2080] = "ISO-2022-JP";
485 codes
[2096] = "ISO-2022-CN";
486 codes
[2112] = "ISO-2022-KR";
487 codes
[2336] = "EUC-JP";
488 codes
[2352] = "GB2312";
489 codes
[2353] = "x-euc-tw";
490 codes
[2368] = "EUC-KR";
491 codes
[2561] = "Shift_JIS";
492 codes
[2562] = "KOI8-R";
493 codes
[2563] = "Big5";
494 codes
[2565] = "HZ-GB-2312";
499 return getLocalizedPref("intl.charset.default", DEFAULT_QUERY_CHARSET
);
501 function fileCharsetFromCode(aCode
) {
510 "X-MAC-CYRILLIC", // 7
511 "X-MAC-DEVANAGARI" , // 9
512 "X-MAC-GURMUKHI", // 10
513 "X-MAC-GUJARATI", // 11
515 "X-MAC-BENGALI", // 13
517 "X-MAC-TELUGU", // 15
518 "X-MAC-KANNADA", // 16
519 "X-MAC-MALAYALAM", // 17
520 "X-MAC-SINHALESE", // 18
521 "X-MAC-BURMESE", // 19
524 "X-MAC-LAOTIAN", // 22
525 "X-MAC-GEORGIAN", // 23
526 "X-MAC-ARMENIAN", // 24
528 "X-MAC-TIBETAN", // 26
529 "X-MAC-MONGOLIAN", // 27
530 "X-MAC-ETHIOPIC", // 28
531 "X-MAC-CENTRALEURROMAN", // 29
532 "X-MAC-VIETNAMESE", // 30
533 "X-MAC-EXTARABIC" // 31
535 // Sherlock files have always defaulted to x-mac-roman, so do that here too
536 return codes
[aCode
] || codes
[0];
540 * Returns a string interpretation of aBytes using aCharset, or null on
543 function bytesToString(aBytes
, aCharset
) {
544 var converter
= Cc
["@mozilla.org/intl/scriptableunicodeconverter"].
545 createInstance(Ci
.nsIScriptableUnicodeConverter
);
546 LOG("bytesToString: converting using charset: " + aCharset
);
549 converter
.charset
= aCharset
;
550 return converter
.convertFromByteArray(aBytes
, aBytes
.length
);
557 * Converts an array of bytes representing a Sherlock file into an array of
558 * lines representing the useful data from the file.
561 * The array of bytes representing the Sherlock file.
562 * @param aCharsetCode
563 * An integer value representing a character set code to be passed to
564 * fileCharsetFromCode, or null for the default Sherlock encoding.
566 function sherlockBytesToLines(aBytes
, aCharsetCode
) {
567 // fileCharsetFromCode returns the default encoding if aCharsetCode is null
568 var charset
= fileCharsetFromCode(aCharsetCode
);
570 var dataString
= bytesToString(aBytes
, charset
);
571 ENSURE(dataString
, "sherlockBytesToLines: Couldn't convert byte array!",
572 Cr
.NS_ERROR_FAILURE
);
574 // Split the string into lines, and filter out comments and
575 // whitespace-only lines
576 return dataString
.split(NEW_LINES
).filter(isUsefulLine
);
580 * Gets the current value of the locale. It's possible for this preference to
581 * be localized, so we have to do a little extra work here. Similar code
582 * exists in nsHttpHandler.cpp when building the UA string.
584 function getLocale() {
585 const localePref
= "general.useragent.locale";
586 var locale
= getLocalizedPref(localePref
);
591 var prefs
= Cc
["@mozilla.org/preferences-service;1"].
592 getService(Ci
.nsIPrefBranch
);
593 return prefs
.getCharPref(localePref
);
597 * Wrapper for nsIPrefBranch::getComplexValue.
599 * The name of the pref to get.
600 * @returns aDefault if the requested pref doesn't exist.
602 function getLocalizedPref(aPrefName
, aDefault
) {
603 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
604 getService(Ci
.nsIPrefBranch
);
605 const nsIPLS
= Ci
.nsIPrefLocalizedString
;
607 return prefB
.getComplexValue(aPrefName
, nsIPLS
).data
;
614 * Wrapper for nsIPrefBranch::setComplexValue.
616 * The name of the pref to set.
618 function setLocalizedPref(aPrefName
, aValue
) {
619 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
620 getService(Ci
.nsIPrefBranch
);
621 const nsIPLS
= Ci
.nsIPrefLocalizedString
;
623 var pls
= Components
.classes
["@mozilla.org/pref-localizedstring;1"]
624 .createInstance(Ci
.nsIPrefLocalizedString
);
626 prefB
.setComplexValue(aPrefName
, nsIPLS
, pls
);
631 * Wrapper for nsIPrefBranch::getBoolPref.
633 * The name of the pref to get.
634 * @returns aDefault if the requested pref doesn't exist.
636 function getBoolPref(aName
, aDefault
) {
637 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
638 getService(Ci
.nsIPrefBranch
);
640 return prefB
.getBoolPref(aName
);
647 * Get a unique nsIFile object with a sanitized name, based on the engine name.
649 * A name to "sanitize". Can be an empty string, in which case a random
650 * 8 character filename will be produced.
651 * @returns A nsIFile object in the user's search engines directory with a
652 * unique sanitized name.
654 function getSanitizedFile(aName
) {
655 var fileName
= sanitizeName(aName
) + "." + XML_FILE_EXT
;
656 var file
= getDir(NS_APP_USER_SEARCH_DIR
);
657 file
.append(fileName
);
658 file
.createUnique(Ci
.nsIFile
.NORMAL_FILE_TYPE
, PERMS_FILE
);
663 * Removes all characters not in the "chars" string from aName.
665 * @returns a sanitized name to be used as a filename, or a random name
666 * if a sanitized name cannot be obtained (if aName contains
667 * no valid characters).
669 function sanitizeName(aName
) {
670 const chars
= "-abcdefghijklmnopqrstuvwxyz0123456789";
671 const maxLength
= 60;
673 var name
= aName
.toLowerCase();
674 name
= name
.replace(/ /g
, "-");
675 name
= name
.split("").filter(function (el
) {
676 return chars
.indexOf(el
) != -1;
680 // Our input had no valid characters - use a random name
681 var cl
= chars
.length
- 1;
682 for (var i
= 0; i
< 8; ++i
)
683 name
+= chars
.charAt(Math
.round(Math
.random() * cl
));
686 if (name
.length
> maxLength
)
687 name
= name
.substring(0, maxLength
);
693 * Notifies watchers of SEARCH_ENGINE_TOPIC about changes to an engine or to
694 * the state of the search service.
697 * The nsISearchEngine object to which the change applies.
699 * A verb describing the change.
701 * @see nsIBrowserSearchService.idl
703 function notifyAction(aEngine
, aVerb
) {
704 var os
= Cc
["@mozilla.org/observer-service;1"].
705 getService(Ci
.nsIObserverService
);
706 LOG("NOTIFY: Engine: \"" + aEngine
.name
+ "\"; Verb: \"" + aVerb
+ "\"");
707 os
.notifyObservers(aEngine
, SEARCH_ENGINE_TOPIC
, aVerb
);
711 * Simple object representing a name/value pair.
713 function QueryParameter(aName
, aValue
) {
714 ENSURE_ARG(aName
&& (aValue
!= null),
715 "missing name or value for QueryParameter!");
722 * Perform OpenSearch parameter substitution on aParamValue.
725 * A string containing OpenSearch search parameters.
726 * @param aSearchTerms
727 * The user-provided search terms. This string will inserted into
728 * aParamValue as the value of the OS_PARAM_USER_DEFINED parameter.
729 * This value must already be escaped appropriately - it is inserted
731 * @param aQueryEncoding
732 * The value to use for the OS_PARAM_INPUT_ENCODING parameter. See
733 * definition in the OpenSearch spec.
735 * @see http://opensearch.a9.com/spec/1.1/querysyntax/#core
737 function ParamSubstitution(aParamValue
, aSearchTerms
, aEngine
) {
738 var value
= aParamValue
;
740 var distributionID
= MOZ_DISTRIBUTION_ID
;
742 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
743 getService(Ci
.nsIPrefBranch
);
744 distributionID
= prefB
.getCharPref(BROWSER_SEARCH_PREF
+ "distributionID");
748 // Custom search parameters. These are only available to default search
750 if (aEngine
._isDefault
) {
751 value
= value
.replace(MOZ_PARAM_LOCALE
, getLocale());
752 value
= value
.replace(MOZ_PARAM_DIST_ID
, distributionID
);
753 value
= value
.replace(MOZ_PARAM_OFFICIAL
, MOZ_OFFICIAL
);
756 // Insert the OpenSearch parameters we're confident about
757 value
= value
.replace(OS_PARAM_USER_DEFINED
, aSearchTerms
);
758 value
= value
.replace(OS_PARAM_INPUT_ENCODING
, aEngine
.queryCharset
);
759 value
= value
.replace(OS_PARAM_LANGUAGE
,
760 getLocale() || OS_PARAM_LANGUAGE_DEF
);
761 value
= value
.replace(OS_PARAM_OUTPUT_ENCODING
,
762 OS_PARAM_OUTPUT_ENCODING_DEF
);
764 // Replace any optional parameters
765 value
= value
.replace(OS_PARAM_OPTIONAL
, "");
767 // Insert any remaining required params with our default values
768 for (var i
= 0; i
< OS_UNSUPPORTED_PARAMS
.length
; ++i
) {
769 value
= value
.replace(OS_UNSUPPORTED_PARAMS
[i
][0],
770 OS_UNSUPPORTED_PARAMS
[i
][1]);
777 * Creates a mozStorage statement that can be used to access the database we
778 * use to hold metadata.
780 * @param dbconn the database that the statement applies to
781 * @param sql a string specifying the sql statement that should be created
783 function createStatement (dbconn
, sql
) {
784 var stmt
= dbconn
.createStatement(sql
);
785 var wrapper
= Cc
["@mozilla.org/storage/statement-wrapper;1"].
786 createInstance(Ci
.mozIStorageStatementWrapper
);
788 wrapper
.initialize(stmt
);
793 * Creates an engineURL object, which holds the query URL and all parameters.
796 * A string containing the name of the MIME type of the search results
797 * returned by this URL.
799 * The HTTP request method. Must be a case insensitive value of either
802 * The URL to which search queries should be sent. For GET requests,
803 * must contain the string "{searchTerms}", to indicate where the user
804 * entered search terms should be inserted.
806 * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
808 * @throws NS_ERROR_NOT_IMPLEMENTED if aType is unsupported.
810 function EngineURL(aType
, aMethod
, aTemplate
) {
811 ENSURE_ARG(aType
&& aMethod
&& aTemplate
,
812 "missing type, method or template for EngineURL!");
814 var method
= aMethod
.toUpperCase();
815 var type
= aType
.toLowerCase();
817 ENSURE_ARG(method
== "GET" || method
== "POST",
818 "method passed to EngineURL must be \"GET\" or \"POST\"");
821 this.method
= method
;
824 var templateURI
= makeURI(aTemplate
);
825 ENSURE(templateURI
, "new EngineURL: template is not a valid URI!",
826 Cr
.NS_ERROR_FAILURE
);
828 switch (templateURI
.scheme
) {
831 // Disable these for now, see bug 295018
834 this.template
= aTemplate
;
837 ENSURE(false, "new EngineURL: template uses invalid scheme!",
838 Cr
.NS_ERROR_FAILURE
);
841 EngineURL
.prototype = {
843 addParam
: function SRCH_EURL_addParam(aName
, aValue
) {
844 this.params
.push(new QueryParameter(aName
, aValue
));
847 getSubmission
: function SRCH_EURL_getSubmission(aSearchTerms
, aEngine
) {
848 var url
= ParamSubstitution(this.template
, aSearchTerms
, aEngine
);
850 // Create an application/x-www-form-urlencoded representation of our params
851 // (name=value&name=value&name=value)
853 for (var i
= 0; i
< this.params
.length
; ++i
) {
854 var param
= this.params
[i
];
855 var value
= ParamSubstitution(param
.value
, aSearchTerms
, aEngine
);
857 dataString
+= (i
> 0 ? "&" : "") + param
.name
+ "=" + value
;
861 if (this.method
== "GET") {
862 // GET method requests have no post data, and append the encoded
863 // query string to the url...
864 if (url
.indexOf("?") == -1 && dataString
)
867 } else if (this.method
== "POST") {
868 // POST method requests must wrap the encoded text in a MIME
869 // stream and supply that as POSTDATA.
870 var stringStream
= Cc
["@mozilla.org/io/string-input-stream;1"].
871 createInstance(Ci
.nsIStringInputStream
);
872 #ifdef MOZILLA_1_8_BRANCH
874 stringStream
.setData(dataString
, dataString
.length
);
876 stringStream
.data
= dataString
;
879 postData
= Cc
["@mozilla.org/network/mime-input-stream;1"].
880 createInstance(Ci
.nsIMIMEInputStream
);
881 postData
.addHeader("Content-Type", "application/x-www-form-urlencoded");
882 postData
.addContentLength
= true;
883 postData
.setData(stringStream
);
886 return new Submission(makeURI(url
), postData
);
890 * Serializes the engine object to a OpenSearch Url element.
892 * The document to use to create the Url element.
894 * The element to which the created Url element is appended.
896 * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
898 _serializeToElement
: function SRCH_EURL_serializeToEl(aDoc
, aElement
) {
899 var url
= aDoc
.createElementNS(OPENSEARCH_NS_11
, "Url");
900 url
.setAttribute("type", this.type
);
901 url
.setAttribute("method", this.method
);
902 url
.setAttribute("template", this.template
);
904 for (var i
= 0; i
< this.params
.length
; ++i
) {
905 var param
= aDoc
.createElementNS(OPENSEARCH_NS_11
, "Param");
906 param
.setAttribute("name", this.params
[i
].name
);
907 param
.setAttribute("value", this.params
[i
].value
);
908 url
.appendChild(aDoc
.createTextNode("\n "));
909 url
.appendChild(param
);
911 url
.appendChild(aDoc
.createTextNode("\n"));
912 aElement
.appendChild(url
);
917 * nsISearchEngine constructor.
919 * A nsILocalFile or nsIURI object representing the location of the
920 * search engine data file.
921 * @param aSourceDataType
922 * The data type of the file used to describe the engine. Must be either
923 * DATA_XML or DATA_TEXT.
925 * Boolean indicating whether the engine should be treated as read-only.
926 * Read only engines cannot be serialized to file.
928 function Engine(aLocation
, aSourceDataType
, aIsReadOnly
) {
929 this._dataType
= aSourceDataType
;
930 this._readOnly
= aIsReadOnly
;
933 if (aLocation
instanceof Ci
.nsILocalFile
) {
934 // we already have a file (e.g. loading engines from disk)
935 this._file
= aLocation
;
936 } else if (aLocation
instanceof Ci
.nsIURI
) {
937 this._uri
= aLocation
;
938 switch (aLocation
.scheme
) {
945 this._uri
= aLocation
;
948 ERROR("Invalid URI passed to the nsISearchEngine constructor",
949 Cr
.NS_ERROR_INVALID_ARG
);
952 ERROR("Engine location is neither a File nor a URI object",
953 Cr
.NS_ERROR_INVALID_ARG
);
957 // The engine's alias.
959 // The data describing the engine. Is either an array of bytes, for Sherlock
960 // files, or an XML document element, for XML plugins.
962 // The engine's data type. See data types (DATA_) defined above.
964 // Whether or not the engine is readonly.
966 // The engine's description
968 // Used to store the engine to replace, if we're an update to an existing
970 _engineToUpdate
: null,
971 // The file from which the plugin was loaded.
973 // Set to true if the engine has a preferred icon (an icon that should not be
974 // overridden by a non-preferred icon).
975 _hasPreferredIcon
: null,
976 // Whether the engine is hidden from the user.
978 // The engine's name.
980 // The engine type. See engine types (TYPE_) defined above.
982 // The name of the charset used to submit the search terms.
984 // A URL string pointing to the engine's search form.
986 // The URI object from which the engine was retrieved.
987 // This is null for local plugins, and is used for error messages and logging.
989 // Whether to obtain user confirmation before adding the engine. This is only
990 // used when the engine is first added to the list.
992 // Whether to set this as the current engine as soon as it is loaded. This
993 // is only used when the engine is first added to the list.
995 // Where the engine was loaded from. Can be one of: SEARCH_APP_DIR,
996 // SEARCH_PROFILE_DIR, SEARCH_IN_EXTENSION.
997 __installLocation
: null,
998 // The number of days between update checks for new versions
999 _updateInterval
: null,
1000 // The url to check at for a new update
1002 // The url to check for a new icon
1003 _iconUpdateURL
: null,
1004 // A reference to the timer used for lazily serializing the engine to file
1005 _serializeTimer
: null,
1008 * Retrieves the data from the engine's file. If the engine's dataType is
1009 * XML, the document element is placed in the engine's data field. For text
1010 * engines, the data is just read directly from file and placed as an array
1011 * of lines in the engine's data field.
1013 _initFromFile
: function SRCH_ENG_initFromFile() {
1014 ENSURE(this._file
&& this._file
.exists(),
1015 "File must exist before calling initFromFile!",
1016 Cr
.NS_ERROR_UNEXPECTED
);
1018 var fileInStream
= Cc
["@mozilla.org/network/file-input-stream;1"].
1019 createInstance(Ci
.nsIFileInputStream
);
1021 fileInStream
.init(this._file
, MODE_RDONLY
, PERMS_FILE
, false);
1023 switch (this._dataType
) {
1024 case SEARCH_DATA_XML
:
1025 var domParser
= Cc
["@mozilla.org/xmlextras/domparser;1"].
1026 createInstance(Ci
.nsIDOMParser
);
1027 var doc
= domParser
.parseFromStream(fileInStream
, "UTF-8",
1028 this._file
.fileSize
,
1031 this._data
= doc
.documentElement
;
1033 case SEARCH_DATA_TEXT
:
1034 var binaryInStream
= Cc
["@mozilla.org/binaryinputstream;1"].
1035 createInstance(Ci
.nsIBinaryInputStream
);
1036 binaryInStream
.setInputStream(fileInStream
);
1038 var bytes
= binaryInStream
.readByteArray(binaryInStream
.available());
1043 ERROR("Bogus engine _dataType: \"" + this._dataType
+ "\"",
1044 Cr
.NS_ERROR_UNEXPECTED
);
1046 fileInStream
.close();
1048 // Now that the data is loaded, initialize the engine object
1049 this._initFromData();
1053 * Retrieves the engine data from a URI.
1055 _initFromURI
: function SRCH_ENG_initFromURI() {
1056 ENSURE_WARN(this._uri
instanceof Ci
.nsIURI
,
1057 "Must have URI when calling _initFromURI!",
1058 Cr
.NS_ERROR_UNEXPECTED
);
1060 LOG("_initFromURI: Downloading engine from: \"" + this._uri
.spec
+ "\".");
1062 var ios
= Cc
["@mozilla.org/network/io-service;1"].
1063 getService(Ci
.nsIIOService
);
1064 var chan
= ios
.newChannelFromURI(this._uri
);
1066 if (this._engineToUpdate
&& (chan
instanceof Ci
.nsIHttpChannel
)) {
1067 var lastModified
= engineMetadataService
.getAttr(this._engineToUpdate
,
1068 "updatelastmodified");
1070 chan
.setRequestHeader("If-Modified-Since", lastModified
, false);
1072 var listener
= new loadListener(chan
, this, this._onLoad
);
1073 chan
.notificationCallbacks
= listener
;
1074 chan
.asyncOpen(listener
, null);
1078 * Attempts to find an EngineURL object in the set of EngineURLs for
1079 * this Engine that has the given type string. (This corresponds to the
1080 * "type" attribute in the "Url" node in the OpenSearch spec.)
1081 * This method will return the first matching URL object found, or null
1082 * if no matching URL is found.
1084 * @param aType string to match the EngineURL's type attribute
1086 _getURLOfType
: function SRCH_ENG__getURLOfType(aType
) {
1087 for (var i
= 0; i
< this._urls
.length
; ++i
) {
1088 if (this._urls
[i
].type
== aType
)
1089 return this._urls
[i
];
1095 _confirmAddEngine
: function SRCH_SVC_confirmAddEngine() {
1096 var sbs
= Cc
["@mozilla.org/intl/stringbundle;1"].
1097 getService(Ci
.nsIStringBundleService
);
1098 var stringBundle
= sbs
.createBundle(SEARCH_BUNDLE
);
1099 var titleMessage
= stringBundle
.GetStringFromName("addEngineConfirmTitle");
1101 // Display only the hostname portion of the URL.
1103 stringBundle
.formatStringFromName("addEngineConfirmation",
1104 [this._name
, this._uri
.host
], 2);
1105 var checkboxMessage
= stringBundle
.GetStringFromName("addEngineUseNowText");
1106 var addButtonLabel
=
1107 stringBundle
.GetStringFromName("addEngineAddButtonLabel");
1109 var ps
= Cc
["@mozilla.org/embedcomp/prompt-service;1"].
1110 getService(Ci
.nsIPromptService
);
1111 var buttonFlags
= (ps
.BUTTON_TITLE_IS_STRING
* ps
.BUTTON_POS_0
) +
1112 (ps
.BUTTON_TITLE_CANCEL
* ps
.BUTTON_POS_1
) +
1113 ps
.BUTTON_POS_0_DEFAULT
;
1115 var checked
= {value
: false};
1116 // confirmEx returns the index of the button that was pressed. Since "Add"
1117 // is button 0, we want to return the negation of that value.
1118 var confirm
= !ps
.confirmEx(null,
1123 null, null, // button 1 & 2 names not used
1127 return {confirmed
: confirm
, useNow
: checked
.value
};
1131 * Handle the successful download of an engine. Initializes the engine and
1132 * triggers parsing of the data. The engine is then flushed to disk. Notifies
1133 * the search service once initialization is complete.
1135 _onLoad
: function SRCH_ENG_onLoad(aBytes
, aEngine
) {
1137 * Handle an error during the load of an engine by prompting the user to
1138 * notify him that the load failed.
1140 function onError(aErrorString
, aTitleString
) {
1141 if (aEngine
._engineToUpdate
) {
1142 // We're in an update, so just fail quietly
1143 LOG("updating " + aEngine
._engineToUpdate
.name
+ " failed");
1146 var sbs
= Cc
["@mozilla.org/intl/stringbundle;1"].
1147 getService(Ci
.nsIStringBundleService
);
1149 var brandBundle
= sbs
.createBundle(BRAND_BUNDLE
);
1150 var brandName
= brandBundle
.GetStringFromName("brandShortName");
1152 var searchBundle
= sbs
.createBundle(SEARCH_BUNDLE
);
1153 var msgStringName
= aErrorString
|| "error_loading_engine_msg2";
1154 var titleStringName
= aTitleString
|| "error_loading_engine_title";
1155 var title
= searchBundle
.GetStringFromName(titleStringName
);
1156 var text
= searchBundle
.formatStringFromName(msgStringName
,
1157 [brandName
, aEngine
._location
],
1160 var ww
= Cc
["@mozilla.org/embedcomp/window-watcher;1"].
1161 getService(Ci
.nsIWindowWatcher
);
1162 ww
.getNewPrompter(null).alert(title
, text
);
1170 var engineToUpdate
= null;
1171 if (aEngine
._engineToUpdate
) {
1172 engineToUpdate
= aEngine
._engineToUpdate
.wrappedJSObject
;
1174 // Make this new engine use the old engine's file.
1175 aEngine
._file
= engineToUpdate
._file
;
1178 switch (aEngine
._dataType
) {
1179 case SEARCH_DATA_XML
:
1180 var parser
= Cc
["@mozilla.org/xmlextras/domparser;1"].
1181 createInstance(Ci
.nsIDOMParser
);
1182 var doc
= parser
.parseFromBuffer(aBytes
, aBytes
.length
, "text/xml");
1183 aEngine
._data
= doc
.documentElement
;
1185 case SEARCH_DATA_TEXT
:
1186 aEngine
._data
= aBytes
;
1190 LOG("_onLoad: Bogus engine _dataType: \"" + this._dataType
+ "\"");
1195 // Initialize the engine from the obtained data
1196 aEngine
._initFromData();
1198 LOG("_onLoad: Failed to init engine!\n" + ex
);
1199 // Report an error to the user
1204 // Check to see if this is a duplicate engine. If we're confirming the
1205 // engine load, then we display a "this is a duplicate engine" prompt,
1206 // otherwise we fail silently.
1207 if (!engineToUpdate
) {
1208 var ss
= Cc
["@mozilla.org/browser/search-service;1"].
1209 getService(Ci
.nsIBrowserSearchService
);
1210 if (ss
.getEngineByName(aEngine
.name
)) {
1211 if (aEngine
._confirm
)
1212 onError("error_duplicate_engine_msg", "error_invalid_engine_title");
1214 LOG("_onLoad: duplicate engine found, bailing");
1219 // If requested, confirm the addition now that we have the title.
1220 // This property is only ever true for engines added via
1221 // nsIBrowserSearchService::addEngine.
1222 if (aEngine
._confirm
) {
1223 var confirmation
= aEngine
._confirmAddEngine();
1224 LOG("_onLoad: confirm is " + confirmation
.confirmed
+
1225 "; useNow is " + confirmation
.useNow
);
1226 if (!confirmation
.confirmed
)
1228 aEngine
._useNow
= confirmation
.useNow
;
1231 // If we don't yet have a file, get one now. The only case where we would
1232 // already have a file is if this is an update and _file was set above.
1234 aEngine
._file
= getSanitizedFile(aEngine
.name
);
1236 if (engineToUpdate
) {
1237 // Keep track of the last modified date, so that we can make conditional
1238 // requests for future updates.
1239 engineMetadataService
.setAttr(aEngine
, "updatelastmodified",
1240 (new Date()).toUTCString());
1242 // Set the new engine's icon, if it doesn't yet have one.
1243 if (!aEngine
._iconURI
&& engineToUpdate
._iconURI
)
1244 aEngine
._iconURI
= engineToUpdate
._iconURI
;
1246 // Clear the "use now" flag since we don't want to be changing the
1247 // current engine for an update.
1248 aEngine
._useNow
= false;
1251 // Write the engine to file
1252 aEngine
._serializeToFile();
1254 // Notify the search service of the sucessful load. It will deal with
1255 // updates by checking aEngine._engineToUpdate.
1256 notifyAction(aEngine
, SEARCH_ENGINE_LOADED
);
1260 * Sets the .iconURI property of the engine.
1263 * A URI string pointing to the engine's icon. Must have a http[s],
1264 * ftp, or data scheme. Icons with HTTP[S] or FTP schemes will be
1265 * downloaded and converted to data URIs for storage in the engine
1266 * XML files, if the engine is not readonly.
1267 * @param aIsPreferred
1268 * Whether or not this icon is to be preferred. Preferred icons can
1269 * override non-preferred icons.
1271 _setIcon
: function SRCH_ENG_setIcon(aIconURL
, aIsPreferred
) {
1272 // If we already have a preferred icon, and this isn't a preferred icon,
1274 if (this._hasPreferredIcon
&& !aIsPreferred
)
1277 var uri
= makeURI(aIconURL
);
1283 LOG("_setIcon: Setting icon url \"" + uri
.spec
+ "\" for engine \""
1284 + this.name
+ "\".");
1285 // Only accept remote icons from http[s] or ftp
1286 switch (uri
.scheme
) {
1288 this._iconURI
= uri
;
1289 notifyAction(this, SEARCH_ENGINE_CHANGED
);
1290 this._hasPreferredIcon
= aIsPreferred
;
1295 // No use downloading the icon if the engine file is read-only
1296 if (!this._readOnly
) {
1297 LOG("_setIcon: Downloading icon: \"" + uri
.spec
+
1298 "\" for engine: \"" + this.name
+ "\"");
1299 var ios
= Cc
["@mozilla.org/network/io-service;1"].
1300 getService(Ci
.nsIIOService
);
1301 var chan
= ios
.newChannelFromURI(uri
);
1303 function iconLoadCallback(aByteArray
, aEngine
) {
1304 // This callback may run after we've already set a preferred icon,
1306 if (aEngine
._hasPreferredIcon
&& !aIsPreferred
)
1309 if (!aByteArray
|| aByteArray
.length
> MAX_ICON_SIZE
) {
1310 LOG("iconLoadCallback: load failed, or the icon was too large!");
1314 var str
= btoa(String
.fromCharCode
.apply(null, aByteArray
));
1315 aEngine
._iconURI
= makeURI(ICON_DATAURL_PREFIX
+ str
);
1317 // The engine might not have a file yet, if it's being downloaded,
1318 // because the request for the engine file itself (_onLoad) may not
1319 // yet be complete. In that case, this change will be written to
1320 // file when _onLoad is called.
1322 aEngine
._serializeToFile();
1324 notifyAction(aEngine
, SEARCH_ENGINE_CHANGED
);
1325 aEngine
._hasPreferredIcon
= aIsPreferred
;
1328 // If we're currently acting as an "update engine", then the callback
1329 // should set the icon on the engine we're updating and not us, since
1330 // |this| might be gone by the time the callback runs.
1331 var engineToSet
= this._engineToUpdate
|| this;
1333 var listener
= new loadListener(chan
, engineToSet
, iconLoadCallback
);
1334 chan
.notificationCallbacks
= listener
;
1335 chan
.asyncOpen(listener
, null);
1342 * Initialize this Engine object from the collected data.
1344 _initFromData
: function SRCH_ENG_initFromData() {
1346 ENSURE_WARN(this._data
, "Can't init an engine with no data!",
1347 Cr
.NS_ERROR_UNEXPECTED
);
1349 // Find out what type of engine we are
1350 switch (this._dataType
) {
1351 case SEARCH_DATA_XML
:
1352 if (checkNameSpace(this._data
, [MOZSEARCH_LOCALNAME
],
1353 [MOZSEARCH_NS_10
])) {
1355 LOG("_init: Initing MozSearch plugin from " + this._location
);
1357 this._type
= SEARCH_TYPE_MOZSEARCH
;
1358 this._parseAsMozSearch();
1360 } else if (checkNameSpace(this._data
, [OPENSEARCH_LOCALNAME
],
1361 OPENSEARCH_NAMESPACES
)) {
1363 LOG("_init: Initing OpenSearch plugin from " + this._location
);
1365 this._type
= SEARCH_TYPE_OPENSEARCH
;
1366 this._parseAsOpenSearch();
1369 ENSURE(false, this._location
+ " is not a valid search plugin.",
1370 Cr
.NS_ERROR_FAILURE
);
1373 case SEARCH_DATA_TEXT
:
1374 LOG("_init: Initing Sherlock plugin from " + this._location
);
1376 // the only text-based format we support is Sherlock
1377 this._type
= SEARCH_TYPE_SHERLOCK
;
1378 this._parseAsSherlock();
1381 // No need to keep a ref to our data (which in some cases can be a document
1382 // element) past this point
1387 * Initialize this Engine object from a collection of metadata.
1389 _initFromMetadata
: function SRCH_ENG_initMetaData(aName
, aIconURL
, aAlias
,
1390 aDescription
, aMethod
,
1392 ENSURE_WARN(!this._readOnly
,
1393 "Can't call _initFromMetaData on a readonly engine!",
1394 Cr
.NS_ERROR_FAILURE
);
1396 this._urls
.push(new EngineURL("text/html", aMethod
, aTemplate
));
1399 this.alias
= aAlias
;
1400 this._description
= aDescription
;
1401 this._setIcon(aIconURL
, true);
1403 this._serializeToFile();
1407 * Extracts data from an OpenSearch URL element and creates an EngineURL
1408 * object which is then added to the engine's list of URLs.
1410 * @throws NS_ERROR_FAILURE if a URL object could not be created.
1412 * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag.
1415 _parseURL
: function SRCH_ENG_parseURL(aElement
) {
1416 var type
= aElement
.getAttribute("type");
1417 // According to the spec, method is optional, defaulting to "GET" if not
1419 var method
= aElement
.getAttribute("method") || "GET";
1420 var template
= aElement
.getAttribute("template");
1423 var url
= new EngineURL(type
, method
, template
);
1425 LOG("_parseURL: failed to add " + template
+ " as a URL");
1426 throw Cr
.NS_ERROR_FAILURE
;
1429 for (var i
= 0; i
< aElement
.childNodes
.length
; ++i
) {
1430 var param
= aElement
.childNodes
[i
];
1431 if (param
.localName
== "Param") {
1433 url
.addParam(param
.getAttribute("name"), param
.getAttribute("value"));
1436 LOG("_parseURL: Url element has an invalid param");
1438 } else if (param
.localName
== "MozParam" &&
1439 // We only support MozParams for default search engines
1442 switch (param
.getAttribute("condition")) {
1443 case "defaultEngine":
1444 const defPref
= BROWSER_SEARCH_PREF
+ "defaultenginename";
1445 var defaultPrefB
= Cc
["@mozilla.org/preferences-service;1"].
1446 getService(Ci
.nsIPrefService
).
1447 getDefaultBranch(null);
1448 const nsIPLS
= Ci
.nsIPrefLocalizedString
;
1451 defaultName
= defaultPrefB
.getComplexValue(defPref
, nsIPLS
).data
;
1454 // If this engine was the default search engine, use the true value
1455 if (this.name
== defaultName
)
1456 value
= param
.getAttribute("trueValue");
1458 value
= param
.getAttribute("falseValue");
1459 url
.addParam(param
.getAttribute("name"), value
);
1464 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
1465 getService(Ci
.nsIPrefBranch
);
1466 value
= prefB
.getCharPref(BROWSER_SEARCH_PREF
+ "param." +
1467 param
.getAttribute("pref"));
1468 url
.addParam(param
.getAttribute("name"), value
);
1475 this._urls
.push(url
);
1479 * Get the icon from an OpenSearch Image element.
1480 * @see http://opensearch.a9.com/spec/1.1/description/#image
1482 _parseImage
: function SRCH_ENG_parseImage(aElement
) {
1483 LOG("_parseImage: Image textContent: \"" + aElement
.textContent
+ "\"");
1484 if (aElement
.getAttribute("width") == "16" &&
1485 aElement
.getAttribute("height") == "16") {
1486 this._setIcon(aElement
.textContent
, true);
1490 _parseAsMozSearch
: function SRCH_ENG_parseAsMoz() {
1491 //forward to the OpenSearch parser
1492 this._parseAsOpenSearch();
1496 * Extract search engine information from the collected data to initialize
1497 * the engine object.
1499 _parseAsOpenSearch
: function SRCH_ENG_parseAsOS() {
1500 var doc
= this._data
;
1502 // The OpenSearch spec sets a default value for the input encoding.
1503 this._queryCharset
= OS_PARAM_INPUT_ENCODING_DEF
;
1505 for (var i
= 0; i
< doc
.childNodes
.length
; ++i
) {
1506 var child
= doc
.childNodes
[i
];
1507 switch (child
.localName
) {
1509 this._name
= child
.textContent
;
1512 this._description
= child
.textContent
;
1516 this._parseURL(child
);
1518 // Parsing of the element failed, just skip it.
1522 this._parseImage(child
);
1524 case "InputEncoding":
1525 this._queryCharset
= child
.textContent
.toUpperCase();
1528 // Non-OpenSearch elements
1530 this._searchForm
= child
.textContent
;
1533 this._updateURL
= child
.textContent
;
1535 case "UpdateInterval":
1536 this._updateInterval
= parseInt(child
.textContent
);
1538 case "IconUpdateUrl":
1539 this._iconUpdateURL
= child
.textContent
;
1543 ENSURE(this.name
&& (this._urls
.length
> 0),
1544 "_parseAsOpenSearch: No name, or missing URL!",
1545 Cr
.NS_ERROR_FAILURE
);
1546 ENSURE(this.supportsResponseType(URLTYPE_SEARCH_HTML
),
1547 "_parseAsOpenSearch: No text/html result type!",
1548 Cr
.NS_ERROR_FAILURE
);
1552 * Extract search engine information from the collected data to initialize
1553 * the engine object.
1555 _parseAsSherlock
: function SRCH_ENG_parseAsSherlock() {
1557 * Trims leading and trailing whitespace from aStr.
1559 function sTrim(aStr
) {
1560 return aStr
.replace(/^\s+/g, "").replace(/\s+$/g, "");
1564 * Extracts one Sherlock "section" from aSource. A section is essentially
1565 * an HTML element with attributes, but each attribute must be on a new
1566 * line, by definition.
1569 * An array of lines from the sherlock file.
1571 * The name of the section (e.g. "search" or "browser"). This value
1572 * is not case sensitive.
1573 * @returns an object whose properties correspond to the section's
1576 function getSection(aLines
, aSection
) {
1577 LOG("_parseAsSherlock::getSection: Sherlock lines:\n" +
1580 var startMark
= new RegExp("^\\s*<" + aSection
.toLowerCase() + "\\s*",
1582 var endMark
= /\s*>\s*$/gi;
1584 var foundStart
= false;
1585 var startLine
, numberOfLines
;
1586 // Find the beginning and end of the section
1587 for (var i
= 0; i
< lines
.length
; i
++) {
1589 if (endMark
.test(lines
[i
])) {
1590 numberOfLines
= i
- startLine
;
1591 // Remove the end marker
1592 lines
[i
] = lines
[i
].replace(endMark
, "");
1593 // If the endmarker was not the only thing on the line, include
1594 // this line in the results
1600 if (startMark
.test(lines
[i
])) {
1602 // Remove the start marker
1603 lines
[i
] = lines
[i
].replace(startMark
, "");
1605 // If the line is empty, don't include it in the result
1611 LOG("_parseAsSherlock::getSection: Start index: " + startLine
+
1612 "\nNumber of lines: " + numberOfLines
);
1613 lines
= lines
.splice(startLine
, numberOfLines
);
1614 LOG("_parseAsSherlock::getSection: Section lines:\n" +
1618 for (var i
= 0; i
< lines
.length
; i
++) {
1619 var line
= sTrim(lines
[i
]);
1621 var els
= line
.split("=");
1622 var name
= sTrim(els
.shift().toLowerCase());
1623 var value
= sTrim(els
.join("="));
1625 if (!name
|| !value
)
1628 // Strip leading and trailing whitespace, remove quotes from the
1629 // value, and remove any trailing slashes or ">" characters
1630 value
= value
.replace(/^["']/, "")
1631 .replace(/["']\s*[\\\/]?>?\s*$/, "") || "";
1632 value
= sTrim(value
);
1634 // Don't clobber existing attributes
1635 if (!(name
in section
))
1636 section
[name
] = value
;
1642 * Returns an array of name-value pair arrays representing the Sherlock
1643 * file's input elements. User defined inputs return USER_DEFINED
1644 * as the value. Elements are returned in the order they appear in the
1648 * <input name="foo" value="bar">
1649 * <input name="foopy" user>
1651 * [["foo", "bar"], ["foopy", "{searchTerms}"]]
1654 * An array of lines from the source file.
1656 function getInputs(aLines
) {
1659 * Extracts an attribute value from a given a line of text.
1660 * Example: <input value="foo" name="bar">
1661 * Extracts the string |foo| or |bar| given an input aAttr of
1662 * |value| or |name|.
1663 * Attributes may be quoted or unquoted. If unquoted, any whitespace
1664 * indicates the end of the attribute value.
1665 * Example: < value=22 33 name=44\334 >
1666 * Returns |22| for "value" and |44\334| for "name".
1669 * The name of the attribute for which to obtain the value. This
1670 * value is not case sensitive.
1672 * The line containing the attribute.
1674 * @returns the attribute value, or an empty string if the attribute
1677 function getAttr(aAttr
, aLine
) {
1678 // Used to determine whether an "input" line from a Sherlock file is a
1679 // "user defined" input.
1680 const userInput
= /(\s|["'=])user(\s|[>="'\/\\+]|$)/i;
1682 LOG("_parseAsSherlock::getAttr: Getting attr: \"" +
1683 aAttr
+ "\" for line: \"" + aLine
+ "\"");
1684 // We're not case sensitive, but we want to return the attribute value
1685 // in its original case, so create a copy of the source
1686 var lLine
= aLine
.toLowerCase();
1687 var attr
= aAttr
.toLowerCase();
1689 var attrStart
= lLine
.search(new RegExp("\\s" + attr
, "i"));
1690 if (attrStart
== -1) {
1692 // If this is the "user defined input" (i.e. contains the empty
1693 // "user" attribute), return our special keyword
1694 if (userInput
.test(lLine
) && attr
== "value") {
1695 LOG("_parseAsSherlock::getAttr: Found user input!\nLine:\"" + lLine
1697 return USER_DEFINED
;
1699 // The attribute doesn't exist - ignore
1700 LOG("_parseAsSherlock::getAttr: Failed to find attribute:\nLine:\""
1701 + lLine
+ "\"\nAttr:\"" + attr
+ "\"");
1705 var valueStart
= lLine
.indexOf("=", attrStart
) + "=".length
;
1706 if (valueStart
== -1)
1709 var quoteStart
= lLine
.indexOf("\"", valueStart
);
1710 if (quoteStart
== -1) {
1712 // Unquoted attribute, get the rest of the line, trimmed at the first
1713 // sign of whitespace. If the rest of the line is only whitespace,
1714 // returns a blank string.
1715 return lLine
.substr(valueStart
).replace(/\s.*$/, "");
1718 // Make sure that there's only whitespace between the start of the
1719 // value and the first quote. If there is, end the attribute value at
1720 // the first sign of whitespace. This prevents us from falling into
1721 // the next attribute if this is an unquoted attribute followed by a
1722 // quoted attribute.
1723 var betweenEqualAndQuote
= lLine
.substring(valueStart
, quoteStart
);
1724 if (/\S/.test(betweenEqualAndQuote
))
1725 return lLine
.substr(valueStart
).replace(/\s.*$/, "");
1727 // Adjust the start index to account for the opening quote
1728 valueStart
= quoteStart
+ "\"".length
;
1729 // Find the closing quote
1730 valueEnd
= lLine
.indexOf("\"", valueStart
);
1731 // If there is no closing quote, just go to the end of the line
1733 valueEnd
= aLine
.length
;
1735 return aLine
.substring(valueStart
, valueEnd
);
1740 LOG("_parseAsSherlock::getInputs: Lines:\n" + aLines
);
1741 // Filter out everything but non-inputs
1742 lines
= aLines
.filter(function (line
) {
1743 return /^\s*<input/i.test(line
);
1745 LOG("_parseAsSherlock::getInputs: Filtered lines:\n" + lines
);
1747 lines
.forEach(function (line
) {
1748 // Strip leading/trailing whitespace and remove the surrounding markup
1749 // ("<input" and ">")
1750 line
= sTrim(line
).replace(/^<input/i, "").replace(/>$/, "");
1752 // If this is one of the "directional" inputs (<inputnext>/<inputprev>)
1753 const directionalInput
= /^(prev|next)/i;
1754 if (directionalInput
.test(line
)) {
1756 // Make it look like a normal input by removing "prev" or "next"
1757 line
= line
.replace(directionalInput
, "");
1759 // If it has a name, give it a dummy value to match previous
1760 // nsInternetSearchService behavior
1761 if (/name\s*=/i.test(line
)) {
1762 line
+= " value=\"0\"";
1764 return; // Line has no name, skip it
1767 var attrName
= getAttr("name", line
);
1768 var attrValue
= getAttr("value", line
);
1769 LOG("_parseAsSherlock::getInputs: Got input:\nName:\"" + attrName
+
1770 "\"\nValue:\"" + attrValue
+ "\"");
1772 inputs
.push([attrName
, attrValue
]);
1777 function err(aErr
) {
1778 LOG("_parseAsSherlock::err: Sherlock param error:\n" + aErr
);
1779 throw Cr
.NS_ERROR_FAILURE
;
1782 // First try converting our byte array using the default Sherlock encoding.
1783 // If this fails, or if we find a sourceTextEncoding attribute, we need to
1784 // reconvert the byte array using the specified encoding.
1785 var sherlockLines
, searchSection
, sourceTextEncoding
, browserSection
;
1787 sherlockLines
= sherlockBytesToLines(this._data
);
1788 searchSection
= getSection(sherlockLines
, "search");
1789 browserSection
= getSection(sherlockLines
, "browser");
1790 sourceTextEncoding
= parseInt(searchSection
["sourcetextencoding"]);
1791 if (sourceTextEncoding
) {
1792 // Re-convert the bytes using the found sourceTextEncoding
1793 sherlockLines
= sherlockBytesToLines(this._data
, sourceTextEncoding
);
1794 searchSection
= getSection(sherlockLines
, "search");
1795 browserSection
= getSection(sherlockLines
, "browser");
1798 // The conversion using the default charset failed. Remove any non-ascii
1799 // bytes and try to find a sourceTextEncoding.
1800 var asciiBytes
= this._data
.filter(function (n
) {return !(0x80 & n
);});
1801 var asciiString
= String
.fromCharCode
.apply(null, asciiBytes
);
1802 sherlockLines
= asciiString
.split(NEW_LINES
).filter(isUsefulLine
);
1803 searchSection
= getSection(sherlockLines
, "search");
1804 sourceTextEncoding
= parseInt(searchSection
["sourcetextencoding"]);
1805 if (sourceTextEncoding
) {
1806 sherlockLines
= sherlockBytesToLines(this._data
, sourceTextEncoding
);
1807 searchSection
= getSection(sherlockLines
, "search");
1808 browserSection
= getSection(sherlockLines
, "browser");
1810 ERROR("Couldn't find a working charset", Cr
.NS_ERROR_FAILURE
);
1813 LOG("_parseAsSherlock: Search section:\n" + searchSection
.toSource());
1815 this._name
= searchSection
["name"] || err("Missing name!");
1816 this._description
= searchSection
["description"] || "";
1817 this._queryCharset
= searchSection
["querycharset"] ||
1818 queryCharsetFromCode(searchSection
["queryencoding"]);
1819 this._searchForm
= searchSection
["searchform"];
1821 this._updateInterval
= parseInt(browserSection
["updatecheckdays"]);
1823 this._updateURL
= browserSection
["update"];
1824 this._iconUpdateURL
= browserSection
["updateicon"];
1826 var method
= (searchSection
["method"] || "GET").toUpperCase();
1827 var template
= searchSection
["action"] || err("Missing action!");
1829 var inputs
= getInputs(sherlockLines
);
1830 LOG("_parseAsSherlock: Inputs:\n" + inputs
.toSource());
1834 if (method
== "GET") {
1835 // Here's how we construct the input string:
1836 // <input> is first: Name Attr: Prefix Data Example:
1837 // YES EMPTY None <value> TEMPLATE<value>
1838 // YES NON-EMPTY ? <name>=<value> TEMPLATE?<name>=<value>
1839 // NO EMPTY ------------- <ignored> --------------
1840 // NO NON-EMPTY & <name>=<value> TEMPLATE?<n1>=<v1>&<n2>=<v2>
1841 for (var i
= 0; i
< inputs
.length
; i
++) {
1842 var name
= inputs
[i
][0];
1843 var value
= inputs
[i
][1];
1846 template
+= USER_DEFINED
;
1848 template
+= "?" + name
+ "=" + value
;
1849 } else if (name
!= "")
1850 template
+= "&" + name
+ "=" + value
;
1852 url
= new EngineURL("text/html", method
, template
);
1854 } else if (method
== "POST") {
1855 // Create the URL object and just add the parameters directly
1856 url
= new EngineURL("text/html", method
, template
);
1857 for (var i
= 0; i
< inputs
.length
; i
++) {
1858 var name
= inputs
[i
][0];
1859 var value
= inputs
[i
][1];
1861 url
.addParam(name
, value
);
1864 err("Invalid method!");
1866 this._urls
.push(url
);
1870 * Returns an XML document object containing the search plugin information,
1871 * which can later be used to reload the engine.
1873 _serializeToElement
: function SRCH_ENG_serializeToEl() {
1874 function appendTextNode(aNameSpace
, aLocalName
, aValue
) {
1877 var node
= doc
.createElementNS(aNameSpace
, aLocalName
);
1878 node
.appendChild(doc
.createTextNode(aValue
));
1879 docElem
.appendChild(node
);
1880 docElem
.appendChild(doc
.createTextNode("\n"));
1884 var parser
= Cc
["@mozilla.org/xmlextras/domparser;1"].
1885 createInstance(Ci
.nsIDOMParser
);
1887 var doc
= parser
.parseFromString(EMPTY_DOC
, "text/xml");
1888 docElem
= doc
.documentElement
;
1890 docElem
.appendChild(doc
.createTextNode("\n"));
1892 appendTextNode(OPENSEARCH_NS_11
, "ShortName", this.name
);
1893 appendTextNode(OPENSEARCH_NS_11
, "Description", this._description
);
1894 appendTextNode(OPENSEARCH_NS_11
, "InputEncoding", this._queryCharset
);
1896 if (this._iconURI
) {
1897 var imageNode
= appendTextNode(OPENSEARCH_NS_11
, "Image",
1898 this._iconURI
.spec
);
1900 imageNode
.setAttribute("width", "16");
1901 imageNode
.setAttribute("height", "16");
1905 appendTextNode(MOZSEARCH_NS_10
, "UpdateInterval", this._updateInterval
);
1906 appendTextNode(MOZSEARCH_NS_10
, "UpdateUrl", this._updateURL
);
1907 appendTextNode(MOZSEARCH_NS_10
, "IconUpdateUrl", this._iconUpdateURL
);
1908 appendTextNode(MOZSEARCH_NS_10
, "SearchForm", this._searchForm
);
1910 for (var i
= 0; i
< this._urls
.length
; ++i
)
1911 this._urls
[i
]._serializeToElement(doc
, docElem
);
1912 docElem
.appendChild(doc
.createTextNode("\n"));
1917 _lazySerializeToFile
: function SRCH_ENG_serializeToFile() {
1918 if (this._serializeTimer
) {
1920 this._serializeTimer
.delay
= LAZY_SERIALIZE_DELAY
;
1922 this._serializeTimer
= Cc
["@mozilla.org/timer;1"].
1923 createInstance(Ci
.nsITimer
);
1924 var timerCallback
= {
1926 notify
: function SRCH_ENG_notify(aTimer
) {
1928 this.self
._serializeToFile();
1930 LOG("Serialization from timer callback failed:\n" + ex
);
1932 this.self
._serializeTimer
= null;
1935 this._serializeTimer
.initWithCallback(timerCallback
,
1936 LAZY_SERIALIZE_DELAY
,
1937 Ci
.nsITimer
.TYPE_ONE_SHOT
);
1942 * Serializes the engine object to file.
1944 _serializeToFile
: function SRCH_ENG_serializeToFile() {
1945 var file
= this._file
;
1946 ENSURE_WARN(!this._readOnly
, "Can't serialize a read only engine!",
1947 Cr
.NS_ERROR_FAILURE
);
1948 ENSURE_WARN(file
&& file
.exists(), "Can't serialize: file doesn't exist!",
1949 Cr
.NS_ERROR_UNEXPECTED
);
1951 var fos
= Cc
["@mozilla.org/network/safe-file-output-stream;1"].
1952 createInstance(Ci
.nsIFileOutputStream
);
1954 // Serialize the engine first - we don't want to overwrite a good file
1955 // if this somehow fails.
1956 doc
= this._serializeToElement();
1958 fos
.init(file
, (MODE_WRONLY
| MODE_TRUNCATE
), PERMS_FILE
, 0);
1961 var serializer
= Cc
["@mozilla.org/xmlextras/xmlserializer;1"].
1962 createInstance(Ci
.nsIDOMSerializer
);
1963 serializer
.serializeToStream(doc
.documentElement
, fos
, null);
1965 LOG("_serializeToFile: Error serializing engine:\n" + e
);
1968 closeSafeOutputStream(fos
);
1972 * Remove the engine's file from disk. The search service calls this once it
1973 * removes the engine from its internal store. This function will throw if
1974 * the file cannot be removed.
1976 _remove
: function SRCH_ENG_remove() {
1977 ENSURE(!this._readOnly
, "Can't remove read only engine!",
1978 Cr
.NS_ERROR_FAILURE
);
1979 ENSURE(this._file
&& this._file
.exists(),
1980 "Can't remove engine: file doesn't exist!",
1981 Cr
.NS_ERROR_FILE_NOT_FOUND
);
1983 this._file
.remove(false);
1988 if (this._alias
=== null)
1989 this._alias
= engineMetadataService
.getAttr(this, "alias");
1995 engineMetadataService
.setAttr(this, "alias", val
);
1996 notifyAction(this, SEARCH_ENGINE_CHANGED
);
2000 return this._description
;
2004 if (this._hidden
=== null)
2005 this._hidden
= engineMetadataService
.getAttr(this, "hidden");
2006 return this._hidden
;
2010 if (value
!= this._hidden
) {
2011 this._hidden
= value
;
2012 engineMetadataService
.setAttr(this, "hidden", value
);
2013 notifyAction(this, SEARCH_ENGINE_CHANGED
);
2018 return this._iconURI
;
2024 return this._iconURI
.spec
;
2027 // Where the engine is being loaded from: will return the URI's spec if the
2028 // engine is being downloaded and does not yet have a file. This is only used
2032 return this._file
.path
;
2035 return this._uri
.spec
;
2040 // The file that the plugin is loaded from is a unique identifier for it. We
2041 // use this as the identifier to store data in the sqlite database
2043 ENSURE_WARN(this._file
, "No _file for id!", Cr
.NS_ERROR_FAILURE
);
2045 if (this._isInProfile
)
2046 return "[profile]/" + this._file
.leafName
;
2048 if (this._isInAppDir
)
2049 return "[app]/" + this._file
.leafName
;
2051 // We're not in the profile or appdir, so this must be an extension-shipped
2052 // plugin. Use the full path.
2053 return this._file
.path
;
2056 get _installLocation() {
2057 ENSURE_WARN(this._file
&& this._file
.exists(),
2058 "_installLocation: engine has no file!",
2059 Cr
.NS_ERROR_FAILURE
);
2061 if (this.__installLocation
=== null) {
2062 if (this._file
.parent
.equals(getDir(NS_APP_SEARCH_DIR
)))
2063 this.__installLocation
= SEARCH_APP_DIR
;
2064 else if (this._file
.parent
.equals(getDir(NS_APP_USER_SEARCH_DIR
)))
2065 this.__installLocation
= SEARCH_PROFILE_DIR
;
2067 this.__installLocation
= SEARCH_IN_EXTENSION
;
2070 return this.__installLocation
;
2074 return this._installLocation
== SEARCH_APP_DIR
;
2076 get _isInProfile() {
2077 return this._installLocation
== SEARCH_PROFILE_DIR
;
2081 // For now, our concept of a "default engine" is "one that is not in the
2082 // user's profile directory", which is currently equivalent to "is app- or
2083 // extension-shipped".
2084 return !this._isInProfile
;
2088 // Whether or not the engine has an update URL
2089 return !!(this._updateURL
|| this._iconUpdateURL
);
2101 if (!this._searchForm
) {
2102 // No searchForm specified in the engine definition file, use the prePath
2103 // (e.g. https://foo.com for https://foo.com/search.php?q=bar).
2104 var htmlUrl
= this._getURLOfType(URLTYPE_SEARCH_HTML
);
2105 ENSURE_WARN(htmlUrl
, "Engine has no HTML URL!", Cr
.NS_ERROR_UNEXPECTED
);
2106 this._searchForm
= makeURI(htmlUrl
.template
).prePath
;
2109 return this._searchForm
;
2112 get queryCharset() {
2113 if (this._queryCharset
)
2114 return this._queryCharset
;
2115 return this._queryCharset
= queryCharsetFromCode(/* get the default */);
2118 // from nsISearchEngine
2119 addParam
: function SRCH_ENG_addParam(aName
, aValue
, aResponseType
) {
2120 ENSURE_ARG(aName
&& (aValue
!= null),
2121 "missing name or value for nsISearchEngine::addParam!");
2122 ENSURE_WARN(!this._readOnly
,
2123 "called nsISearchEngine::addParam on a read-only engine!",
2124 Cr
.NS_ERROR_FAILURE
);
2126 aResponseType
= URLTYPE_SEARCH_HTML
;
2128 var url
= this._getURLOfType(aResponseType
);
2130 ENSURE(url
, "Engine object has no URL for response type " + aResponseType
,
2131 Cr
.NS_ERROR_FAILURE
);
2133 url
.addParam(aName
, aValue
);
2135 // Serialize the changes to file lazily
2136 this._lazySerializeToFile();
2139 // from nsISearchEngine
2140 getSubmission
: function SRCH_ENG_getSubmission(aData
, aResponseType
) {
2142 aResponseType
= URLTYPE_SEARCH_HTML
;
2144 var url
= this._getURLOfType(aResponseType
);
2150 // Return a dummy submission object with our searchForm attribute
2151 return new Submission(makeURI(this.searchForm
), null);
2154 LOG("getSubmission: In data: \"" + aData
+ "\"");
2155 var textToSubURI
= Cc
["@mozilla.org/intl/texttosuburi;1"].
2156 getService(Ci
.nsITextToSubURI
);
2159 data
= textToSubURI
.ConvertAndEscape(this.queryCharset
, aData
);
2161 LOG("getSubmission: Falling back to default queryCharset!");
2162 data
= textToSubURI
.ConvertAndEscape(DEFAULT_QUERY_CHARSET
, aData
);
2164 LOG("getSubmission: Out data: \"" + data
+ "\"");
2165 return url
.getSubmission(data
, this);
2168 // from nsISearchEngine
2169 supportsResponseType
: function SRCH_ENG_supportsResponseType(type
) {
2170 return (this._getURLOfType(type
) != null);
2174 QueryInterface
: function SRCH_ENG_QI(aIID
) {
2175 if (aIID
.equals(Ci
.nsISearchEngine
) ||
2176 aIID
.equals(Ci
.nsISupports
))
2178 throw Cr
.NS_ERROR_NO_INTERFACE
;
2181 get wrappedJSObject() {
2187 // nsISearchSubmission
2188 function Submission(aURI
, aPostData
) {
2190 this._postData
= aPostData
;
2192 Submission
.prototype = {
2197 return this._postData
;
2199 QueryInterface
: function SRCH_SUBM_QI(aIID
) {
2200 if (aIID
.equals(Ci
.nsISearchSubmission
) ||
2201 aIID
.equals(Ci
.nsISupports
))
2203 throw Cr
.NS_ERROR_NO_INTERFACE
;
2207 // nsIBrowserSearchService
2208 function SearchService() {
2211 SearchService
.prototype = {
2213 _sortedEngines
: null,
2214 // Whether or not we need to write the order of engines on shutdown. This
2215 // needs to happen anytime _sortedEngines is modified after initial startup.
2216 _needToSetOrderPrefs
: false,
2219 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
2220 getService(Ci
.nsIPrefBranch
);
2221 var shouldLog
= false;
2223 shouldLog
= prefB
.getBoolPref(BROWSER_SEARCH_PREF
+ "log");
2227 // Replace the empty LOG function with the useful one
2231 engineMetadataService
.init();
2232 engineUpdateService
.init();
2234 this._addObservers();
2236 var fileLocator
= Cc
["@mozilla.org/file/directory_service;1"].
2237 getService(Ci
.nsIProperties
);
2238 var locations
= fileLocator
.get(NS_APP_SEARCH_DIR_LIST
,
2239 Ci
.nsISimpleEnumerator
);
2241 while (locations
.hasMoreElements()) {
2242 var location
= locations
.getNext().QueryInterface(Ci
.nsIFile
);
2243 this._loadEngines(location
);
2246 // Now that all engines are loaded, build the sorted engine list
2247 this._buildSortedEngineList();
2249 selectedEngineName
= getLocalizedPref(BROWSER_SEARCH_PREF
+
2251 this._currentEngine
= this.getEngineByName(selectedEngineName
) ||
2255 _addEngineToStore
: function SRCH_SVC_addEngineToStore(aEngine
) {
2256 LOG("_addEngineToStore: Adding engine: \"" + aEngine
.name
+ "\"");
2258 // See if there is an existing engine with the same name. However, if this
2259 // engine is updating another engine, it's allowed to have the same name.
2260 var hasSameNameAsUpdate
= (aEngine
._engineToUpdate
&&
2261 aEngine
.name
== aEngine
._engineToUpdate
.name
);
2262 if (aEngine
.name
in this._engines
&& !hasSameNameAsUpdate
) {
2263 LOG("_addEngineToStore: Duplicate engine found, aborting!");
2267 if (aEngine
._engineToUpdate
) {
2268 // We need to replace engineToUpdate with the engine that just loaded.
2269 var oldEngine
= aEngine
._engineToUpdate
;
2271 // Remove the old engine from the hash, since it's keyed by name, and our
2272 // name might change (the update might have a new name).
2273 delete this._engines
[oldEngine
.name
];
2275 // Hack: we want to replace the old engine with the new one, but since
2276 // people may be holding refs to the nsISearchEngine objects themselves,
2277 // we'll just copy over all "private" properties (those without a getter
2278 // or setter) from one object to the other.
2279 for (var p
in aEngine
) {
2280 if (!(aEngine
.__lookupGetter__(p
) || aEngine
.__lookupSetter__(p
)))
2281 oldEngine
[p
] = aEngine
[p
];
2283 aEngine
= oldEngine
;
2284 aEngine
._engineToUpdate
= null;
2286 // Add the engine back
2287 this._engines
[aEngine
.name
] = aEngine
;
2288 notifyAction(aEngine
, SEARCH_ENGINE_CHANGED
);
2290 // Not an update, just add the new engine.
2291 this._engines
[aEngine
.name
] = aEngine
;
2292 // Only add the engine to the list of sorted engines if the initial list
2293 // has already been built (i.e. if this._sortedEngines is non-null). If
2294 // it hasn't, we're still loading engines from disk, and will build the
2295 // sorted engine list when that initial loading is done.
2296 if (this._sortedEngines
) {
2297 this._sortedEngines
.push(aEngine
);
2298 this._needToSetOrderPrefs
= true;
2300 notifyAction(aEngine
, SEARCH_ENGINE_ADDED
);
2303 if (aEngine
._hasUpdates
) {
2304 // Schedule the engine's next update, if it isn't already.
2305 if (!engineMetadataService
.getAttr(aEngine
, "updateexpir"))
2306 engineUpdateService
.scheduleNextUpdate(aEngine
);
2308 // We need to save the engine's _dataType, if this is the first time the
2309 // engine is added to the dataStore, since ._dataType isn't persisted
2310 // and will change on the next startup (since the engine will then be
2311 // XML). We need this so that we know how to load any future updates from
2313 if (!engineMetadataService
.getAttr(aEngine
, "updatedatatype"))
2314 engineMetadataService
.setAttr(aEngine
, "updatedatatype",
2319 _loadEngines
: function SRCH_SVC_loadEngines(aDir
) {
2320 LOG("_loadEngines: Searching in " + aDir
.path
+ " for search engines.");
2322 // Check whether aDir is the user profile dir
2323 var isInProfile
= aDir
.equals(getDir(NS_APP_USER_SEARCH_DIR
));
2325 var files
= aDir
.directoryEntries
2326 .QueryInterface(Ci
.nsIDirectoryEnumerator
);
2327 var ios
= Cc
["@mozilla.org/network/io-service;1"].
2328 getService(Ci
.nsIIOService
);
2330 while (files
.hasMoreElements()) {
2331 var file
= files
.nextFile
;
2333 // Ignore hidden and empty files, and directories
2334 if (!file
.isFile() || file
.fileSize
== 0 || file
.isHidden())
2337 var fileURL
= ios
.newFileURI(file
).QueryInterface(Ci
.nsIURL
);
2338 var fileExtension
= fileURL
.fileExtension
.toLowerCase();
2339 var isWritable
= isInProfile
&& file
.isWritable();
2342 switch (fileExtension
) {
2344 dataType
= SEARCH_DATA_XML
;
2346 case SHERLOCK_FILE_EXT
:
2347 dataType
= SEARCH_DATA_TEXT
;
2354 var addedEngine
= null;
2356 addedEngine
= new Engine(file
, dataType
, !isWritable
);
2357 addedEngine
._initFromFile();
2359 LOG("_loadEngines: Failed to load " + file
.path
+ "!\n" + ex
);
2363 if (fileExtension
== SHERLOCK_FILE_EXT
) {
2366 this._convertSherlockFile(addedEngine
, fileURL
.fileBaseName
);
2368 LOG("_loadEngines: Failed to convert: " + fileURL
.path
+ "\n" + ex
);
2369 // The engine couldn't be converted, mark it as read-only
2370 addedEngine
._readOnly
= true;
2374 // If the engine still doesn't have an icon, see if we can find one
2375 if (!addedEngine
._iconURI
) {
2376 var icon
= this._findSherlockIcon(file
, fileURL
.fileBaseName
);
2378 addedEngine
._iconURI
= ios
.newFileURI(icon
);
2382 this._addEngineToStore(addedEngine
);
2386 _saveSortedEngineList
: function SRCH_SVC_saveSortedEngineList() {
2387 // We only need to write the prefs. if something has changed.
2388 if (!this._needToSetOrderPrefs
)
2391 // Set the useDB pref to indicate that from now on we should use the order
2392 // information stored in the database.
2393 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
2394 getService(Ci
.nsIPrefBranch
);
2395 prefB
.setBoolPref(BROWSER_SEARCH_PREF
+ "useDBForOrder", true);
2397 var engines
= this._getSortedEngines(true);
2401 for (var i
= 0; i
< engines
.length
; ++i
) {
2406 engineMetadataService
.setAttrs(engines
, names
, values
);
2409 _buildSortedEngineList
: function SRCH_SVC_buildSortedEngineList() {
2410 var addedEngines
= { };
2411 this._sortedEngines
= [];
2414 // If the user has specified a custom engine order, read the order
2415 // information from the engineMetadataService instead of the default
2417 if (getBoolPref(BROWSER_SEARCH_PREF
+ "useDBForOrder", false)) {
2418 for each (engine
in this._engines
) {
2419 var orderNumber
= engineMetadataService
.getAttr(engine
, "order");
2421 // Since the DB isn't regularly cleared, and engine files may disappear
2422 // without us knowing, we may already have an engine in this slot. If
2423 // that happens, we just skip it - it will be added later on as an
2424 // unsorted engine. This problem will sort itself out when we call
2425 // _saveSortedEngineList at shutdown.
2426 if (orderNumber
&& !this._sortedEngines
[orderNumber
-1]) {
2427 this._sortedEngines
[orderNumber
-1] = engine
;
2428 addedEngines
[engine
.name
] = engine
;
2430 // We need to call _saveSortedEngines so this gets sorted out.
2431 this._needToSetOrderPrefs
= true;
2435 // Filter out any nulls for engines that may have been removed
2436 var filteredEngines
= this._sortedEngines
.filter(function(a
) { return !!a
; });
2437 if (this._sortedEngines
.length
!= filteredEngines
.length
)
2438 this._needToSetOrderPrefs
= true;
2439 this._sortedEngines
= filteredEngines
;
2442 // The DB isn't being used, so just read the engine order from the prefs
2448 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
2449 getService(Ci
.nsIPrefBranch
);
2451 prefB
.getChildList(BROWSER_SEARCH_PREF
+ "order.extra.", { });
2453 for each (prefName
in extras
) {
2454 engineName
= prefB
.getCharPref(prefName
);
2456 engine
= this._engines
[engineName
];
2457 if (!engine
|| engine
.name
in addedEngines
)
2460 this._sortedEngines
.push(engine
);
2461 addedEngines
[engine
.name
] = engine
;
2467 engineName
= getLocalizedPref(BROWSER_SEARCH_PREF
+ "order." + (++i
));
2471 engine
= this._engines
[engineName
];
2472 if (!engine
|| engine
.name
in addedEngines
)
2475 this._sortedEngines
.push(engine
);
2476 addedEngines
[engine
.name
] = engine
;
2480 // Array for the remaining engines, alphabetically sorted
2481 var alphaEngines
= [];
2483 for each (engine
in this._engines
) {
2484 if (!(engine
.name
in addedEngines
))
2485 alphaEngines
.push(this._engines
[engine
.name
]);
2487 alphaEngines
= alphaEngines
.sort(function (a
, b
) {
2488 return a
.name
.localeCompare(b
.name
);
2490 this._sortedEngines
= this._sortedEngines
.concat(alphaEngines
);
2494 * Converts a Sherlock file and its icon into the custom XML format used by
2495 * the Search Service. Saves the engine's icon (if present) into the XML as a
2496 * data: URI and changes the extension of the source file from ".src" to
2497 * ".xml". The engine data is then written to the file as XML.
2499 * The Engine object that needs to be converted.
2501 * The basename of the Sherlock file.
2502 * Example: "foo" for file "foo.src".
2504 * @throws NS_ERROR_FAILURE if the file could not be converted.
2506 * @see nsIURL::fileBaseName
2508 _convertSherlockFile
: function SRCH_SVC_convertSherlock(aEngine
, aBaseName
) {
2509 var oldSherlockFile
= aEngine
._file
;
2511 // Back up the old file
2513 var backupDir
= oldSherlockFile
.parent
;
2514 backupDir
.append("searchplugins-backup");
2516 if (!backupDir
.exists())
2517 backupDir
.create(Ci
.nsIFile
.DIRECTORY_TYPE
, PERMS_DIRECTORY
);
2519 oldSherlockFile
.copyTo(backupDir
, null);
2521 // Just bail. Engines that can't be backed up won't be converted, but
2522 // engines that aren't converted are loaded as readonly.
2523 LOG("_convertSherlockFile: Couldn't back up " + oldSherlockFile
.path
+
2525 throw Cr
.NS_ERROR_FAILURE
;
2528 // Rename the file, but don't clobber existing files
2529 var newXMLFile
= oldSherlockFile
.parent
.clone();
2530 newXMLFile
.append(aBaseName
+ "." + XML_FILE_EXT
);
2532 if (newXMLFile
.exists()) {
2533 // There is an existing file with this name, create a unique file
2534 newXMLFile
.createUnique(Ci
.nsIFile
.NORMAL_FILE_TYPE
, PERMS_FILE
);
2537 // Rename the .src file to .xml
2538 oldSherlockFile
.moveTo(null, newXMLFile
.leafName
);
2540 aEngine
._file
= newXMLFile
;
2542 // Write the converted engine to disk
2543 aEngine
._serializeToFile();
2545 // Update the engine's _type.
2546 aEngine
._type
= SEARCH_TYPE_MOZSEARCH
;
2548 // See if it has a corresponding icon
2550 var icon
= this._findSherlockIcon(aEngine
._file
, aBaseName
);
2551 if (icon
&& icon
.fileSize
< MAX_ICON_SIZE
) {
2552 // Use this as the engine's icon
2553 var bStream
= Cc
["@mozilla.org/binaryinputstream;1"].
2554 createInstance(Ci
.nsIBinaryInputStream
);
2555 var fileInStream
= Cc
["@mozilla.org/network/file-input-stream;1"].
2556 createInstance(Ci
.nsIFileInputStream
);
2558 fileInStream
.init(icon
, MODE_RDONLY
, PERMS_FILE
, 0);
2559 bStream
.setInputStream(fileInStream
);
2562 while (bStream
.available() != 0)
2563 bytes
= bytes
.concat(bStream
.readByteArray(bStream
.available()));
2566 // Convert the byte array to a base64-encoded string
2567 var str
= btoa(String
.fromCharCode
.apply(null, bytes
));
2569 aEngine
._iconURI
= makeURI(ICON_DATAURL_PREFIX
+ str
);
2570 LOG("_importSherlockEngine: Set sherlock iconURI to: \"" +
2571 aEngine
._iconURL
+ "\"");
2573 // Write the engine to disk to save changes
2574 aEngine
._serializeToFile();
2576 // Delete the icon now that we're sure everything's been saved
2579 } catch (ex
) { LOG("_convertSherlockFile: Error setting icon:\n" + ex
); }
2583 * Finds an icon associated to a given Sherlock file. Searches the provided
2584 * file's parent directory looking for files with the same base name and one
2585 * of the file extensions in SHERLOCK_ICON_EXTENSIONS.
2586 * @param aEngineFile
2587 * The Sherlock plugin file.
2589 * The basename of the Sherlock file.
2590 * Example: "foo" for file "foo.src".
2591 * @see nsIURL::fileBaseName
2593 _findSherlockIcon
: function SRCH_SVC_findSherlock(aEngineFile
, aBaseName
) {
2594 for (var i
= 0; i
< SHERLOCK_ICON_EXTENSIONS
.length
; i
++) {
2595 var icon
= aEngineFile
.parent
.clone();
2596 icon
.append(aBaseName
+ SHERLOCK_ICON_EXTENSIONS
[i
]);
2597 if (icon
.exists() && icon
.isFile())
2604 * Get a sorted array of engines.
2605 * @param aWithHidden
2606 * True if hidden plugins should be included in the result.
2608 _getSortedEngines
: function SRCH_SVC_getSorted(aWithHidden
) {
2610 return this._sortedEngines
;
2612 return this._sortedEngines
.filter(function (engine
) {
2613 return !engine
.hidden
;
2617 // nsIBrowserSearchService
2618 getEngines
: function SRCH_SVC_getEngines(aCount
) {
2619 LOG("getEngines: getting all engines");
2620 var engines
= this._getSortedEngines(true);
2621 aCount
.value
= engines
.length
;
2625 getVisibleEngines
: function SRCH_SVC_getVisible(aCount
) {
2626 LOG("getVisibleEngines: getting all visible engines");
2627 var engines
= this._getSortedEngines(false);
2628 aCount
.value
= engines
.length
;
2632 getDefaultEngines
: function SRCH_SVC_getDefault(aCount
) {
2633 function isDefault(engine
) {
2634 return engine
._isDefault
;
2636 var engines
= this._sortedEngines
.filter(isDefault
);
2637 var engineOrder
= {};
2641 // Build a list of engines which we have ordering information for.
2642 // We're rebuilding the list here because _sortedEngines contain the
2643 // current order, but we want the original order.
2645 // First, look at the "browser.search.order.extra" branch.
2647 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
2648 getService(Ci
.nsIPrefBranch
);
2649 var extras
= prefB
.getChildList(BROWSER_SEARCH_PREF
+ "order.extra.",
2652 for each (var prefName
in extras
) {
2653 engineName
= prefB
.getCharPref(prefName
);
2655 if (!(engineName
in engineOrder
))
2656 engineOrder
[engineName
] = i
++;
2659 LOG("Getting extra order prefs failed: " + e
);
2662 // Now look through the "browser.search.order" branch.
2663 for (var j
= 1; ; j
++) {
2664 engineName
= getLocalizedPref(BROWSER_SEARCH_PREF
+ "order." + j
);
2668 if (!(engineName
in engineOrder
))
2669 engineOrder
[engineName
] = i
++;
2672 LOG("getDefaultEngines: engineOrder: " + engineOrder
.toSource());
2674 function compareEngines (a
, b
) {
2675 var aIdx
= engineOrder
[a
.name
];
2676 var bIdx
= engineOrder
[b
.name
];
2685 return a
.name
.localeCompare(b
.name
);
2687 engines
.sort(compareEngines
);
2689 aCount
.value
= engines
.length
;
2693 getEngineByName
: function SRCH_SVC_getEngineByName(aEngineName
) {
2694 return this._engines
[aEngineName
] || null;
2697 getEngineByAlias
: function SRCH_SVC_getEngineByAlias(aAlias
) {
2698 for (var engineName
in this._engines
) {
2699 var engine
= this._engines
[engineName
];
2700 if (engine
&& engine
.alias
== aAlias
)
2706 addEngineWithDetails
: function SRCH_SVC_addEWD(aName
, aIconURL
, aAlias
,
2707 aDescription
, aMethod
,
2709 ENSURE_ARG(aName
, "Invalid name passed to addEngineWithDetails!");
2710 ENSURE_ARG(aMethod
, "Invalid method passed to addEngineWithDetails!");
2711 ENSURE_ARG(aTemplate
, "Invalid template passed to addEngineWithDetails!");
2713 ENSURE(!this._engines
[aName
], "An engine with that name already exists!",
2714 Cr
.NS_ERROR_FILE_ALREADY_EXISTS
);
2716 var engine
= new Engine(getSanitizedFile(aName
), SEARCH_DATA_XML
, false);
2717 engine
._initFromMetadata(aName
, aIconURL
, aAlias
, aDescription
,
2718 aMethod
, aTemplate
);
2719 this._addEngineToStore(engine
);
2722 addEngine
: function SRCH_SVC_addEngine(aEngineURL
, aDataType
, aIconURL
,
2724 LOG("addEngine: Adding \"" + aEngineURL
+ "\".");
2726 var uri
= makeURI(aEngineURL
);
2727 var engine
= new Engine(uri
, aDataType
, false);
2728 engine
._initFromURI();
2730 LOG("addEngine: Error adding engine:\n" + ex
);
2731 throw Cr
.NS_ERROR_FAILURE
;
2733 engine
._setIcon(aIconURL
, false);
2734 engine
._confirm
= aConfirm
;
2737 removeEngine
: function SRCH_SVC_removeEngine(aEngine
) {
2738 ENSURE_ARG(aEngine
, "no engine passed to removeEngine!");
2740 var engineToRemove
= null;
2741 for (var e
in this._engines
)
2742 if (aEngine
.wrappedJSObject
== this._engines
[e
])
2743 engineToRemove
= this._engines
[e
];
2745 ENSURE(engineToRemove
, "removeEngine: Can't find engine to remove!",
2746 Cr
.NS_ERROR_FILE_NOT_FOUND
);
2748 if (engineToRemove
== this.currentEngine
)
2749 this._currentEngine
= null;
2751 if (engineToRemove
._readOnly
) {
2752 // Just hide it (the "hidden" setter will notify) and remove its alias to
2753 // avoid future conflicts with other engines.
2754 engineToRemove
.hidden
= true;
2755 engineToRemove
.alias
= null;
2757 // Cancel the lazy serialization timer if it's running
2758 if (engineToRemove
._serializeTimer
) {
2759 engineToRemove
._serializeTimer
.cancel();
2760 engineToRemove
._serializeTimer
= null;
2763 // Remove the engine file from disk (this might throw)
2764 engineToRemove
._remove();
2765 engineToRemove
._file
= null;
2767 // Remove the engine from _sortedEngines
2768 var index
= this._sortedEngines
.indexOf(engineToRemove
);
2769 ENSURE(index
!= -1, "Can't find engine to remove in _sortedEngines!",
2770 Cr
.NS_ERROR_FAILURE
);
2771 this._sortedEngines
.splice(index
, 1);
2773 // Remove the engine from the internal store
2774 delete this._engines
[engineToRemove
.name
];
2776 notifyAction(engineToRemove
, SEARCH_ENGINE_REMOVED
);
2778 // Since we removed an engine, we need to update the preferences.
2779 this._needToSetOrderPrefs
= true;
2783 moveEngine
: function SRCH_SVC_moveEngine(aEngine
, aNewIndex
) {
2784 ENSURE_ARG((aNewIndex
< this._sortedEngines
.length
) && (aNewIndex
>= 0),
2785 "SRCH_SVC_moveEngine: Index out of bounds!");
2786 ENSURE_ARG(aEngine
instanceof Ci
.nsISearchEngine
,
2787 "SRCH_SVC_moveEngine: Invalid engine passed to moveEngine!");
2788 ENSURE(!aEngine
.hidden
, "moveEngine: Can't move a hidden engine!",
2789 Cr
.NS_ERROR_FAILURE
);
2791 var engine
= aEngine
.wrappedJSObject
;
2793 var currentIndex
= this._sortedEngines
.indexOf(engine
);
2794 ENSURE(currentIndex
!= -1, "moveEngine: Can't find engine to move!",
2795 Cr
.NS_ERROR_UNEXPECTED
);
2797 // Our callers only take into account non-hidden engines when calculating
2798 // aNewIndex, but we need to move it in the array of all engines, so we
2799 // need to adjust aNewIndex accordingly. To do this, we count the number
2800 // of hidden engines in the list before the engine that we're taking the
2801 // place of. We do this by first finding newIndexEngine (the engine that
2802 // we were supposed to replace) and then iterating through the complete
2803 // engine list until we reach it, increasing aNewIndex for each hidden
2804 // engine we find on our way there.
2806 // This could be further simplified by having our caller pass in
2807 // newIndexEngine directly instead of aNewIndex.
2808 var newIndexEngine
= this._getSortedEngines(false)[aNewIndex
];
2809 ENSURE(newIndexEngine
, "moveEngine: Can't find engine to replace!",
2810 Cr
.NS_ERROR_UNEXPECTED
);
2812 for (var i
= 0; i
< this._sortedEngines
.length
; ++i
) {
2813 if (newIndexEngine
== this._sortedEngines
[i
])
2815 if (this._sortedEngines
[i
].hidden
)
2819 if (currentIndex
== aNewIndex
)
2820 return; // nothing to do!
2823 var movedEngine
= this._sortedEngines
.splice(currentIndex
, 1)[0];
2824 this._sortedEngines
.splice(aNewIndex
, 0, movedEngine
);
2826 notifyAction(engine
, SEARCH_ENGINE_CHANGED
);
2828 // Since we moved an engine, we need to update the preferences.
2829 this._needToSetOrderPrefs
= true;
2832 restoreDefaultEngines
: function SRCH_SVC_resetDefaultEngines() {
2833 for each (var e
in this._engines
) {
2834 // Unhide all default engines
2835 if (e
.hidden
&& e
._isDefault
)
2840 get defaultEngine() {
2841 const defPref
= BROWSER_SEARCH_PREF
+ "defaultenginename";
2842 // Get the default engine - this pref should always exist, but the engine
2844 this._defaultEngine
= this.getEngineByName(getLocalizedPref(defPref
, ""));
2845 if (!this._defaultEngine
|| this._defaultEngine
.hidden
)
2846 this._defaultEngine
= this._getSortedEngines(false)[0] || null;
2847 return this._defaultEngine
;
2850 get currentEngine() {
2851 if (!this._currentEngine
|| this._currentEngine
.hidden
)
2852 this._currentEngine
= this.defaultEngine
;
2853 return this._currentEngine
;
2855 set currentEngine(val
) {
2856 ENSURE_ARG(val
instanceof Ci
.nsISearchEngine
,
2857 "Invalid argument passed to currentEngine setter");
2859 var newCurrentEngine
= this.getEngineByName(val
.name
);
2860 ENSURE(newCurrentEngine
, "Can't find engine in store!",
2861 Cr
.NS_ERROR_UNEXPECTED
);
2863 this._currentEngine
= newCurrentEngine
;
2865 var currentEnginePref
= BROWSER_SEARCH_PREF
+ "selectedEngine";
2867 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
2868 getService(Ci
.nsIPrefService
).QueryInterface(Ci
.nsIPrefBranch
);
2870 if (this._currentEngine
== this.defaultEngine
) {
2871 if (prefB
.prefHasUserValue(currentEnginePref
))
2872 prefB
.clearUserPref(currentEnginePref
);
2875 setLocalizedPref(currentEnginePref
, this._currentEngine
.name
);
2878 notifyAction(this._currentEngine
, SEARCH_ENGINE_CURRENT
);
2882 observe
: function SRCH_SVC_observe(aEngine
, aTopic
, aVerb
) {
2884 case SEARCH_ENGINE_TOPIC
:
2885 if (aVerb
== SEARCH_ENGINE_LOADED
) {
2886 var engine
= aEngine
.QueryInterface(Ci
.nsISearchEngine
);
2887 LOG("nsSearchService::observe: Done installation of " + engine
.name
2889 this._addEngineToStore(engine
.wrappedJSObject
);
2890 if (engine
.wrappedJSObject
._useNow
) {
2891 LOG("nsSearchService::observe: setting current");
2892 this.currentEngine
= aEngine
;
2896 case QUIT_APPLICATION_TOPIC
:
2897 this._removeObservers();
2898 this._saveSortedEngineList();
2903 _addObservers
: function SRCH_SVC_addObservers() {
2904 var os
= Cc
["@mozilla.org/observer-service;1"].
2905 getService(Ci
.nsIObserverService
);
2906 os
.addObserver(this, SEARCH_ENGINE_TOPIC
, false);
2907 os
.addObserver(this, QUIT_APPLICATION_TOPIC
, false);
2910 _removeObservers
: function SRCH_SVC_removeObservers() {
2911 var os
= Cc
["@mozilla.org/observer-service;1"].
2912 getService(Ci
.nsIObserverService
);
2913 os
.removeObserver(this, SEARCH_ENGINE_TOPIC
);
2914 os
.removeObserver(this, QUIT_APPLICATION_TOPIC
);
2917 QueryInterface
: function SRCH_SVC_QI(aIID
) {
2918 if (aIID
.equals(Ci
.nsIBrowserSearchService
) ||
2919 aIID
.equals(Ci
.nsIObserver
) ||
2920 aIID
.equals(Ci
.nsISupports
))
2922 throw Cr
.NS_ERROR_NO_INTERFACE
;
2926 var engineMetadataService
= {
2927 init
: function epsInit() {
2928 var engineDataTable
= "id INTEGER PRIMARY KEY, engineid STRING, name STRING, value STRING";
2929 var file
= getDir(NS_APP_USER_PROFILE_50_DIR
);
2930 file
.append("search.sqlite");
2931 var dbService
= Cc
["@mozilla.org/storage/service;1"].
2932 getService(Ci
.mozIStorageService
);
2934 this.mDB
= dbService
.openDatabase(file
);
2936 if (ex
.result
== 0x8052000b) { /* NS_ERROR_FILE_CORRUPTED */
2937 // delete and try again
2939 this.mDB
= dbService
.openDatabase(file
);
2946 this.mDB
.createTable("engine_data", engineDataTable
);
2948 // Fails if the table already exists, which is fine
2951 this.mGetData
= createStatement (
2953 "SELECT value FROM engine_data WHERE engineid = :engineid AND name = :name");
2954 this.mDeleteData
= createStatement (
2956 "DELETE FROM engine_data WHERE engineid = :engineid AND name = :name");
2957 this.mInsertData
= createStatement (
2959 "INSERT INTO engine_data (engineid, name, value) " +
2960 "VALUES (:engineid, :name, :value)");
2962 getAttr
: function epsGetAttr(engine
, name
) {
2963 // attr names must be lower case
2964 name
= name
.toLowerCase();
2966 var stmt
= this.mGetData
;
2968 var pp
= stmt
.params
;
2969 pp
.engineid
= engine
._id
;
2974 value
= stmt
.row
.value
;
2979 setAttr
: function epsSetAttr(engine
, name
, value
) {
2980 // attr names must be lower case
2981 name
= name
.toLowerCase();
2983 this.mDB
.beginTransaction();
2985 var pp
= this.mDeleteData
.params
;
2986 pp
.engineid
= engine
._id
;
2988 this.mDeleteData
.step();
2989 this.mDeleteData
.reset();
2991 pp
= this.mInsertData
.params
;
2992 pp
.engineid
= engine
._id
;
2995 this.mInsertData
.step();
2996 this.mInsertData
.reset();
2998 this.mDB
.commitTransaction();
3001 setAttrs
: function epsSetAttrs(engines
, names
, values
) {
3002 this.mDB
.beginTransaction();
3004 for (var i
= 0; i
< engines
.length
; i
++) {
3005 // attr names must be lower case
3006 var name
= names
[i
].toLowerCase();
3008 var pp
= this.mDeleteData
.params
;
3009 pp
.engineid
= engines
[i
]._id
;
3011 this.mDeleteData
.step();
3012 this.mDeleteData
.reset();
3014 pp
= this.mInsertData
.params
;
3015 pp
.engineid
= engines
[i
]._id
;
3017 pp
.value
= values
[i
];
3018 this.mInsertData
.step();
3019 this.mInsertData
.reset();
3022 this.mDB
.commitTransaction();
3025 deleteEngineData
: function epsDelData(engine
, name
) {
3026 // attr names must be lower case
3027 name
= name
.toLowerCase();
3029 var pp
= this.mDeleteData
.params
;
3030 pp
.engineid
= engine
._id
;
3032 this.mDeleteData
.step();
3033 this.mDeleteData
.reset();
3037 const SEARCH_UPDATE_LOG_PREFIX
= "*** Search update: ";
3040 * Outputs aText to the JavaScript console as well as to stdout, if the search
3041 * logging pref (browser.search.update.log) is set to true.
3043 function ULOG(aText
) {
3044 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
3045 getService(Ci
.nsIPrefBranch
);
3046 var shouldLog
= false;
3048 shouldLog
= prefB
.getBoolPref(BROWSER_SEARCH_PREF
+ "update.log");
3052 dump(SEARCH_UPDATE_LOG_PREFIX
+ aText
+ "\n");
3053 var consoleService
= Cc
["@mozilla.org/consoleservice;1"].
3054 getService(Ci
.nsIConsoleService
);
3055 consoleService
.logStringMessage(aText
);
3059 var engineUpdateService
= {
3060 init
: function eus_init() {
3061 var tm
= Cc
["@mozilla.org/updates/timer-manager;1"].
3062 getService(Ci
.nsIUpdateTimerManager
);
3063 // figure out how often to check for any expired engines
3064 var prefB
= Cc
["@mozilla.org/preferences-service;1"].
3065 getService(Ci
.nsIPrefBranch
);
3066 var interval
= prefB
.getIntPref(BROWSER_SEARCH_PREF
+ "updateinterval");
3068 // Interval is stored in hours
3069 var seconds
= interval
* 3600;
3070 tm
.registerTimer("search-engine-update-timer", engineUpdateService
,
3074 scheduleNextUpdate
: function eus_scheduleNextUpdate(aEngine
) {
3075 var interval
= aEngine
._updateInterval
|| SEARCH_DEFAULT_UPDATE_INTERVAL
;
3076 var milliseconds
= interval
* 86400000; // |interval| is in days
3077 engineMetadataService
.setAttr(aEngine
, "updateexpir",
3078 Date
.now() + milliseconds
);
3081 notify
: function eus_Notify(aTimer
) {
3082 ULOG("notify called");
3084 if (!getBoolPref(BROWSER_SEARCH_PREF
+ "update", true))
3087 // Our timer has expired, but unfortunately, we can't get any data from it.
3088 // Therefore, we need to walk our engine-list, looking for expired engines
3089 var searchService
= Cc
["@mozilla.org/browser/search-service;1"].
3090 getService(Ci
.nsIBrowserSearchService
);
3091 var currentTime
= Date
.now();
3092 ULOG("currentTime: " + currentTime
);
3093 for each (engine
in searchService
.getEngines({})) {
3094 engine
= engine
.wrappedJSObject
;
3095 if (!engine
._hasUpdates
|| engine
._readOnly
)
3098 ULOG("checking " + engine
.name
);
3100 var expirTime
= engineMetadataService
.getAttr(engine
, "updateexpir");
3101 var updateURL
= engine
._updateURL
;
3102 var iconUpdateURL
= engine
._iconUpdateURL
;
3103 ULOG("expirTime: " + expirTime
+ "\nupdateURL: " + updateURL
+
3104 "\niconUpdateURL: " + iconUpdateURL
);
3106 var engineExpired
= expirTime
<= currentTime
;
3108 if (!expirTime
|| !engineExpired
) {
3109 ULOG("skipping engine");
3113 ULOG(engine
.name
+ " has expired");
3115 var testEngine
= null;
3117 var updateURI
= makeURI(updateURL
);
3119 var dataType
= engineMetadataService
.getAttr(engine
, "updatedatatype")
3121 ULOG("No loadtype to update engine!");
3125 testEngine
= new Engine(updateURI
, dataType
, false);
3126 testEngine
._engineToUpdate
= engine
;
3127 testEngine
._initFromURI();
3129 ULOG("invalid updateURI");
3131 if (iconUpdateURL
) {
3132 // If we're updating the engine too, use the new engine object,
3133 // otherwise use the existing engine object.
3134 (testEngine
|| engine
)._setIcon(iconUpdateURL
, true);
3137 // Schedule the next update
3138 this.scheduleNextUpdate(engine
);
3140 } // end engine iteration
3144 const kClassID
= Components
.ID("{7319788a-fe93-4db3-9f39-818cf08f4256}");
3145 const kClassName
= "Browser Search Service";
3146 const kContractID
= "@mozilla.org/browser/search-service;1";
3150 createInstance: function (outer
, iid
) {
3152 throw Cr
.NS_ERROR_NO_AGGREGATION
;
3153 return (new SearchService()).QueryInterface(iid
);
3159 registerSelf: function (componentManager
, fileSpec
, location
, type
) {
3160 componentManager
.QueryInterface(Ci
.nsIComponentRegistrar
);
3161 componentManager
.registerFactoryLocation(kClassID
,
3164 fileSpec
, location
, type
);
3167 unregisterSelf: function(componentManager
, fileSpec
, location
) {
3168 componentManager
.QueryInterface(Ci
.nsIComponentRegistrar
);
3169 componentManager
.unregisterFactoryLocation(kClassID
, fileSpec
);
3172 getClassObject: function (componentManager
, cid
, iid
) {
3173 if (!cid
.equals(kClassID
))
3174 throw Cr
.NS_ERROR_NO_INTERFACE
;
3175 if (!iid
.equals(Ci
.nsIFactory
))
3176 throw Cr
.NS_ERROR_NOT_IMPLEMENTED
;
3180 canUnload: function (componentManager
) {
3185 function NSGetModule(componentManager
, fileSpec
) {
3189 #include
../../../toolkit/content/debug
.js