335443 - support <link> autodetection of search engines patch by Pam Greene <pamg.bugs@gmail.com> r=brettw@gmail.com sr=ben@mozilla.org

git-svn-id: svn://10.0.0.236/trunk@196086 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
beng%bengoodger.com
2006-05-06 02:37:51 +00:00
parent 6bf2aa0a46
commit 6653745c48
13 changed files with 242 additions and 36 deletions

View File

@@ -37,6 +37,7 @@
# Tom Germeau <tom.germeau@epigoon.com>
# Jesse Ruderman <jruderman@gmail.com>
# Joe Hughes <joe@retrovirus.com>
# Pamela Greene <pamg.bugs@gmail.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -914,6 +915,9 @@ function prepareForStartup()
// Initialize the feedhandler
FeedHandler.init();
// Initialize the searchbar
BrowserSearch.init();
}
function delayedStartup()
@@ -2886,6 +2890,85 @@ var DownloadsButtonDNDObserver = {
}
const BrowserSearch = {
/**
* Initialize the BrowserSearch
*/
init: function() {
gBrowser.addEventListener("DOMLinkAdded",
function (event) { BrowserSearch.onLinkAdded(event); },
false);
},
/**
* A new <link> tag has been discovered - check to see if it advertises
* a OpenSearch engine.
*/
onLinkAdded: function(event) {
// XXX this event listener can/should probably be combined with the onLinkAdded
// listener in tabbrowser.xml. See comments in FeedHandler.onLinkAdded().
const target = event.target;
var erel = target.rel;
var etype = target.type;
var etitle = target.title;
var ehref = target.href;
const searchRelRegex = /(^|\s)search($|\s)/i;
const searchHrefRegexHttp = /^http:\/\//i;
const searchHrefRegexHttps = /^https:\/\//i;
if (!etype)
return;
if (etype == "application/opensearchdescription+xml" &&
searchRelRegex.test(erel) &&
(searchHrefRegexHttp.test(ehref) || searchHrefRegexHttps.test(ehref)))
{
const targetDoc = target.ownerDocument;
// Set the attribute of the (first) search button.
var searchButton = document.getAnonymousElementByAttribute(this.getSearchBar(),
"anonid", "searchbar-dropmarker");
if (searchButton) {
var browser = gBrowser.getBrowserForDocument(targetDoc);
// Append the URI and an appropriate title to the browser data.
var engines = [];
if (browser.engines)
engines = browser.engines;
var iconURL = null;
if (gBrowser.shouldLoadFavIcon(browser.currentURI))
iconURL = browser.currentURI.prePath + "/favicon.ico";
var usableTitle = target.title || browser.contentTitle || target.href;
engines.push({ uri: target.href,
title: usableTitle,
icon: iconURL });
browser.engines = engines;
if (browser == gBrowser || browser == gBrowser.mCurrentBrowser)
this.updateSearchButton();
}
}
},
/**
* Update the browser UI to show whether or not additional engines are
* available when a page is loaded or the user switches tabs to a page that
* has search engines.
*/
updateSearchButton: function() {
var searchButton = document.getAnonymousElementByAttribute(this.getSearchBar(),
"anonid", "searchbar-dropmarker");
if (!searchButton)
return;
var engines = gBrowser.mCurrentBrowser.engines;
if (!engines || engines.length == 0) {
if (searchButton.hasAttribute("addengines"))
searchButton.removeAttribute("addengines");
}
else {
searchButton.setAttribute("addengines", "true");
}
},
/**
* Gives focus to the search bar, if it is present on the toolbar, or loads
* the default engine's search form otherwise. For Mac, opens a new window
@@ -2951,7 +3034,7 @@ const BrowserSearch = {
} else
loadURI(submission.uri.spec, null, submission.postData, false);
},
/**
* Returns the search bar element if it is present in the toolbar and not
* hidden, null otherwise.
@@ -3702,6 +3785,7 @@ nsBrowserStatusHandler.prototype =
asyncUpdateUI : function () {
FeedHandler.updateFeeds();
BrowserSearch.updateSearchButton();
#ifdef ALTSS_ICON
updatePageStyles();
#endif
@@ -3793,6 +3877,9 @@ nsBrowserStatusHandler.prototype =
// clear out feed data
gBrowser.mCurrentBrowser.feeds = null;
// clear out search-engine data
gBrowser.mCurrentBrowser.engines = null;
const nsIChannel = Components.interfaces.nsIChannel;
var urlStr = aRequest.QueryInterface(nsIChannel).URI.spec;
var observerService = Components.classes["@mozilla.org/observer-service;1"]
@@ -6310,9 +6397,7 @@ var FeedHandler = {
const targetDoc = event.target.ownerDocument;
// find which tab this is for, and set the attribute on the browser
// should there be a getTabForDocument method on tabbedbrowser?
var shellInfo = this._getContentShell(targetDoc);
var browserForLink = shellInfo.browser;
var browserForLink = gBrowser.getBrowserForDocument(targetDoc);
if (!browserForLink) {
// ??? this really shouldn't happen..
return;

View File

@@ -65,14 +65,6 @@ var SubscriptionOptions = {
catch (e) {
}
var reader = document.getElementById("reader");
try {
reader.value = prefs.getCharPref(PREF_SELECTED_HANDLER);
}
catch (e) {
reader.value = "bookmarks";
}
var clientApp = document.getElementById("clientApp");
try {
clientApp.file =
@@ -118,6 +110,14 @@ var SubscriptionOptions = {
catch (e) {
webService.selectedIndex = 0;
}
var reader = document.getElementById("reader");
try {
reader.value = prefs.getCharPref(PREF_SELECTED_HANDLER);
}
catch (e) {
reader.value = "bookmarks";
}
},
populateWebHandlers: function SO_populateWebHandlers(popup) {

View File

@@ -24,6 +24,7 @@
# Pierre Chanial (v2) <p_ch@verizon.net>
# Gavin Sharp (v3) <gavin@gavinsharp.com>
# Ben Goodger <beng@google.com>
# Pamela Greene <pamg.bugs@gmail.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -198,6 +199,67 @@
]]></body>
</method>
<!-- Rebuilds the dynamic portion of the popup menu (i.e., the menu items
for new search engines that can be added to the available list). This
is called each time the popup is shown.
-->
<method name="rebuildPopupDynamic">
<body><![CDATA[
var popup = this._popup;
// Clear any addengine menuitems, including addengine-item entries and
// the addengine-separator. Work backward to avoid invalidating the
// indexes as items are removed.
var items = popup.childNodes;
for (var i = items.length - 1; i >= 0; i--) {
if (items[i].getAttribute("class").indexOf("addengine") != -1)
popup.removeChild(items[i]);
}
var addengines = getBrowser().mCurrentBrowser.engines;
if (addengines && addengines.length > 0) {
const kXULNS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// Find the (first) separator in the remaining menu, or the first item
// if no separators are present.
var insertLocation = popup.firstChild;
while (insertLocation.nextSibling &&
insertLocation.localName != "menuseparator") {
insertLocation = insertLocation.nextSibling;
}
if (insertLocation.localName != "menuseparator")
insertLocation = popup.firstChild;
var separator = document.createElementNS(kXULNS, "menuseparator");
separator.setAttribute("class", "addengine-separator");
popup.insertBefore(separator, insertLocation);
// Insert the "add this engine" items.
for (var i = 0; i < addengines.length; i++) {
menuitem = document.createElement("menuitem");
var engineInfo = addengines[i];
var labelStr =
this._stringBundle.getFormattedString("cmd_addFoundEngine",
[engineInfo.title]);
menuitem = document.createElementNS(kXULNS, "menuitem");
menuitem.setAttribute("class", "menuitem-iconic addengine-item");
menuitem.setAttribute("label", labelStr);
menuitem.setAttribute("tooltiptext", engineInfo.uri);
menuitem.setAttribute("uri", engineInfo.uri);
if (engineInfo.icon)
menuitem.setAttribute("src", engineInfo.icon);
menuitem.setAttribute("title", engineInfo.title);
popup.insertBefore(menuitem, insertLocation);
}
}
]]></body>
</method>
<!-- Rebuilds the list of visible search engines in the menu. Does not remove
or update any dynamic entries (i.e., "Add this engine" items) nor the
Manage Engines item. This is called by the observer when the list of
visible engines, or the currently selected engine, has changed.
-->
<method name="rebuildPopup">
<body><![CDATA[
var popup = this._popup;
@@ -206,12 +268,11 @@
while (popup.firstChild && popup.firstChild.localName != "menuseparator")
popup.removeChild(popup.firstChild);
this._engines = this.searchService.getVisibleEngines({ });
const kXULNS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// Prepend engines
// Prepend visible engines
this._engines = this.searchService.getVisibleEngines({ });
for (var i = this._engines.length - 1; i >= 0; --i) {
var menuitem = document.createElementNS(kXULNS, "menuitem");
var name = this._engines[i].name;
@@ -259,7 +320,23 @@
<method name="onEnginePopupCommand">
<parameter name="aTarget"/>
<body><![CDATA[
if (aTarget.engine) {
if (aTarget.getAttribute("class").indexOf("addengine-item") != -1) {
var searchService = Components
.classes["@mozilla.org/browser/search-service;1"]
.getService(Components.interfaces.nsIBrowserSearchService);
if (searchService) {
// If the description file URI ends in "xml", assume an XML file;
// otherwise, assume text. That's about the best we can do without
// loading the file itself.
const engineURI = aTarget.getAttribute("uri");
var type = Components.interfaces.nsISearchEngine.DATA_TEXT;
if (engineURI.search(/\.xml$/i))
type = Components.interfaces.nsISearchEngine.DATA_XML;
searchService.addEngine(engineURI, type,
aTarget.getAttribute("src"));
}
}
else if (aTarget.engine) {
this.currentEngine = aTarget.engine;
this.focus();
this.select();
@@ -335,9 +412,12 @@
this.handleSearchCommand(event);
else if (anonid == "open-engine-manager")
this.openManager(event);
else if (target.getAttribute("class").indexOf("searchbar-engine-menuitem") != -1)
else if (target.getAttribute("class").indexOf("addengine-item") != -1 ||
target.engine)
this.onEnginePopupCommand(target);
]]></handler>
]]></handler>
<handler event="popupshowing" action="this.rebuildPopupDynamic();"/>
</handlers>
</binding>
@@ -400,8 +480,8 @@
<![CDATA[
// Don't open search popup if history popup is open
if (!this.popupOpen) {
document.getAnonymousElementByAttribute(this.parentNode,
"anonid", "searchbar-dropmarker").click();
document.getAnonymousElementByAttribute(this,
"anonid", "searchbar-popup").click();
return false;
}
return true;

View File

@@ -90,8 +90,13 @@ const DEFAULT_QUERY_CHARSET = "ISO-8859-1";
const SEARCH_BUNDLE = "chrome://browser/locale/search.properties";
const BRAND_BUNDLE = "chrome://branding/locale/brand.properties";
// Although the specification at http://opensearch.a9.com/spec/1.1/description/#autodiscovery
// gives the _alt versions of the namespace names, many existing opensearch engines
// are using the former versions. We therefore allow either.
const kOpenSearchNS_10 = "http://a9.com/-/spec/opensearchdescription/1.0/";
const kOpenSearchNS_11 = "http://a9.com/-/spec/opensearchdescription/1.1/";
const kOpenSearchNS_10_alt = "http://a9.com/-/spec/opensearch/1.0/";
const kOpenSearchNS_11_alt = "http://a9.com/-/spec/opensearch/1.1/";
const kOpenSearchLocalName = "OpenSearchDescription";
const kMozSearchNS_10 = "http://www.mozilla.org/2006/browser/search/";
@@ -106,12 +111,13 @@ const EMPTY_DOC = "<?xml version=\"1.0\"?>\n" +
const BROWSER_SEARCH_PREF = "browser.search.";
// Unsupported search parameters.
// Unsupported search parameters, which will be replaced with blanks.
// XXX We do use inputEncoding - should consider having it available. This
// would require doing multiple parameter substition, so just having
// would require doing multiple parameter substitution, so just having
// searchTerms is sufficient for now.
const kIllegalWords = /(\{count\})|(\{startIndex\})|(\{startPage\})|(\{language\})|(\{outputEncoding\})|(\{inputEncoding\})/;
const kInvalidWords = /(\{count\})|(\{startIndex\})|(\{startPage\})|(\{language\})|(\{outputEncoding\})|(\{inputEncoding\})/;
// Supported search parameters.
const kValidWords = /\{searchTerms\}/gi;
const kUserDefined = "{searchTerms}";
@@ -581,12 +587,12 @@ function notifyAction(aEngine, aVerb) {
* Simple object representing a name/value pair.
* @throws NS_ERROR_NOT_IMPLEMENTED if the provided value includes unsupported
* parameters.
* @see kIllegalWords.
* @see kInvalidWords.
*/
function QueryParameter(aName, aValue) {
ENSURE_ARG(aName && aValue, "missing name or value for QueryParameter!");
ENSURE(!kIllegalWords.test(aValue),
ENSURE(!kInvalidWords.test(aValue),
"Illegal value while creating a QueryParameter",
Cr.NS_ERROR_NOT_IMPLEMENTED);
@@ -610,10 +616,10 @@ function QueryParameter(aName, aValue) {
*
* @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
*
* @throws NS_ERROR_NOT_IMPLEMENTED if aType is unsupported, or if aTemplate
* includes unsupported parameters.
*
* @see kIllegalWords.
* @throws NS_ERROR_NOT_IMPLEMENTED if aType is unsupported. If invalid
* (unsupported) parameters are included in aTemplate, they will be
* replaced with blanks in the final query, so no error needs to be
* returned here.
*/
function EngineURL(aType, aMethod, aTemplate) {
ENSURE_ARG(aType && aMethod && aTemplate,
@@ -628,9 +634,6 @@ function EngineURL(aType, aMethod, aTemplate) {
ENSURE(type == "text/html", "EngineURLs must be of type text/html!",
Cr.NS_ERROR_NOT_IMPLEMENTED);
ENSURE(!kIllegalWords.test(aTemplate), "Invalid URL parameter!",
Cr.NS_ERROR_NOT_IMPLEMENTED);
this.type = type;
this.method = method;
this.template = aTemplate;
@@ -647,6 +650,8 @@ EngineURL.prototype = {
* From an array of QueryParameter objects, generates a string in the
* application/x-www-form-urlencoded format:
* name=value&name=value&name=value...
* Any invalid or unimplemented query fields will be replqaced with empty
* strings.
* @param aParams
* An array of QueryParameter objects
* @param aData
@@ -654,6 +659,8 @@ EngineURL.prototype = {
* |kValidWords| regexp
* @returns A string of encoded param names and values in
* application/x-www-form-urlencoded format.
*
* @see kInvalidWords
*/
function makeQueryString(aParams, aData) {
var str = "";
@@ -665,7 +672,9 @@ EngineURL.prototype = {
return str;
}
// Replace known fields with given parameters and clear unknown fields.
var url = this.template.replace(kValidWords, aData);
url = url.replace(kInvalidWords, "");
var postData = null;
var dataString = makeQueryString(this.params, aData);
if (this.method == "GET") {
@@ -1010,7 +1019,9 @@ Engine.prototype = {
this._parseAsMozSearch();
} else if (checkNameSpace(this._data, [kOpenSearchLocalName],
[kOpenSearchNS_11, kOpenSearchNS_10])) {
[kOpenSearchNS_11, kOpenSearchNS_10]) ||
checkNameSpace(this._data, [kOpenSearchLocalName],
[kOpenSearchNS_11_alt, kOpenSearchNS_10_alt])) {
LOG("_init: Initing OpenSearch plugin from " + this._location);
@@ -1073,6 +1084,10 @@ Engine.prototype = {
var method = aElement.getAttribute("method");
var template = aElement.getAttribute("template");
// According to the spec, method is an optional attribute, defaulting to "get".
if (!method)
method = "get";
var url = new EngineURL(type, method, template);
for (var i = 0; i < aElement.childNodes.length; ++i) {

View File

@@ -6,3 +6,7 @@ cmd_clearHistory_accesskey=C
error_loading_engine_title=Download Error
error_loading_engine_msg=%S could not download the search plugin from:\n%S\n\nPlease try again or contact the author.
cmd_addFoundEngine=Add "%S"
cmd_addEngine=Add More Engines...
cmd_addEngine_accesskey=A

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -23,6 +23,7 @@ classic.jar:
skin/classic/browser/search-bar-background.png
skin/classic/browser/Search-bar.png
skin/classic/browser/Search.png
skin/classic/browser/Search-add-engines.png
skin/classic/browser/Secure.png
skin/classic/browser/Security-broken.png
skin/classic/browser/Secure-urlbar.png

View File

@@ -33,6 +33,10 @@
padding: 0px;
}
.search-go-button[addengines="true"] {
list-style-image: url("chrome://browser/skin/Search-add-engines.png");
}
.search-go-button:hover {
-moz-image-region: rect(0px 32px 16px 16px);
}
@@ -45,6 +49,6 @@
-moz-image-region: rect(0px, 48px, 16px, 32px);
}
.searchbar-engine-menuitem[selected="true"] .menu-iconic-text {
.searchbar-engine-menuitem[selected="true"] > .menu-iconic-text {
font-weight: bold;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -14,6 +14,7 @@ classic.jar:
skin/classic/browser/search-arrow.gif
skin/classic/browser/Search-bar.png
skin/classic/browser/Search.png
skin/classic/browser/Search-add-engines.png
skin/classic/browser/Secure.png
skin/classic/browser/Security-broken.png
skin/classic/browser/Throbber.gif

View File

@@ -52,6 +52,10 @@
padding-bottom: 0px;
}
.search-go-button[addengines="true"] {
list-style-image: url("chrome://browser/skin/Search-add-engines.png");
}
.search-go-button:hover {
-moz-image-region: rect(0px 32px 16px 16px);
}
@@ -64,7 +68,6 @@
-moz-image-region: rect(0px, 48px, 16px, 32px);
}
.searchbar-engine-menuitem[selected="true"] .menu-iconic-text {
.searchbar-engine-menuitem[selected="true"] > .menu-iconic-text {
font-weight: bold;
}

View File

@@ -60,6 +60,7 @@ DIRS += \
ifndef MOZ_SUITE
# XXX Suite doesn't want these just yet
DIRS += \
feeds \
typeaheadfind \
viewconfig \
$(NULL)

View File

@@ -214,6 +214,18 @@
</body>
</method>
<method name="getBrowserForDocument">
<parameter name="aDocument"/>
<body>
<![CDATA[
var index = this.getBrowserIndexForDocument(aDocument);
if (index < 0)
return null;
return this.getBrowserAtIndex(index);
]]>
</body>
</method>
<method name="getNotificationBox">
<parameter name="aBrowser"/>
<body>