Monday, February 11, 2008

Mozilla Project Update: Release v.02

Mozilla Project: Localized Search in Firefox Search Box - Release v.02

My objectives for this release were to dynamically "Add" a search engine plugin when Firefox 3 (i.e. Minefield) loads a web page with a search plugin, and then propagate the search engine to the top of the searchbar's menu as the current engine. The propagation of a newly added search engine as the current engine in the searchbar's menu was already a behavioral feature of Firefox 3 so there were no changes required for this part of my release target. Determining how to dynamically "Add" an available search engine plugin was challenging to achieve, but in the end, it only required a few minor modifications to the code in the browser.js file.

Source Code Description and Modification

As I mentioned in my previous discussion of the source code for searchbar events, when Firefox 3 loads a web page containing a <link> element, a "DOMLinkAdded" event fires. The browser has an event listener for "DOMLinkAdded" events and it employs an event handler named DOMLinkHandler. This event handler calls its onLinkAdded() function, which creates a generic engine object if the page's <link> element's attributes have valid values. Subsequently, the engine object is passed to the BrowserSearch object's addEngine() function.

During the normal flow of code execution, the addEngine() function receives a reference to an engine object as one of its parameters. The function uses searchService, an nsIBrowserSearchService object, to determine if the search engine is already on the list of engines. If an nsISearchEngine object with a name value matching the generic engine object's title property is already on the list, then it is considered to be a "hidden" engine and it is "pushed" onto the browser.hiddenEngines array. This array is used by the search.xml file to determine how to populate and display the search engines on the searchbar menu. If the search engine is not on the list, it gets pushed onto the "non-hidden", browser.engines array. In this case, the search engine would then be displayed as an "Add <Search Engine>" item on the searchbar menu and the searchbar button's background color would be changed to blue after a call to the updateSearchButton() function.

My code modifications change the BrowserSearch addEngine() function by adding the new search plugin if it is NOT found on the existing list of engines. I dynamically "Add" the search plugin by calling the addEngine() function defined in the nsSearchService.js file. This function accomplishes the important step of creating a new nsISearchEngine object. It is this type of object that is required by methods in search.xml, such as observe(), offerNewEngine() and hideNewEngine(). The remainder of the code in the BrowserSearch addEngine() function follows the normal paths of execution.

My additions to the code in the browser.js file are denoted with plus signs in the patch file shown below:

? localsearchpatch_v02.txt
? nohup.out
? objdir-ff-debug
Index: browser/base/content/browser.js
===================================================================
RCS file: /cvsroot/mozilla/browser/base/content/browser.js,v
retrieving revision 1.961
diff -u -8 -p -r1.961 browser.js
--- browser/base/content/browser.js 10 Feb 2008 06:57:05 -0000 1.961
+++ browser/base/content/browser.js 11 Feb 2008 19:57:26 -0000
@@ -2780,16 +2780,19 @@ const BrowserSearch = {
// If this engine (identified by title) is already in the list, add it
// to the list of hidden engines rather than to the main list.
// XXX This will need to be changed when engines are identified by URL;
// see bug 335102.
var searchService = Cc["@mozilla.org/browser/search-service;1"].
getService(Ci.nsIBrowserSearchService);
if (searchService.getEngineByName(engine.title))
hidden = true;
+ else
+ // Dynamically "Add" the web site's search engine plugin.
+ searchService.addEngine(engine.href, Components.interfaces.nsISearchEngine.DATA_XML, iconURL, false);

var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];

engines.push({ uri: engine.href,
title: engine.title,
icon: iconURL });

if (hidden)
See my project wiki page for information about how to apply and use this patch file.

Difficulties and Lessons Learned

Although the solution was straightforward for what I was essentially attempting to achieve with this release, I had some difficulties understanding how to arrive at it. From the outset, I understood that I would likely need to make changes to BrowserSearch's addEngine() function. My first thoughts were to pass the generic engine object to the searchbar's hideNewEngine() or observe() functions as shown in the following code examples:

Example 1

if (hidden)
browser.hiddenEngines = engines;
else {
browser.engines = engines;
this.searchBar.hideNewEngine(engine);
if (browser == gBrowser || browser == gBrowser.mCurrentBrowser)
this.updateSearchButton();
}

Example 2

if (hidden)
browser.hiddenEngines = engines;
else {
browser.engines = engines;
this.searchBar.observe(engine, "browser-search-engine-modified", "engine-added");
if (browser == gBrowser || browser == gBrowser.mCurrentBrowser)
this.updateSearchButton();
}
The results of these code modifications were that the search plugins were not dynamically added and they remained as "Add <Search Engine>" items on the searchbar menu. The following JavaScript error message was produced from the above code changes:

************************************************************
* Call to xpconnect wrapped JSObject produced this error: *
[Exception... "'[JavaScript Error: "aEngine.wrappedJSObject is undefined" {file: "chrome://browser/content/search/search.xml" line: 256}]' when calling method: [nsIDOMEventListener::handleEvent]" nsresult: "0x80570021 (NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS)" location: "<unknown>" data: yes]
************************************************************

I later learned from Gavin on the #seneca irc channel that I was attempting to pass generic engine objects to the functions in search.xml. However, these functions work with nsISearchEngine objects and it was ineffective to use a searchBar object (i.e. this.searchBar) to pass them to the functions in search.xml. I needed to use an nsIBrowserSearchService object to "Add" the search engine to the search service's list because the search service only deals with nsISearchEngine objects.

I also made unsuccessful attempts to dynamically load the search plugin by simply adding the generic engine object to the browser.hiddenEngines array in BrowserSearch's addEngine() function and by removing any code that would add it to the browser.engines array. However, these code changes and similar other ones did not produce the desired results for this release.

No comments: