/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

Browser = {};
Browser.Database = {};
Browser.Database.name = 'browser_data';
Browser.Database.displayName = 'BrowserData';
Browser.Database.version = 1;
Browser.Database.estimatedSize = 45*1024;
// 
//  NetworkMonitor.js
//  browser
//  
//  Copyright 2008-2009 Palm, Inc. All rights reserved.
//
function NetworkMonitor() {
	
	// Always assume true and let the system tell us otherwise.
	this.online = true;
	
	// We need to listen on connection manager state
	this._connMgrService = new Mojo.Service.Request('palm://com.palm.bus/signal', {
		method: 'registerServerStatus',
		parameters: {
			'serviceName':'com.palm.connectionmanager'
		},
		onSuccess: this._connectionManagerSignal.bind(this)
	});
	
}

NetworkMonitor.prototype._connectionManagerSignal = function(payload) {
	
	if (payload.connected) {
		Mojo.Log.info("@@@ CONNECTION MANAGER SIGNAL - UP");
		// On a signal we re-subscribe to the connection manager.
		var self = this;
		this.connMgrSubscription = new Mojo.Service.Request('palm://com.palm.connectionmanager', {
				method: 'getstatus',
				parameters: {'subscribe': true},
				onSuccess: function(response) {	
					
					self.online = response.isInternetConnectionAvailable;
					if (self._userStatusChangeCb) {
						self._userStatusChangeCb(response);
					}
				},
				onFailure: function() {
					Mojo.Log.error("Failure subscribing to connection mgr status");
				}
			});
					
	} else {
		if (PalmSystem.version.match("desktop")) {
			// No connection manager AND we are the desktop so assume
			// we are running untethered
			Mojo.Log.info("@@@ CONNECTION MANAGER SIGNAL - DOWN - BUT ASSUMING RUNNING ON THE DESKTOP");
			this.online = true;
		} else {
			Mojo.Log.info("@@@ CONNECTION MANAGER SIGNAL - DOWN");
			this.online = false;
			this.stopObserving();
		}
	}
};

NetworkMonitor.prototype.observe = function(statusChangeCb) {
	
	this._userStatusChangeCb = statusChangeCb;
};

NetworkMonitor.prototype.stopObserving = function() {
	if (this.connMgrSubscription) {
		this.connMgrSubscription.cancel();
		delete this.connMgrSubscription;
	}
};

NetworkMonitor.prototype.addNetworkCheckedMethods = function(params) {
	
	params = params || {};
	var prefix = "_netchecked_";
	var that = params.target;
	var onNetworkDown = params.onNetworkDown;
	var monitor = this;
	methods = params.methods || [];
	methods.each(function(method) {
		
		that[prefix+method] = that[method];
		that[method] = function(m) {
			
			var self = that;
			return function() {

				if (monitor.online) {					
					self[prefix+m].apply(self, arguments);
				} else {
					if (onNetworkDown) {
						onNetworkDown({
							method: m,
							args: arguments
						});
					}
				}				
			};
		}(method);
	});
};
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

function AppAssistant(appController) {

	try {
		this.database = openDatabase(Browser.Database.name, Browser.Database.version, Browser.Database.displayName, Browser.Database.estimatedSize);
	}
	catch (e) {
		Mojo.Log.logException(e, "AppAssistant#AppAssistant: Database FAILED to open.");
	}
}

/**
 * Application wide help location
 */
AppAssistant.helpUrl = 'http://help.palm.com/web/index.html';
AppAssistant.noNetworkUrl = 'no-network';

AppAssistant.launchHelp = function() {

	// Launch the help system.
	var req = new Mojo.Service.Request('palm://com.palm.applicationManager', {
			method: 'open',
			parameters: {
				id: 'com.palm.app.help',
				params: {
					'target': AppAssistant.helpUrl
				}
			}
		});
};

AppAssistant.launchNetworkHelp = function() {
	
	// Launch the help system.
	var req = new Mojo.Service.Request('palm://com.palm.applicationManager', {
			method: 'open',
			parameters: {
				id: 'com.palm.app.help',
				params: {
					'target': AppAssistant.noNetworkUrl
				}
			}
		});	
};

AppAssistant.Network = new NetworkMonitor();

AppAssistant.prototype.setup = function() {

	AppAssistant.Network.observe(function(response){Mojo.Log.info("@@@ Listening to network events: online=", response.isInternetConnectionAvailable);});
};

AppAssistant.prototype.getDatabase = function() {

	return this.database;
};

AppAssistant.prototype.handleLaunch = function(params) {

	try {
		var launchParams = {};

		if (Object.isString(params)) {
			if (params.isJSON()) {
				launchParams = params.evalJSON();
			}
		}
		else {
			launchParams = params;
		}

		if (launchParams.defaultData) {
			this._processDefaultData(launchParams.defaultData);
			return;
		}

		// If we're headless, then we need to manually create a card stage.
		if (Mojo.Controller.appInfo.noWindow) {
			var stageName = "browser-" + Date.now();
			var f = function(stageController) {

				// Temporary code. The "url" property was renamed to "target"
				if (launchParams.url) {
					Mojo.Log.warn("WARNING: 'url' param deprecated; use 'target' instead!");
					launchParams.target = launchParams.url;
					delete launchParams.url;
				}

				// Sanitize the target property
				if (launchParams.target) {
					launchParams.target = launchParams.target.stripScripts().stripTags();
				}

				// Indicate this is a result of a launch.
				var pageParams = {
					launchParams: launchParams
				};
				
				// If we don't have a startup identifier nor URL we also push the startpage.
				if (!launchParams.target && !launchParams.pageIdentifier) {

					stageController.pushScene({
						name: 'startpage',
						transition: Mojo.Transition.crossFade
						},
						pageParams);

				} else {
					stageController.pushScene('page', pageParams);
				}
			};
			this.controller.createStageWithCallback({name: stageName, lightweight: true}, f);
		} else if(params.banner) {
			// This parameter is set when we're launched due to clicking our notification banner.
			// It causes us to switch to the notify scene.
			this.goToNotifyScene();
		}
	}
	catch (e) {
		Mojo.Log.logException(e, "AppAssistant#handleLaunch");
	}
};

AppAssistant.prototype.cleanup = function() {

};

AppAssistant.prototype.goToNotifyScene = function() {
	Mojo.Log.warn("Notification scene not yet implemented.");
};

AppAssistant.prototype._getCarrierNetworkSettings = function() {
	// Not yet called. Still coordinating with Ezekiel.
	try {
		var request = new Mojo.Service.Request('palm://com.palm.data.carriernetowrksettings/getUaProf', {
			parameters: {},
			onSuccess: function(error) {
				Mojo.Log.info("Got carrier network settings. %s", $H(error).inspect());
			}.bind(this),
			onFailure: function(error) {
				Mojo.Log.error("Error getting carrier network settings. return %s, errorCode: %s, msg: '%s'", error.returnValue, error.errorCode, error.errorText);
			}.bind(this)
		});
	}
	catch (e) {
		Mojo.Log.logException(e, "_getCarrierNetworkSettings");
	}
};

AppAssistant.prototype._onDeleteDefaultBookmarksSuccess = function(defaultData) {

	try {

		if (defaultData.bookmarks) {

			var folder = new BookmarkFolder('default');

			// Fixup the default bookmark data just in case any values were omitted.
			for (var i = 0; i < defaultData.bookmarks.length; i++) {

				var item = defaultData.bookmarks[i];
				var date = undefined;
				if (item.date) {
					date = Date.parse(item.date);
				}
				var bookmark = new UrlReference(item.url, item.title, date);

				bookmark.defaultEntry = true;
				bookmark.iconFile32 = item.iconFile32 ? item.iconFile32 : bookmark.iconFile32;
				bookmark.iconFile64 = item.iconFile64 ? item.iconFile64 : bookmark.iconFile64;
				bookmark.thumbnailFile = item.thumbnailFile ? item.thumbnailFile : bookmark.thumbnailFile;
				bookmark.visitCount = item.visitCount ? item.visitCount : bookmark.visitCount;
				bookmark.lastVisited = item.lastVisited ? item.lastVisited : bookmark.lastVisited;

				folder.addItem(bookmark);
			}
			this.bookmarkStore.write(folder, function() {
			}, function(e, msg) {
				Mojo.Log.error("Failure adding default bookmark: %s, msg: '%s'", e, msg);
			});
		}
	}
	catch (e) {
		Mojo.Log.logException(e, "_onDeleteDefaultBookmarksSuccess");
	}

	delete this.bookmarkStore;
};

AppAssistant.prototype._processDefaultData = function(defaultData) {

	try {
		Mojo.Log.info("Processing default data.");
		var deleteSuccess = this._onDeleteDefaultBookmarksSuccess.bind(this, defaultData);

		this.bookmarkStore = new BookmarkStore({database: this.getDatabase()},
		function() {
			this.bookmarkStore.deleteDefaultBookmarks(deleteSuccess,
				function(msg, e) {
					Mojo.Log.error('Failure deleting default bookmarks: %s.', msg);
					delete this.bookmarkStore;
				}.bind(this));
		}.bind(this),
		function(msg, e) {
			Mojo.Log.error("Failed to create the bookmark store %s.", msg);
		}.bind(this));
	}
	catch(e) {
		Mojo.Log.logException(e, "_processDefaultData");
	}
};

WebPreferences = Class.create({
	
	kCookieName: "WebPreferencesCookie",

	_fields: {
		'ClearCache': false, 
		'ClearCookies': false,
		'ClearHistory': false,
		'EnableJavaScript': true, 
		'AcceptCookies': true,
		'BlockPopups': true
	},
	
	initialize: function() {
				
		var cookie = new Mojo.Model.Cookie(this.kCookieName);
		var preferences = cookie.get() || {};
		
		// Setup the properties  
		for (var field in this._fields) {
			
			this[field] = this._fields[field];
			if (typeof preferences[field] !== 'undefined') {
				this[field] = preferences[field];
			} 
		}
	},

	activate: function(actor) {
		
		try {
			if (actor) {
				if (actor.clearCache && this.ClearCache) {
					//Mojo.Log.info("@@@ WEB PREFS: Clearing Cache");
					actor.clearCache();
					this.ClearCache = false;
				}
				
				if (actor.clearCookies && this.ClearCookies) {
					//Mojo.Log.info("@@@ WEB PREFS: Clearing Cookies");
					actor.clearCookies();
					this.ClearCookies = false;
				}
				
				if (actor.clearHistory && this.ClearHistory) {
					//Mojo.Log.info("@@@ WEB PREFS: Clearing History");
					actor.clearHistory();
					this.ClearHistory = false;
				}
				
				if (actor.setBlockPopups) {
					//Mojo.Log.info("@@@ WEB PREFS: BlockPopups=", this.BlockPopups);
					actor.setBlockPopups(this.BlockPopups);
				}
				
				if (actor.setEnableJavaScript) {
					//Mojo.Log.info("@@@ WEB PREFS: EnableJavaScipt=", this.EnableJavaScript);
					actor.setEnableJavaScript(this.EnableJavaScript);
				}
				
				if (actor.setAcceptCookies) {
					//Mojo.Log.info("@@@ WEB PREFS: AcceptCookies=", this.AcceptCookies);
					actor.setAcceptCookies(this.AcceptCookies);
				}
				
				// Store the new settings.
				this.save(); 
			}
		} catch (e) {
			
			Mojo.Log.logException(e, "Failed to update web preferences");
		}
	},
	
	save: function() {
		
		// Store the current settings.
		var cookie = new Mojo.Model.Cookie(this.kCookieName);
		var preferences = {};
		
		for (var field in this._fields) {
			preferences[field] = this[field];
		}
		
		cookie.put(preferences);
	}
});

AppAssistant.WebPreferences = new WebPreferences();
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
* The assistant for the Bookmarks scene.
*/
function BookmarksAssistant(webPreferences) {
	this._webPreferences = webPreferences;
	this.bookmarkStore = new BookmarkStore({database: Mojo.Controller.appController.assistant.getDatabase()});
	this.bookmarksModel = $A();
	this.urlToBookmark = $H();
	this.usingBookmarksDb = false;
}

BookmarksAssistant.Menu = {

	Clear: {
		label: $L('Clear Bookmarks'),
		command: 'clear-bookmarks-command'
	}
};

BookmarksAssistant.prototype.setup = function() {

	try {
		this.controller.setupWidget('bookmarksList',
						{itemTemplate:'bookmarks/bookmarks-entry',
						 listTemplate:'bookmarks/bookmarks-container',
						 itemsCallback:this._retrieveItemsCallback.bind(this),
						 swipeToDelete:true,
						 reorderable:true});
		this.bookmarksListWidget = this.controller.get('bookmarksList');

		this._onListSelectionHandler = this._onListSelection.bindAsEventListener(this);
		this._onListDeleteHandler = this._listDeleteHandler.bindAsEventListener(this);
		this._onListReorderHandler = this._listReorderHandler.bindAsEventListener(this);

		var appMenuModel = {
			visible: true,
			items: [
				MenuData.ApplicationMenu.ShowBookmarks,
				MenuData.ApplicationMenu.ShowHistory,
				BookmarksAssistant.Menu.Clear
			]
		};

		this.controller.setupWidget(Mojo.Menu.appMenu, undefined, appMenuModel);

	}
	catch (e) {
		Mojo.Log.logException(e, 'BookmarksAssistant.setup');
	}
};

BookmarksAssistant.prototype.cleanup = function(event) {
};

BookmarksAssistant.prototype.activate = function() {

	this.bookmarksListWidget.addEventListener(Mojo.Event.listTap, this._onListSelectionHandler);
	this.bookmarksListWidget.addEventListener(Mojo.Event.listDelete, this._onListDeleteHandler);
	this.bookmarksListWidget.addEventListener(Mojo.Event.listReorder, this._onListReorderHandler);

	// Doing this now because the read callback needs the widget to be
	// instantiated and this happens after setup.
	this._resetBookmarkList();
	this._readBookmarks();
};

BookmarksAssistant.prototype.deactivate = function(event) {

	Mojo.Event.stopListening(this.bookmarksListWidget, Mojo.Event.listTap, this._onListSelectionHandler);
	Mojo.Event.stopListening(this.bookmarksListWidget, Mojo.Event.listDelete, this._onListDeleteHandler);
	Mojo.Event.stopListening(this.bookmarksListWidget, Mojo.Event.listReorder, this._onListReorderHandler);
};

/** @private */
BookmarksAssistant.prototype._resetBookmarkList = function() {
	delete this.bookmarksModel;
	this.bookmarksModel = $A();
	this.urlToBookmark = $H();
	var lir = this.bookmarksListWidget.mojo.getLoadedItemRange();
	this.bookmarksListWidget.mojo.noticeRemovedItems(lir.offset, lir.limit);
};

/**
 * Start reading bookmarks (actual read is asynchronous).
 * @private
 */
BookmarksAssistant.prototype._readBookmarks = function() {

	if (!this.usingBookmarksDb) {
		this.usingBookmarksDb = true;
		this.bookmarkStore.readBookmarks(
			new BookmarkFolder(),
			null,
			this._onBookmarksReadSuccess.bind(this),
			this._onBookmarksReadFailure.bind(this) );
	}
};

BookmarksAssistant.prototype._deleteImage = function(fname) {
	
	this.controller.serviceRequest('palm://com.palm.browserServer/deleteImage', {
			parameters: {file: fname},
			onFailure: function() {
				Mojo.Log.error("Failure deleting file: %s", fname);
			}
		});
};

BookmarksAssistant.prototype._deleteBookmarkImages = function(urlRef) {
	this._deleteImage(urlRef.iconFile32);
	this._deleteImage(urlRef.iconFile64);
	this._deleteImage(urlRef.thumbnailFile);
};

BookmarksAssistant.prototype._clearBookmarks = function() {

	var self = this;
	this.controller.showAlertDialog({
		onChoose: function(value) {
			if (value === 'ok') {
				self.bookmarkStore.deleteBookmark('ALL', 
					function() {
						// Now delete all of the bookmark images.
						for (var idx=0, len = self.bookmarksModel.length; idx < len; idx++) {
							self._deleteBookmarkImages(self.bookmarksModel[idx]);
						}
						self._resetBookmarkList();						
					},
					
					function(sqlFunc, params) {
						Mojo.Log.error("Unable to delete all bookmarks");
					});
			}
		},
		title:$L('Clear Bookmarks'),
		message:$L('Are you sure you want to clear all bookmarks?'),
		cancelable:true,
		choices:[
			{label:$L('Clear Bookmarks'), value:'ok', type:'negative'},
			{label:$L('Cancel'), value:'cancel'}
		]
	});
};

/**
 * Called by the BookmarksStore when it has finished successfully reading the
 * bookmarks from the database
 *
 * @param {BookmarkFolder} bookmarkFolder
 * @private
 */
BookmarksAssistant.prototype._onBookmarksReadSuccess = function(bookmarkFolder) {

	try {
		for (var d = 0; d < bookmarkFolder.contents.length; d++) {
			var item = bookmarkFolder.contents[d];
			if (item instanceof UrlReference) {

				this.urlToBookmark.set(item.url, item);
				item.dispUrl = UrlUtil.cleanup(item.url);
				if (item.title.empty()) {
					item.title = item.dispUrl;
					item.dispUrl = '&nbsp;';
				}
				else {
					item.title = item.title.escapeHTML();
				}

				this.bookmarksModel.push(item);
			}
		}

		this.bookmarksListWidget.mojo.setLength(this.bookmarksModel.length);
	}
	finally {
		this.usingBookmarksDb = false;
	}
};

/** @private */
BookmarksAssistant.prototype._onBookmarkUpdateSuccess = function() {
};

/** @private */
BookmarksAssistant.prototype._onBookmarkUpdateFailure = function(sqlFunc, params) {
	Mojo.Log.error("ERROR updating bookmark, code: %d, message: '%s'.", params.code, params.message);
};

/**
 * Called by the BookmarksStore when it has failed reading thie bookmarks
 * from the database.
 *
 * @param {Object} msg
 * @param {Object} e
 * @private
 */
BookmarksAssistant.prototype._onBookmarksReadFailure = function(msg, e) {
	Mojo.Log.error("Error reading bookmarks: %s", e.toString());
	this.usingBookmarksDb = false;
};

/**
 * This callback is called by the URL search list to retrieve model objects to be
 * displayed in the list.
 *
 * @param {Object} listWidget	The widget to add the items to.
 * @param {Number} offset	The zero based item offset
 * @param {Number} count	The number of items to retrieve.
 *
 * @private
 */
BookmarksAssistant.prototype._retrieveItemsCallback = function(listWidget, offset, count) {

	// The bookmarks model has already been fully loaded but we only lazily load
	// the list.
	var model = this.bookmarksModel.slice(offset, offset+count);
	listWidget.mojo.noticeUpdatedItems(offset, model);
};

BookmarksAssistant.prototype._listReorderHandler = function(event) {
	this.bookmarksModel.splice(this.bookmarksModel.indexOf(event.item), 1);
	this.bookmarksModel.splice(event.toIndex, 0, event.item);
	for(var i=0; i<this.bookmarksModel.length;i++)	{
		this.bookmarksModel[i].index=i;
		this.bookmarkStore.insertOrUpdateBookmark( this.bookmarksModel[i],
			this._onBookmarkUpdateSuccess.bind(this),
			this._onBookmarkUpdateFailure.bind(this) );

	}
};

BookmarksAssistant.prototype._listDeleteHandler = function(event) {
	var urlRef = event.item;
	this.bookmarksModel.splice(this.bookmarksModel.indexOf(urlRef), 1);
	this.bookmarkStore.deleteBookmark(event.item.id,
		this._onBookmarkDeleteSuccess.bind(this, urlRef),
		this._onBookmarkDeleteFailure.bind(this) );

};

/** @private */
BookmarksAssistant.prototype._onBookmarkDeleteSuccess = function(urlRef) {
	this._deleteBookmarkImages(urlRef);
};

/** @private */
BookmarksAssistant.prototype._onBookmarkDeleteFailure = function(sqlFunc, params) {
	Mojo.Log.error("ERROR deleting bookmark, code: %d, message: '%s'.", params.code, params.message);
};

BookmarksAssistant.prototype._onListSelection = function(event) {

	var targetRow = this.controller.get(event.originalEvent.target);
	var selectedInfo = targetRow.match('#info');

	var urlRef = event.item;
	var onClose = function(saved) {
		if (saved) {
			this._resetBookmarkList();
			this._readBookmarks();
		}
	}.bind(this);

	if (selectedInfo) {
		var params = {task: BookmarkDialogAssistant.editBookmarkTask,
			urlReference: urlRef,
			sceneController: this.controller,
			bookmarkStore: this.bookmarkStore,
			onClose: onClose,
			deleteImage: this._deleteImage.bind(this)};

		BookmarkDialogAssistant.showDialog(params);
	}
	else {
		this.controller.stageController.popScene({type: 'bookmarks', payload: urlRef});
	}
};

BookmarksAssistant.prototype.handleCommand = function(event) {

	try {
		switch (event.type) {

			case Mojo.Event.back:
				if (this.controller.stageController._sceneStack.size() > 1) {
					Event.stop(event);
					this.controller.stageController.popScene();
				}
				break;

			case Mojo.Event.command:

				switch (event.command) {

					case MenuData.ApplicationMenu.ShowHistory.command:
						this._openHistoryView();
						break;

					case BookmarksAssistant.Menu.Clear.command:
						this._clearBookmarks();
						break;

					case Mojo.Menu.prefsCmd:
						this.prefsSceneActive = true;
						this._openPreferencesView();
						break;

					case Mojo.Menu.helpCmd:
						this._showHelpCommand();
						break;
				}
				break;

			case Mojo.Event.commandEnable:

				// Standard Application Menu commands.
				if (event.command === Mojo.Menu.prefsCmd ||
				    event.command === Mojo.Menu.helpCmd) {

					event.stopPropagation(); // Enable the chosen menuitems
					return;
				}

				if (event.command === MenuData.ApplicationMenu.ShowBookmarks.command) {
					event.preventDefault();
					return;
				}

				break;
		}
	}
	catch (e) {
		Mojo.Log.logException(e, 'BookmarksAssistant#handleCommand');
	}
};

BookmarksAssistant.prototype._openPreferencesView = function() {

	this.controller.stageController.pushScene('preferences', this._webPreferences);

};

BookmarksAssistant.prototype._openHistoryView = function() {

	this.controller.stageController.swapScene(
		{
			name: 'history',
			transition: Mojo.Transition.crossFade
		},
		this._webPreferences);
};

BookmarksAssistant.prototype._showHelpCommand = function() {

	// Launch help
	AppAssistant.launchHelp();
};



/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * Constructor.
 * 
 * If this is a new UrlReference then the tmpIconFile* images will be set and the iconFile* images will not.
 * Conversely if the iconFile* images are set and the tmpIconFile* are not then this is an existing bookmark
 * that will be edited.
 * 
 * <ul>
 * <li>sceneController - Instance of Mojo.Controller.SceneController.</li>
 * <li>bookmarkStore - Instance of BookmarkStore.</li>
 * <li>urlReference - Instance of UrlReference.</li>
 * <li>task - String (BookmarkDialogAssistant.createBookmarkTask, BookmarkDialogAssistant.editBookmarkTask,
 *                   or BookmarkDialogAssistant.createLaunchpointTask.</li>
 * <li>deleteImage - function to call to delete an image.</li>
 * <li>onClose - function to call when closing the bookmark dialog.</li>
 * </ul>
 * 
 * @param {Object} The assistant parameters. 
 */
function BookmarkDialogAssistant(params) {
	
	this.params = Mojo.Model.decorate(params);
	
	Mojo.assert(params.deleteImage, "Must provide image delete func.");
	
	this.savedUrlReference = false;

	this.tempFiles = $A();
	switch (this.params.task) {
		case BookmarkDialogAssistant.createBookmarkTask:
			this.actionBtnTitle = $L('Add bookmark');
			break;
		case BookmarkDialogAssistant.editBookmarkTask:
			this.actionBtnTitle = $L('Save bookmark');
			break;
		case BookmarkDialogAssistant.createLaunchpointTask:
			this.actionBtnTitle = $L('Add to Launcher');
			break;
		default:
			this.actionBtnTitle = $L('Save');
	}
	var urlReference = this.params.urlReference;
	if (urlReference.tmpIconFile64 === undefined) {
		urlReference.tmpIconFile64 = urlReference.iconFile64;
	}
	if (urlReference.tmpIconFile32 === undefined) {
		urlReference.tmpIconFile32 = urlReference.iconFile32;
	}
}

BookmarkDialogAssistant.createBookmarkTask = 'task-create-bookmark';
BookmarkDialogAssistant.editBookmarkTask = 'task-edit-bookmark';
BookmarkDialogAssistant.createLaunchpointTask = 'task-create-launchpoint';


BookmarkDialogAssistant.prototype.setup = function(widget) {
	
	var controller = this.params.sceneController;
	this.widget = widget;
	
	// A change to the default behaviour of textfields means
	// we have to set the changeOnKeyPress attribute to 'true'
	// to guarantee we have the model updated with the textfield
	// entry on each keypress.
	var titleFieldAttributes = { 
		modelProperty: 'title',
		changeOnKeyPress: true,
		focusMode: Mojo.Widget.focusSelectMode  
	};
	controller.setupWidget('titleField', titleFieldAttributes, this.params.urlReference);
	
	var urlFieldAttributes = {
		modelProperty: 'url', 
		textReplacement:false, 
		changeOnKeyPress: true,
		focusMode: Mojo.Widget.focusSelectMode  
	};
	controller.setupWidget('urlField', urlFieldAttributes, this.params.urlReference);

	var img = Mojo.View.render({object: {icon: this.params.urlReference.tmpIconFile64}, template: "bookmarks/imageicondiventry"});
	controller.get('imageicondiv').update(img);
	if (this.params.task !== BookmarkDialogAssistant.createLaunchpointTask) {
		controller.get('pageiconsubtitle').hide();
	}
	if (this.params.task === BookmarkDialogAssistant.createLaunchpointTask) {
		controller.get('actionButton').addEventListener(Mojo.Event.tap, this._onAddLaunchpointButtonTap.bindAsEventListener(this));
	}
	else {
		controller.get('actionButton').addEventListener(Mojo.Event.tap, this._onAddBookmarkButtonTap.bindAsEventListener(this));
	}
	controller.get('cancelBookmarkButton').addEventListener(Mojo.Event.tap, this._onCancelButtonTap.bindAsEventListener(this));
	controller.listen('imageicondiv', Mojo.Event.tap, this._onCustomizeIconButtonTap.bindAsEventListener(this));
};

BookmarkDialogAssistant.prototype._onCustomizeIconButtonTap = function() {
	this.params.sceneController.stageController.pushScene('customizeicon', {urlReference: this.params.urlReference});
};

/** @private */
BookmarkDialogAssistant.prototype._addLaunchIconSuccess = function(response) {
    Mojo.Log.info("Successfully added launch icon.");
};

/** @private */
BookmarkDialogAssistant.prototype._addLaunchIconFailure = function() {
    Mojo.Log.error("Failed to add launch icon");
};

/** @private */
BookmarkDialogAssistant.prototype._onBookmarkAddSuccess = function(oldIconFile64, oldIconFile32) {
    Mojo.Log.info("Successfully added/updated bookmark");
	// Delete the old icons that are no longer referenced in the database
	if (oldIconFile64) {
		Mojo.Log.info("Deleting: %s", oldIconFile64);
		this.params.deleteImage(oldIconFile64);
	}
	if (oldIconFile32) {
		Mojo.Log.info("Deleting: %s", oldIconFile32);
		this.params.deleteImage(oldIconFile32);
	}
};

BookmarkDialogAssistant.prototype._onAddBookmarkButtonTap = function() {
	if (this.params.urlReference.url !== undefined) {
		var oldIconFile64 = this.params.urlReference.iconFile64;
		var oldIconFile32 = this.params.urlReference.iconFile32;
		this.params.urlReference.iconFile64 = this.params.urlReference.tmpIconFile64;
		this.params.urlReference.iconFile32 = this.params.urlReference.tmpIconFile32;
		this.params.bookmarkStore.insertOrUpdateBookmark( this.params.urlReference, 
			this._onBookmarkAddSuccess.bind(this, oldIconFile64, oldIconFile32),
			function(sqlFunc, params) {
				Mojo.Log.error("ERROR adding/updating bookmark, code: %s, message '%s'", 
					params.code, params.message);
			}
		);
		this.savedUrlReference = true;
	}
	else {
		Mojo.Log.warn("No current URL");
	}

	if (this.params.onClose) {
		this.params.onClose(this.savedUrlReference);
		delete this.params.onClose;
	}
		
	this.widget.mojo.close();
};

BookmarkDialogAssistant.prototype._onAddLaunchpointButtonTap = function() {
    try {
        var appParams = {
            scene: 'page',
            url: this.params.urlReference.url
        };

        var title = this.title || $L('Web page');
        var callParams = {
            id: 'com.palm.app.browser',
            'icon': this.params.urlReference.tmpIconFile64,
            'title': this.params.urlReference.title,
            'params': appParams
        };
        this.params.sceneController.serviceRequest('palm://com.palm.applicationManager/addLaunchPoint', {
            parameters: callParams,
            onSuccess: this._addLaunchIconSuccess.bind(this),
            onFailure: this._addLaunchIconFailure.bind(this)
        });
		this.savedUrlReference = true;
    } 
    catch (e) {
        Mojo.Log.logException(e, '_onAddLaunchpointButtonTap');
    }

	if (this.params.onClose) {
		this.params.onClose(this.savedUrlReference);
		delete this.params.onClose;
	}
	
	this.widget.mojo.close();
};

BookmarkDialogAssistant.prototype._onCancelButtonTap = function() {
	if (this.params.urlReference.iconFile64 !== this.params.urlReference.tmpIconFile64) {
		this.params.deleteImage(this.params.urlReference.tmpIconFile64);
	}
	if (this.params.urlReference.iconFile32 !== this.params.urlReference.tmpIconFile32) {
		this.params.deleteImage(this.params.urlReference.tmpIconFile32);
	}
	if (this.params.task !== BookmarkDialogAssistant.editBookmarkTask) {
		this.params.deleteImage(this.params.urlReference.thumbnailFile);
	}

	if (this.params.onClose) {
		this.params.onClose(this.savedUrlReference);
		delete this.params.onClose;
	}
	this.widget.mojo.close();
};

BookmarkDialogAssistant.prototype.cleanup = function() {
	
	// If the callback hasn't been called then call it. This
	// was probably due to a back.
	if (this.params.onClose) {
		this.params.onClose(false);
	}	
};

/**
 * Display the bookmark dialog.
 *
 * @param {Object} An object containing parameters for this function. See BookmarkDialogAssistant constructor.
 */
BookmarkDialogAssistant.showDialog = function(params) {

	var assistant = new BookmarkDialogAssistant(params);
	var title = params.urlReference.title || '';	
	
	return params.sceneController.showDialog({
		template: 'bookmarks/bookmark-dialog',
		assistant: assistant,
		title: title,
		// We do this flag for the moment to give the user
		// the expect behaviour of dropping back to the
		// bookmark/launcher dialog after using the customize
		// icon feature.
		preventCancel: true,
		actionBtnTitle: assistant.actionBtnTitle
	});
};
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * Returns the time at midnight for this date.
 * 
 * @return A new data object with the same date, but time at midnight.
 */
Date.prototype.getMidnight = function() {
	var delta = this.getHours()*60*60*1000 + this.getMinutes()*60*1000 + 
				this.getSeconds()*1000 + this.getMilliseconds();
	
	return new Date(this.getTime() - delta);
};

/**
 * Add the specified number of minutes to this this object and return
 * the result in a new data object.
 *  
 * @param {Nujmber} minutes The number of minutes to add.
 * @return {Date} The new date object.
 */
Date.prototype.addMinutes = function(minutes) {
    return new Date(this.getTime() + minutes*60*1000);
};

/**
 * Add the specified number of days to this this object and return
 * the result in a new data object.
 *  
 * @param {Nujmber} days The number of days to add.
 * @return {Date} The new date object.
 */
Date.prototype.addDays = function(days) {
    return new Date(this.getTime() + days*24*60*60*1000);
};

/**
 * Return a new date object in UTC. 
 * 
 * @return {Date} The new date object in equivalent UTC time.
 */
Date.prototype.getUTC = function(days) {
	return this.addMinutes( this.getTimezoneOffset() );
};

/**
 * Set the time of this data object to an epoch value in UTC.
 * 
 * @param {Number} utc The epoch in UTC (milliseconds).
 */
Date.prototype.setUTCTime = function(utc) {
	this.setTime(utc - this.getTimezoneOffset() * 60 * 1000);
};
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * @name history-assistant.js
 * @fileOverview The assistant for the History scene.
 */

/**
 * Constructor.
 * @param {PageAssistant} pageAssistant
 */
function HistoryAssistant(webPreferences) {
	this._webPreferences = webPreferences;
	this.historyStore = new HistoryStore({
		database: Mojo.Controller.appController.assistant.getDatabase()
	});

	this._chunkSize = 50;
}

HistoryAssistant.Menu = {

	Clear: {
		label: $L('Clear History'),
		command: 'clear-history-command'
	}
};

HistoryAssistant.prototype.setup = function() {

	try {
		this.controller.setupWidget('historyList', {
			itemTemplate:'history/history-entry',
			listTemplate:'history/history-container',
			itemsCallback:this._itemsCallback.bind(this)
		});

		this._onListSelectionHandler = this._onListSelection.bindAsEventListener(this);
		this._historyListWidget = this.controller.get('historyList');

		var appMenuModel = {
			visible: true,
			items: [
				MenuData.ApplicationMenu.ShowBookmarks,
				MenuData.ApplicationMenu.ShowHistory,
				HistoryAssistant.Menu.Clear
			]
		};

		this.controller.setupWidget(Mojo.Menu.appMenu, undefined, appMenuModel);
	}
	catch (e) {
		Mojo.Log.logException(e, 'HistoryAssistant#setup');
	}
};

HistoryAssistant.prototype.cleanup = function() {

};

HistoryAssistant.prototype.activate = function() {

	this._historyListWidget.addEventListener(Mojo.Event.listTap, this._onListSelectionHandler);

	// On an activation for a re-render of the contents.
	var len = this._historyListWidget.mojo.getLength();	
	var lir = this._historyListWidget.mojo.setLengthAndInvalidate(len);
};

HistoryAssistant.prototype.deactivate = function() {

	Mojo.Event.stopListening(this._historyListWidget, Mojo.Event.listTap, this._onListSelectionHandler);
};

/**
 * Called by the HistoryStore when it has finished successfully reading the
 * history from the database
 *
 * @param {HistoryList} historyList
 * @private
 */
HistoryAssistant.prototype._onHistoryReadSuccess = function(historyList, offset, limit) {

	var model = $A();
	for (var d = 0; d < historyList.days.length; d++) {

		var day = historyList.days[d];
		for (var i = 0; i < day.items.length; i++ ) {

			var item = day.items[i];
			item.dispUrl = UrlUtil.cleanup(item.url);
			if (!item.title || item.title.empty()) {
				item.title = item.dispUrl;
				item.dispUrl = '&nbsp;';
			}
			else {
				item.title = item.title.escapeHTML();
			}

			model.push(item);
		}
	}

	// Update the list with the new model and if required
	// setup the list to callback and to load more data.
	this._historyListWidget.mojo.noticeUpdatedItems(offset, model);
	newlen = this._historyListWidget.mojo.getLength();

	if (limit !== model.length || model.length === 0) {
		// No more to load - We ran out of data.
		this._historyListWidget.mojo.setLength(offset + model.length);

	} else {
		// Don't go backward in length!
		if (newlen <= (model.length + offset)) {
			this._historyListWidget.mojo.setLength(newlen + this._chunkSize);
		}
	}
};

/**
 * This callback is called by the URL search list to retrieve model objects to be
 * displayed in the list.
 *
 * @param {Object} listWidget	The widget to add the items to.
 * @param {Number} offset	The zero based item offset
 * @param {Number} count	The number of items to retrieve.
 *
 * @private
 */
HistoryAssistant.prototype._itemsCallback = function(listWidget, offset, count) {

	this.historyStore.getHistory(new HistoryList(),
		undefined,
		this._onHistoryReadSuccess.bind(this),
		function(msg, e) {
			Mojo.Log.error("Error reading history: %s.", e.toString());
		},
		offset,
		count);
};

HistoryAssistant.prototype._onListSelection = function(event) {

	var url = event.item.url;
	this.controller.stageController.popScene({
		type: 'history',
		payload: url
	});
};

/**
 * handle a menu command.
 */
HistoryAssistant.prototype.handleCommand = function(event) {

	try {
		switch (event.type) {

			case Mojo.Event.command:

				switch (event.command) {

					case MenuData.ApplicationMenu.ShowBookmarks.command:
						this._openBookmarkView();
						break;

					case HistoryAssistant.Menu.Clear.command:
						this._clearHistory();
						break;

					case Mojo.Menu.prefsCmd:
						this._openPreferencesView();
						break;

					case Mojo.Menu.helpCmd:
						this._showHelpCommand();
						break;
				}
				break;

			case Mojo.Event.commandEnable:

				// Standard Application Menu commands.
				if (event.command === Mojo.Menu.prefsCmd ||
				    event.command === Mojo.Menu.helpCmd) {

					event.stopPropagation(); // Enable the chosen menuitems
					return;
				}

				if (event.command === MenuData.ApplicationMenu.ShowHistory.command) {
					event.preventDefault();
					return;
				}

				break;
		}
	}
	catch (e) {
		Mojo.Log.logException(e, 'handleCommand');
	}
};

HistoryAssistant.prototype._openPreferencesView = function() {

	this.controller.stageController.pushScene('preferences', this._webPreferences);
};

HistoryAssistant.prototype._openBookmarkView = function() {

	this.controller.stageController.swapScene({
			name: 'bookmarks',
			transition: Mojo.Transition.crossFade
		},
		this._webPreferences);
};

HistoryAssistant.prototype._clearHistory = function(){

	var self = this;
	this.controller.showAlertDialog({
		onChoose: function(value) {
			if (value === 'ok') {
				// We also now clear the local history list.
				self.historyStore.deleteHistoryBefore(new Date(),
					function(){
						var lir = self._historyListWidget.mojo.setLengthAndInvalidate(0);
						self._historyListWidget.mojo.noticeRemovedItems(0, 0);
			
						// Persist the request to clear the session history
						self._webPreferences.ClearHistory = true;
						self._webPreferences.save();	
					},
					function(msg, e) {
						Mojo.Log.error("History delete failed: %s", msg);
					});
			}
		},
		title:$L('Clear History'),
		message:$L('Clearing your history will permanently delete your browsing history. This operation is irreversible!'),
		cancelable:true,
		choices:[
			{label:$L('Clear History'), value:'ok'},
			{label:$L('Cancel'), value:'cancel'}
		]
	});
};

HistoryAssistant.prototype._showHelpCommand = function() {

	// Launch the help system.
	AppAssistant.launchHelp();
};

/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

var WebKitErrors = {
	ERR_SYS_FILE_DOESNT_EXIST: 14,
	ERR_WK_FLOADER_CANCELLED: 1000,
	ERR_CURL_FAILURE: 2000,
	ERR_CURL_COULDNT_RESOLVE_HOST: 2006,
	ERR_CURL_SSL_CACERT: 2060
};

/**
 * The assistant for the browser page scene.
 */
var BrowserServerStatus;

function PageAssistant(params){

	// Record our launch parameters for later use in adapter setup.
	this.launchParams = params.launchParams;
	this._submittedUrl = this.launchParams.target;
	this._orientation = this.launchParams.orientation || 'up'; // Orientation is UP by default.
	this.currPageBookmark = this.launchParams.bookmark; ///< Bookmark for the current page

	this.cmdMenuModel = undefined;
	this.currentUrl = undefined;
	this.currentTitle = undefined;
	this.loadingPage = false;
	this.firstPageLoaded = false;
	this.disableSceneScrolling = false;
	this._editorInPageFocused = false;
	this._spacerHeight = 0;
	this._wkCanGoBack = false; ///< WebKit can go backward
	this._wkCanGoForward = false; ///< WebKit can go forward
	this.hasFocus = true;
	this.imagesToDelete = [];
	this._adapterConnected = true;
	this._isActivated = undefined;
	this._bookmarkDialog = null;
	this._webView = undefined;
	this._disconnectedScrim = null;
	this._disconnectedSpinner = null;

	this.historyStore = new HistoryStore({
		database: Mojo.Controller.appController.assistant.getDatabase()
	});
	this.bookmarkStore = new BookmarkStore({
		database: Mojo.Controller.appController.assistant.getDatabase()
	});

	this._onTitleUrlChangeHandler = this._onTitleUrlChange.bind(this);
	this._onLoadProgressHandler = this._onLoadProgress.bind(this);
	this._onLoadStartedHandler = this._onLoadStarted.bind(this);
	this._onLoadStoppedHandler = this._onLoadStopped.bind(this);
	this._onDidFinishDocumentLoadHandler = this._onDidFinishDocumentLoad.bind(this);
	this._onSetMainDocumentErrorHandler = this._onSetMainDocumentError.bind(this);
	this._onCreatePageHandler = this._onCreatePage.bind(this);
	this._onTapRejectedHandler = this._onTapRejected.bind(this);
	this._onModifierTapHandler = this._onModifierTap.bind(this);

	this._onDragStartHandler = this._onDragStart.bind(this);
	this._addAsScrollListenerHandler = this._addAsScrollListener.bind(this);
	this._onCardDeactivateHandler = this._onCardDeactivate.bindAsEventListener(this);
	this._onCardActivateHandler = this._onCardActivate.bindAsEventListener(this);
	this._onEditorFocusedHandler = this._onEditorFocused.bind(this);
	this._onKeyDownEventHandler = this._onKeyDownEvent.bind(this);
	this._onKeyPressEventHandler = this._onKeyPressEvent.bind(this);
	this._onKeyUpEventHandler = this._onKeyUpEvent.bind(this);
	this._onAdapterConnectHandler = this._onAdapterConnect.bind(this);
	this._onAdapterDisconnectHandler = this._onAdapterDisconnect.bind(this);
	this._onUpdateHistoryHandler = this._onUpdateHistory.bind(this);
}

/**
 * Setup this scene.
 */
PageAssistant.prototype.setup = function() {

	try {
		var targetWindow = this.controller.window;
		if (targetWindow.PalmSystem && targetWindow.PalmSystem.setWindowOrientation) {
			this._orientation = Mojo.Controller.appController.getScreenOrientation();
			targetWindow.PalmSystem.setWindowOrientation(this._orientation);
		}

		// Allow override of 'back' gesture in landscape mode.
		this.controller.useLandscapePageUpDown(true);

		// Create the browser server status control.
		this._browserServerStatus = new BrowserServerStatus(this.controller);
		this._browserServerStatus.setup({
			onConnect: function() {
				if (this.currentUrl) {
					this.openUrl(this.currentUrl);
				}
			}.bind(this)});

		this._setupMenus();
		this._setupWebView();

		// save the current scene controller physics parameters
		var scroller = this.controller.getSceneScroller();
		this.savedFlickSpeed = scroller.mojo.kFlickSpeed;
		this.savedFlickRatio = scroller.mojo.kFlickRatio;
		scroller.mojo.updatePhysicsParameters({flickSpeed: 0.12, flickRatio: 0.2});


		// Listen for drag events, and stop them when appropriate
		Mojo.Event.listen(
			this.controller.getSceneScroller(),
			Mojo.Event.dragStart,
			this._stopSceneScrolling.bindAsEventListener(this),
			true);

		// Listen for scene scope keyboard events.
		this.controller.listen(this.controller.sceneElement, Mojo.Event.keyup, this._onKeyUpEventHandler);
		this.controller.listen(this.controller.sceneElement, Mojo.Event.keydown, this._onKeyDownEventHandler);
		this.controller.listen(this.controller.sceneElement, Mojo.Event.keypress, this._onKeyPressEventHandler);

		var handlRes = this._onWebViewResourceHandoff.bind(this);
		this._webView = this.controller.get('web_view');
		this._webView.addEventListener(Mojo.Event.webViewMimeNotSupported, handlRes, false);
		this._webView.addEventListener(Mojo.Event.webViewMimeHandoff, handlRes, false);
		this._webView.addEventListener(Mojo.Event.webViewUrlRedirect, this._onUrlRedirect.bind(this), false);
		this._webView.addEventListener(Mojo.Event.webViewModifierTap, this._onModifierTapHandler, false);
		this._webView.addEventListener(Mojo.Event.webViewUpdateHistory, this._onUpdateHistoryHandler, false);
	    this._downloadController = new DownloadController(this.controller);
        this._downloadController.setup();
	}
	catch (e) {
		Mojo.Log.logException(e, 'PageAssistant#setup');
	}
};

PageAssistant.prototype.cleanup = function() {

	try {
		// Clean up the controls
		this._pageControls.cleanup();

		// Remove listeners on the webview widget.
		this._webView.removeEventListener(Mojo.Event.webViewUpdateHistory, this._onUpdateHistoryHandler, false);

		// Delete old history
		var now = new Date();
		this.historyStore.deleteHistoryBefore(now.addDays(-this.kMaxHistoryDays));

		// restore scroller params
		this.controller.getSceneScroller().mojo.updatePhysicsParameters({flickSpeed: this.savedFlickSpeed, flickRatio: this.savedFlickRatio});
	}
	catch (e) {
		Mojo.Log.logException(e, 'PageAssistant#cleanup');
	}
};

PageAssistant.prototype.orientationChanged = function(orientation) {

	// Switch the orientation of the menu spacer or other components..
	//Mojo.Log.info("ORIENTATIONCHANGED: old: %s, new: %s", this._orientation, orientation);
	var targetWindow = this.controller.window;
	if (this._orientation === orientation) {
		return;
	}

	// Update the web browser UI components to reflect the new orientation.
	// Setting 'free' after an override doesn't currently work so we are
	// always explicit.
	this._orientation = orientation;
	if (targetWindow.PalmSystem && targetWindow.PalmSystem.setWindowOrientation && this._isActivated) {
		if (this._bookmarkDialog) {
			// When not 'activated' or have the bookmark/launcher dialog up we force 'up'
			this._orientation = 'up';
		}

		// Calling this will cause another orientationChanged event to we
		// protect against recursion by testing current orientation against
		// the new setting on re-entry.
		targetWindow.PalmSystem.setWindowOrientation(this._orientation);
	}

	this._setOrientation(this._orientation);
};

/*
 * Disable the scene scroller to prevent the web view from scrolling underneath whatever is being displayed on top of it
 */
PageAssistant.prototype.disableSceneScroller = function() {
	this.disableSceneScrolling = true;
};

/*
 * Enable the scene scroller (everything back to normal)
 */
PageAssistant.prototype.enableSceneScroller = function() {
	this.disableSceneScrolling = false;
};

/*
 * If the scene must not be scrolled then stop the propagation of the DragStart event
 */
PageAssistant.prototype._stopSceneScrolling = function(event) {

	if (!this.disableSceneScrolling) {
		return;
	}
	var scroller = event.down.target.up('div[x-mojo-element=Scroller]');

	Mojo.Log.info("  Event Scroller ID=%s, Scene Scroller ID=%s", scroller.id, this.controller.getSceneScroller().id);
	if (scroller.id === this.controller.getSceneScroller().id) {
		event.stopPropagation();
	}
};

/**
 * Show the startpage which displays the list of current bookmarks.
 */
PageAssistant.prototype._showStartPage = function() {

	this._webView.mojo.focus();
	this._addressBar.hide();
	this.chrome.hide();
	// If asked to show the startpage then stop any pending loads
	this._webView.mojo.stopLoad();
	this._handleAction('userstop');
	this.controller.stageController.pushScene({
			name: 'startpage',
			transition: Mojo.Transition.crossFade
		},
		{
			lastUrl: this.currentUrl,
			orientation: this._orientation
		});
};

PageAssistant.prototype.activate = function(message) {

	this._isActivated = true;
	try {
		
		// Update any pending web preferences changes
		AppAssistant.WebPreferences.activate(this._webView.mojo);

		// Signal we are interested in showing browser server status.
		this._browserServerStatus.showActivateState();

		this.controller.document.addEventListener(Mojo.Event.activate, this._onCardActivateHandler, false);
		this.controller.document.addEventListener(Mojo.Event.deactivate, this._onCardDeactivateHandler, false);

		var webView = this.controller.get('web_view');
		webView.addEventListener(Mojo.Event.webViewLinkClicked, this._onLinkClickedHandler, true);
		webView.addEventListener(Mojo.Event.webViewTitleUrlChanged, this._onTitleUrlChangeHandler, true);
		webView.addEventListener(Mojo.Event.webViewLoadProgress, this._onLoadProgressHandler, true);
		webView.addEventListener(Mojo.Event.webViewLoadStarted, this._onLoadStartedHandler, true);
		webView.addEventListener(Mojo.Event.webViewLoadStopped, this._onLoadStoppedHandler, true);
		webView.addEventListener(Mojo.Event.webViewSetMainDocumentError, this._onSetMainDocumentErrorHandler, false);
		webView.addEventListener(Mojo.Event.webViewCreatePage, this._onCreatePageHandler, true);
		webView.addEventListener(Mojo.Event.webViewDidFinishDocumentLoad, this._onDidFinishDocumentLoadHandler, true);

		webView.addEventListener(Mojo.Event.webViewTapRejected, this._onTapRejectedHandler, true);
		webView.addEventListener(Mojo.Event.dragStart, this._onDragStartHandler, true);
		webView.addEventListener(Mojo.Event.webViewEditorFocused, this._onEditorFocusedHandler, true);
		webView.addEventListener(Mojo.Event.webViewServerConnect, this._onAdapterConnectHandler, false);
		webView.addEventListener(Mojo.Event.webViewServerDisconnect, this._onAdapterDisconnectHandler, false);

		this.controller.getSceneScroller().addEventListener(Mojo.Event.scrollStarting,
			this._addAsScrollListenerHandler, false);

		// Process any activate parameters passed to use from the assistant that popped us.
		// We defer the processing to allow any native components to become 'active' after
		// activate is complete.
		if (message) {
			switch (message.type) {
				case 'bookmarks':
					this.openBookmark.bind(this, message.payload).defer();
					break;

				case 'history' :
					this.openUrl.bind(this, message.payload).defer();
					break;

				case 'bookmarks-dialog':
					// This is to work around the current implementation
					// calling the page assistants showBookmarkDialog from the
					// bookmark assistant. This will be rearchitected.
					this.showBookmarkDialog(BookmarkDialogAssistant.editBookmarkTask, message.payload);
					break;

				case 'customizeicon':
					// The customizeicon dialog was dismissed via a save/error.
					break;

				case 'startpage':
					if (message.payload) {
						// If we arrived here as a result of a bookmark tap
						// of a types URL from the startpage we make sure to
						// clear the session history
						webView.mojo.clearHistory();
						this.openUrl.bind(this, message.payload).defer();
					} else {
						// forward from the startpage?
						this._pageControls.showIdle();
					}
					break;
					
				case 'startpage-bookmark':
					if (message.payload) {
						// If we arrived here as a result of a bookmark tap
						// with a bookmark payload from the startpage we make 
						// sure to clear the session history
						webView.mojo.clearHistory();
						this.openBookmark.bind(this, message.payload).defer();
					} 
					break;

				default:
					Mojo.Log.warn("Unknown Activate message type: " + message.type);
					break;
			}
		}

		// Activate the addressbar.
		this._addressBar.closeSearchResults();
		this._addressBar.startObserving();
		
		// Update the current history state. We could of had a history wipe 
		// before this seen became activate again.
		this._updateHistoryState();
		
		// On an activate we make sure to refocus the webview widget
		// so we have a focused element. Workaround for some focus issue
		// on input fields in the webview widget when move from startpage 
		// back to the page assistant.
		this._webView.mojo.focus();
	}
	catch (e) {
		Mojo.Log.logException(e, 'PageAssistant.activate');
	}
};

PageAssistant.prototype.deactivate = function() {

	try {
		this._isActivated = false;

		// Signal we are not interesting in showing the browser server status.
		this._browserServerStatus.showDeactivateState();

		// Cleanup focus handlers.
		this.controller.document.removeEventListener(Mojo.Event.activate, this._onCardActivateHandler, false);
		this.controller.document.removeEventListener(Mojo.Event.deactivate, this._onCardDeactivateHandler, false);

		var webView = this.controller.get('web_view');
		webView.removeEventListener(Mojo.Event.webViewTitleUrlChanged, this._onTitleUrlChangeHandler, true);
		webView.removeEventListener(Mojo.Event.webViewLoadProgress, this._onLoadProgressHandler, true);
		webView.removeEventListener(Mojo.Event.webViewLoadStarted, this._onLoadStartedHandler, true);
		webView.removeEventListener(Mojo.Event.webViewLoadStopped, this._onLoadStoppedHandler, true);
		webView.removeEventListener(Mojo.Event.webViewSetMainDocumentError, this._onSetMainDocumentErrorHandler, false);
		webView.removeEventListener(Mojo.Event.webViewCreatePage, this._onCreatePageHandler, true);
		webView.removeEventListener(Mojo.Event.webViewDidFinishDocumentLoad, this._onDidFinishDocumentLoadHandler, true);

		webView.removeEventListener(Mojo.Event.webViewTapRejected, this._onTapRejectedHandler, true);
		webView.removeEventListener(Mojo.Event.dragStart, this._onDragStartHandler, true);
		webView.removeEventListener(Mojo.Event.webViewEditorFocused, this._onEditorFocusedHandler, true);
		webView.removeEventListener(Mojo.Event.webViewUpdateHistory, this._onUpdateHistoryHandler, false);
		webView.removeEventListener(Mojo.Event.webViewServerConnect, this._onAdapterConnectHandler, false);
		webView.removeEventListener(Mojo.Event.webViewServerDisconnect, this._onAdapterDisconnectHandler, false);

		this.controller.getSceneScroller().removeEventListener(Mojo.Event.scrollStarting,
			this._addAsScrollListenerHandler, false);

		this._addressBar.stopObserving();
	}
	catch (e) {
		Mojo.Log.logException(e, 'deactivate');
	}
};

/**
 * Called by the webview widget when setup is complete?
 */
PageAssistant.prototype.ready = function() {

	try {
		var self = this;

		// Update the web preferences.
		AppAssistant.WebPreferences.activate(this._webView.mojo);

		// Set the initial orientation.
		this._setOrientation(this._orientation);

		// Register any system redirects for this application.
		if (this._webView) {
			this._webView.mojo.addUrlRedirect('^file:', true, '', 0);
			this._webView.mojo.addSystemRedirects('com.palm.app.browser');
		}
		
		// Test the network and setup our network alerts.
		var netAlert = function(params){

			// Show a custom network alert dialog.
			self.controller.showAlertDialog({
				
				onChoose: function(value) {
					
					if (value === 'help') {
						AppAssistant.launchNetworkHelp();
					}
				},
				
				title: $L('No Internet Connection'),
				message: $L('Please enable networking before using the browser.'),
				choices: [{
						label: $L('Help'),
						value: 'help',
						type: 'dismiss',
						buttonClass: 'secondary'
					}, {
						label: $L('OK'),
						value: 'OK',
						type: 'alert',
						buttonClass: 'primary'
					}]
			});
			
			// We have shown a dialog so defocus anything on the scene to avoid
			// input.
			self._addressBar.blur();
		};

		AppAssistant.Network.addNetworkCheckedMethods({
			target: this,
			methods: ['openUrl', '_goBack', '_goForward', '_reload'],
			onNetworkDown: netAlert
		});

		// Never display the search results on first launch.
		this._addressBar.enableTitleView();
		this._addressBar.closeSearchResults();
		this._pageControls.showIdle();

		// Hide the address bar if we have a URL or identifier
		if (this._getStartupUrl() || (this._getStartupPageIdentifier() !== undefined)) {
			this._addressBar.hide();
			this.chrome.hide();
		}

		// On first launch we alway attempt to connect to the network if we are not online.
		if (!AppAssistant.Network.online) {

			ConnectionWidget.connect({
				type: 'data',
				onSuccess: function(response){
					// If the connection widget was previously cancelled and we
					// are under its internal timeout limit then popup the network
					// alert dialog.
					if (response === "WiFi-UserCancelled") {
						// Show the simple alert
						netAlert({});
					}
				}
			}, self.controller.stageController);
		}

		// One way we can currently load a new webpage is to pass a
		// URL via the setup params to the webview widget so we need
		// to strip the URL of trailing/preceeding whitespace before
		// submission.
		var url = this._getStartupUrl();
		if (url) {
			this.openUrl(url);
		}
	}
	catch (e) {
		Mojo.Log.logException(e, 'ready');
	}
};

PageAssistant.prototype._onAdapterConnect = function(event) {
	Mojo.Log.info("Connected to browser server.");
	this._browserServerStatus.connected();
};

PageAssistant.prototype._onAdapterDisconnect = function(event) {
	Mojo.Log.error("Disconnected from browser server.");
	this._browserServerStatus.disconnected();
};

PageAssistant.prototype._onCardActivate = function(event) {

	this.hasFocus = true;

	// Update any pending web preferences changes
	AppAssistant.WebPreferences.activate(this._webView.mojo);

	// If we are 'full-screen' the show activate version of the browser
	// server status.
	this._browserServerStatus.showActivateState();
};

PageAssistant.prototype._onCardDeactivate = function(event) {

	this.hasFocus = false;

	// If we have been 'minimized' then show the deactivated version of
	// the browser server status.
	this._browserServerStatus.showDeactivateState();

	// Bookmark dialog must be dismissed if the window loses focus
	if (this._bookmarkDialog) {
		this._bookmarkDialog.mojo.close();
		this._bookmarkDialog = null;
	}

	// Dismiss any search lists and switch to 'title' mode if we
	// are not at the start page otherwise switch to URL mode.
	this._addressBar.closeSearchResults();
	this._addressBar.enableTitleView();
};

PageAssistant.prototype._addAsScrollListener = function(event) {

	if (event.target === this.controller.getSceneScroller()) {
		event.scroller.addListener(this);
	}
};

PageAssistant.prototype._setupMenus = function() {

	try {
		// Setup the navigation and page load buttons.
		this._pageControls = new PageControls(this.controller);
		// We enable IDLE command button for page-assistant.
		this._pageControls.setup(true);

		// Create the application menus and shortcuts (once the modifier keys work).
		this.appMenuModel = {
			visible: true,
			items: [
				MenuData.ApplicationMenu.NewCard,
				MenuData.ApplicationMenu.AddBookmark,
				{
					label: $L("Page"),
					items: [
						MenuData.ApplicationMenu.AddToLauncher,
						MenuData.ApplicationMenu.SharePage]
				},
				MenuData.ApplicationMenu.ShowBookmarks,
				MenuData.ApplicationMenu.ShowHistory]
		};

		this.controller.setupWidget(Mojo.Menu.appMenu, undefined, this.appMenuModel);

		// Create the URL Bar
		this._addressBar = new AddressBar(this.controller);
		this._addressBar.setup({
			onPropertyChange: this._onAddressBarPropertyChange.bind(this),
			orientation: this._orientation,
			onSelect: this._onAddressBarSelect.bind(this),
			onEnableSceneScroller: this._onEnableSceneScroller.bind(this)
		});

		// Create the chrome helper.
		this.chrome = new Chrome(this.controller);
		this.chrome.setup({
			elementName: 'view_menu_bkgnd',
			orientation: this._orientation
		});

	} catch(e) {

		Mojo.Log.logException(e, "PageAssistant#_setupMenus");
	}
};

PageAssistant.prototype._onEnableSceneScroller = function(enable) {

	if (enable) {
		this.enableSceneScroller();
	} else {
		this.disableSceneScroller();
	}
};

PageAssistant.prototype._onAddressBarSelect = function(selection) {
	
	if (selection instanceof UrlReference) {
		// It's a bookmark
		this.openBookmark(selection);
	} else {
		// It's a simple string URL otherwise.
		this.openUrl(selection);
	}
};

/**
 * Called when the URL entry field text is submitted. This occurs on
 * both a blur and a user pressing the ENTER key. We therefore test
 * for the cause of the event before performing a URL submission.
 *
 * @param {Object} event
 */
PageAssistant.prototype._onAddressBarPropertyChange = function(propertyChangeEvent){

	try {
		// With the new TextField widget we can query the action that
		// caused this event on the URL text field by looking at the
		// originalEvent property. Nice.
		var url;
		switch (propertyChangeEvent.type) {

			case 'keyCode':

				// If we receive a key event then always show the address bar.
				if (!this._addressBar.isVisible()){
					this._addressBar.show();
				}

				url = propertyChangeEvent.value;
				
				// We keep the title insync with an address update. 
				this._addressBar.setAddressAndTitle(url, UrlUtil.cleanup(url));
				if (propertyChangeEvent.keyCode === Mojo.Char.enter) {
					// Clear the timers and hide the search list.
					this._addressBar.closeSearchResults();

					// Check we have a URL and if not then don't do anything.
					if (url) {
						this.openUrl(this._addressBar.convertInputToUrl(url));
					}
					else {
						this._addressBar.hide();
						this.chrome.hide();
						this._webView.mojo.focus();
					}
				}
				break;

			case 'blur':

				// Always switch to title mode on a blur.
				this._addressBar.enableTitleView();
				this._addressBar.closeSearchResults();

				// Hide the URL bar is we aren't over-scrolled.
				if (!this.chrome.isVisible()) {
					this._addressBar.hide();
					this._webView.mojo.focus();
				}
				break;
				
			case 'mojo-property-change':
			
				// Update the title with the new URL resulting from a copy/paste action.
				url = propertyChangeEvent.value;
				this._addressBar.setAddressAndTitle(url, UrlUtil.cleanup(url));
				break;

			default:
				Mojo.Log.warn("Unknown propertyChangeEvent");
				break;
		}
	}
	catch (e) {
		Mojo.Log.logException(e, '_onAddressBarPropertyChange');
	}
};

PageAssistant.prototype._safeGetSafeStringLength = function(str) {
	if (str === null || str === undefined) {
		return 0;
	}
	else {
		return str.length;
	}
};

PageAssistant.prototype._onEditorFocused = function(event) {

	// If the URL doesn't have focus then set the focus to the
	// webview widget.
	this._editorInPageFocused = event.focused;
};

/**
 * Setup the web view.
 */
PageAssistant.prototype._setupWebView = function() {
	var params = {};

	params.pageIdentifier = this._getStartupPageIdentifier();
	params.showClickedLink = true;

	this.controller.setupWidget('web_view', params);
};

/**
 * Return the page identifier (if there is one) sent with the launch
 * params. This is used when creating a new page.
 */
PageAssistant.prototype._getStartupPageIdentifier = function() {

	if (this.launchParams !== undefined) {
		return this.launchParams.pageIdentifier;
	}
	else {
		return undefined;
	}
};

/**
 * Return the startup URL for this page. This may be passed in as a parameter or
 * and if not then it's the user's preferences. Will return undefined if the
 * launch params also contain a page identifier.
 */
PageAssistant.prototype._getStartupUrl = function() {
	var url = undefined;
	var pageIdentifier = undefined;

	if (this.launchParams !== undefined && this.launchParams.target !== undefined) {
		url = this.launchParams.target;
		pageIdentifier = this.launchParams.pageIdentifier;
	}
	else {
		var query = location.toString().toQueryParams();
		if (query) {
			url = query.url;
		}
	}

	if (pageIdentifier !== undefined) {
		url = undefined;
	}

	return url;
};

/**
 * Navigate this page to the specified URL.
 * @param {Object} url
 */
PageAssistant.prototype.openUrl = function(newurl) {

	try {
		// This URL the user will submit to the webview widget. We are
		// careful to strip leading/trailing whitespace before submission.
		var url = newurl && newurl.strip();
		this._submittedUrl = url;

		// For fresh pages we make sure the editorInPageFocused flag is cleared.
		// and let the callback event tell us otherwise.
		this._editorInPageFocused = false;
		delete this.currentTitle;
		delete this.currPageBookmark; // will be set after this load starts if loading bookmark

		// HI requirement. Switch the addressbar to a title mode and
		// explicitly set the focus to the webview widget. 
		this._addressBar.setAddressAndTitle(url, UrlUtil.cleanup(url));
		this._addressBar.enableTitleView();
		this._webView.mojo.openURL(url);
		this._webView.mojo.focus();
		this._handleAction('useropen');
	}
	catch (e) {
		Mojo.Log.logException(e, 'openUrl');
	}
};


/**
 * Go to the URL specified by the URL reference and update the bookmark.
 *
 * @param {UrlReference} bookmark The bookmark
 */
PageAssistant.prototype.openBookmark = function(bookmark) {
	try {
		this.openUrl(bookmark.url);
		this.currPageBookmark = bookmark;
		this.bookmarkStore.updateVisitCount(bookmark, function() {
			Mojo.Log.info("Successfully updated visitedCount");
		}, function() {
			Mojo.Log.info("Successfully updated visitedCount");
		});
	}
	catch (e) {
		Mojo.Log.logException(e, 'openBookmark');
	}
};

/**
 * Return the title of the page for display purposes. If there is no
 * title then a cleaned up URL is returned.
 */
PageAssistant.prototype._getDisplayTitle = function() {

	if (this.currentTitle && this.currentTitle.length > 0) {
		return this.currentTitle;
	}
	else {
		return UrlUtil.cleanup(this.currentUrl);
	}
};

PageAssistant.prototype._updateHistory = function() {

	if (!this.currentUrl || (this.currentUrl.length === 0)) {
		return;
	}

	var title = this.currentTitle || null;
	this.historyStore.addHistory(this.currentUrl, title, new Date(),
		function() {},
		function(transaction, err) {
			Mojo.Log.error("Failure adding history entry: code: %d, message:'%s'.", err.code, err.message);
		}
	);
};

/**
 * Called with both the title and URL changes. This event comes *after* loadStopped.
 * @param {Object} event The event object.
 */
PageAssistant.prototype._onTitleUrlChange = function(event) {

	try {
		// Force all titles from this point forward to be single line entities
		// by removing any newlines. We will also strip the title of any leading
		// and trailing spaces. We do all this because many of the visuals expect
		// the title to be a single line.
		this.currentTitle = event.title && event.title.replace(/\n/g, ' ').strip();
		this.currentUrl = event.url;

		// Clear any cached URLs
		this._submittedUrl = undefined;

		// We use the URL changed event to determine if we should show
		// a secure-lock graphic in the title bar.
		if (this.currentUrl && this.currentUrl.toUpperCase().startsWith('HTTPS://')) {
			this._addressBar.enableSSLLock(true);
		} else {
			this._addressBar.enableSSLLock(false);
		}

		// Configure the URL bar to reflect a URL/Title change
		if (!this._addressBar.hasFocus()) {
			var title = this.currentTitle || UrlUtil.cleanup(this.currentUrl);
			this._addressBar.setAddressAndTitle(event.url, title);
			this._addressBar.enableTitleView();
		}

		// Update the command menu and history after a  title change.
		this._updateHistory();
		this._updateHistoryState();
	}
	catch (e) {
		Mojo.Log.logException(e, '_onTitleUrlChange');
	}
};

/**
 * Handle a tap-rejected event on the page. This is only called if the
 * tap is on a non-interactive portion of the page
 */
PageAssistant.prototype._onTapRejected = function(event) {

	try {
		if (!this._webView) {
			return;
		}

		// On a Tap we hide the search list and switch to title and clear
		// any cached URLs.
		this._addressBar.enableTitleView();
		this._addressBar.closeSearchResults();

		// If we are NOT overscrolled then hide the address bar
		if (!this.chrome.isVisible()) {
			this._addressBar.hide();
			this._webView.mojo.focus();
		}
	}
	catch (e) {
		Mojo.Log.logException(e, "_onTapRejected");
	}
};

PageAssistant.prototype._onDragStart = function(event) {

};

PageAssistant.prototype._isResource  = function(uri) {

	// Parse the URL
	try {
		var p = new Poly9.URLParser(uri);

		// Get the protocol and then normalize the text
		var protocol = p.getProtocol();
		if (protocol) {
			protocol = protocol.toLowerCase();
		}

		return (!protocol || (protocol == 'http') || (protocol == 'https'));
	}
	catch (e) {
		return false;
	}
};

PageAssistant.prototype._isValidMimeType = function(mimeType)
{
	return mimeType !== '' && mimeType !== 'unknown';
};

/**
 * Retrieve the information for a given resource.
 *
 * @param {String} uri	The URI of the resource to retrieve the information for.
 * @param {String} mimeType	The mimeType of the resource to retrieve the information for.
 * @param {function} onSuccess	Called when call is successful
 * @param {function} onFailure	Called when call fails
 */
PageAssistant.prototype._getResourceInfo = function(uri, mimeType, onSuccess, onFailure) {

	var params = {uri: uri};

	if (this._isValidMimeType(mimeType)) {
		params.mime = mimeType;
	}

	this.controller.serviceRequest('palm://com.palm.applicationManager/getResourceInfo', {
		parameters: params,
		onSuccess: onSuccess,
		onFailure: onFailure
	});
};

PageAssistant.prototype._newBrowserPage = function(url, pageIdentifier){

	var params = {scene: 'page'};
	if (url !== undefined) {
		params.target = url;
	}
	if (pageIdentifier !== undefined) {
		params.pageIdentifier = pageIdentifier;
	}

	this.controller.serviceRequest('palm://com.palm.applicationManager', {
		method: 'open',
		parameters: {
			'id': 'com.palm.app.browser',
			'params': params
		}
	});
};

/**
 * Stream the resource. The caller should have already called getResourceInfo
 * to determine that this application can stream.
 *
 * @param {String} uri The resource to stream.
 * @param {String} appid The application id to open.
 * @param {String} mimeType uri mime type.
 */
PageAssistant.prototype._streamResource = function(uri, appid, mimeType){

	Mojo.Log.error("Streaming: '%s' with '%s' (%s)", uri, appid, mimeType);

    this._downloadWidgetElement = this.controller.showDialog({
        uri: uri,
        mimeType: mimeType,
        appid: appid,
        template: 'download/download-stream-popup',
        assistant: new DownloadDialogAssistant({
        sceneAssistant: this,
        onDismiss: function(cParams) { // DOWNLOAD
                //this._onPopupHandler('close');
                this._downloadController.downloadResource(uri);
        }.bind(this),
        onAccept: function(cParams) { // STREAM
                var params = {target: uri, mimeType: mimeType, appid: appid};
                // Only a few select applications can be
                var crossAppScene = {
                    'com.palm.app.videoplayer': 'nowplaying',
                    'com.palm.app.streamingmusicplayer': 'nowplaying'
                };
                //this._onPopupHandler('close');
                if (crossAppScene[appid]) {
                    var args = { appId: appid, name: crossAppScene[appid] };
                    this.controller.stageController.pushScene(args, params);
                }
                else {
                    this._downloadController.downloadResource(uri);
                }
        }.bind(this)})
    });
    // Record we have a popup
    //this._onPopupHandler('open');

};

/**
 * Download a resource using the download manager.
 *
 * @param {String} uri	The resource to download
 */
PageAssistant.prototype._downloadResource = function(uri) {

        Mojo.Log.info("Downloading: " + uri);

        try {
                // We should no longer download a resource but inform the user
                // we are unable to perform the download.
                if (!this._downloadWidgetElement) {
                        this._downloadWidgetElement = this.controller.showDialog({
                                template: 'download/download-popup',
                                assistant: new DownloadDialogAssistant({
                                        sceneAssistant: this,
                                        onDismiss: function() {
                                                //this._onPopupHandler('close');
                                                delete this._downloadWidgetElement;
                                        }.bind(this),
                                        onAccept: function() {
                                                //this._onPopupHandler('close');
                                                this._downloadController.downloadResource(uri);
                                        }.bind(this)})
                                });

                        // Record we have a popup
                        //this._onPopupHandler('open');
                }
	} catch (e) {
		Mojo.Log.logException(e, "#_downloadResource");
	}

	// Hide the address bar.
	this._addressBar.hide();
	this._webView.mojo.focus();
};

/**
 * Open a resource using the applicationManager.
 */
PageAssistant.prototype._openResource = function(uri, mimeType) {
	var params = { target: uri };
	if (mimeType) {
		params.mime = mimeType;
	}

	this.controller.serviceRequest('palm://com.palm.applicationManager', {
			method: 'open',
			parameters: params,
			onSuccess: function(item, response) {
				if (response.returnValue) {
					Mojo.Log.info("Successfully handed off '%s'", uri);
				}
				else {
					Mojo.Log.error("Error handing off '%s', msg:'%s'", uri, response.errorText);
				}
			},
			onFailure: function(response) {
				Mojo.Log.error("Failure handing off '%s', msg:'%s'", uri, response.errorText);
			}
		});
};

/**
 * WebView widget isn't going to handle this "main" resource so this
 * is our callback.
 *
 * @param {Object} event @see Mojo.Widget.WebView
 */
PageAssistant.prototype._onWebViewResourceHandoff = function(event) {
	try {
		Mojo.Log.info("Just got handed off a resource: '%s' (%s)", event.url, event.mimeType);

		if (event.url && event.url.toUpperCase().startsWith("FILE:")) {
			this.controller.showAlertDialog({
				onChoose: function(value) { /* Do Nothing */},
				message: $L('Cannot display local files.'),
				choices:[{label:$L('OK'), value:'1', type:'dismiss'}]
			});

			// Dismiss any open search list and dismiss the address bar if
			// if we are overscrolled.
			this._addressBar.enableTitleView();
			this._addressBar.closeSearchResults();
			if (!this.chrome.isVisible()) {
				this._addressBar.hide();
				this._webView.mojo.focus();
			}
			
			// Signal the page controls we've stopped attempting to load
			// a local resource.
			this._handleAction('userstop');
			return;
		}

		var getResInfoSuccess = function(response) {
			if (response.returnValue) {
				if (response.appIdByExtension === 'com.palm.app.browser') {
					// Don't handoff to app mgr because it'll just reopen me and I'll 
					// wind up in an infinite card open loop. (NOV-58615).
					this._downloadResource(event.url);
				}
				else {
					if (response.canStream) {
						this._streamResource(event.url, response.appIdByExtension, response.mimeByExtension);
					}
					else if (UrlUtil.isCommand(event.url)) {
						this._openResource(event.url, event.mimeType);
					}
					else {
						this._downloadResource(event.url);
					}
				}
			}
			else {
				Mojo.Log.info("Resource not streamed, downloading");
				this._downloadResource(event.url);
			}
		}.bind(this);

		var getResInfoFailure = function(response) {
			Mojo.Log.error("Error getting resource info for '%s':(%s)", event.url, event.mimeType);
			Mojo.Log.info("Resource not streamed, downloading");
			this._downloadResource(event.url);
		}.bind(this);

		this._getResourceInfo(event.url, event.mimeType, getResInfoSuccess, getResInfoFailure);
	}
	catch (e) {
		Mojo.Log.logException(e, '_onWebViewResourceHandoff');
		throw e;
	}
};

/**
 * Called when the WebView has redirected a URL to me. Handoff to the applicationManager.
 */
PageAssistant.prototype._onUrlRedirect = function(event) {
	Mojo.Log.info("Redirecting URL '%s'", event.url);
	this._onWebViewResourceHandoff(event);
};

PageAssistant.prototype._onModifierTap = function(event) {
	try {
		Mojo.Log.info("User just meta tapped on the webview widget");
		if (event.linkInfo.success) {
			Mojo.Log.info("User meta-tapped on '%s'", event.linkInfo.url);
			this._newBrowserPage(event.linkInfo.url);
		}
		else {
			Mojo.Log.info("User meta-tapped on nothing");
		}
	}
	catch (e) {
		Mojo.Log.logException(e, '_onModifierTap');
	}
};

PageAssistant.prototype._onLoadProgress = function(event) {
	this._handleAction('progress', {progress: event.progress});
};

PageAssistant.prototype._onLoadStarted = function(event) {

	this.loadingPage = true;
	this.firstPageLoaded = true;

	// If the page is loading then hide and disable chrome and
	// the URL bar
	if (!this._addressBar.hasFocus()) {
		this._addressBar.hide();
		this.controller.get('web_view').mojo.focus();
	}

	this.chrome.hide();
	this._handleAction('start');
};

function SimpleOkDialogAssistant(sceneAssistant, onClose) {
	this.controller = sceneAssistant.controller;
	this._okButtonHandler = this.handleOk.bindAsEventListener(this);
	this.onClose = onClose;
}

SimpleOkDialogAssistant.prototype.setup = function(widget) {
	this.widget = widget;
};

SimpleOkDialogAssistant.prototype.cleanup = function() {
	if (this.onClose) {
		this.onClose();
	}
};

SimpleOkDialogAssistant.prototype.activate = function() {
	this.controller.get('okButton').addEventListener(Mojo.Event.tap, this._okButtonHandler, false);	
};

SimpleOkDialogAssistant.prototype.deactivate = function() {
	this.controller.get('okButton').removeEventListener(Mojo.Event.tap, this._okButtonHandler, false);
};

SimpleOkDialogAssistant.prototype.handleOk = function(widget) {
	
	this.widget.mojo.close();
};

PageAssistant.prototype._onSetMainDocumentError = function(event) {

	if (event.errorCode === WebKitErrors.ERR_WK_FLOADER_CANCELLED) {
		// We get this when the user does something to interrupt a page load.
		return;
	}

	// Create the error assistant once and once only. (load on demand)
	if (!this.errorDialogAssistant) {
		this.errorDialogAssistant = new SimpleOkDialogAssistant(this,
			function() {
				// HI requirement. Jump to URL bar after dialog closes.
				this._gotoUrlBar.bind(this, this._jumpToUrl).defer();
			}.bind(this));
	}

	// If we have a submitted URL then use this as the source of the
	// error else we switch to using the failing URL given to us from
	// the event. The submitted URL gets wiped when a new Title/Url
	// change event comes in so we persist the submitted URL here
	// for use by the alert dialog.  
	//Mojo.Log.info("@@@@ SUBMITTED URL : ", this._submittedUrl);
	//Mojo.Log.info("@@@@ FAILING URL: ", event.failingURL);
	this._jumpToUrl = this._submittedUrl || event.failingURL;
	
	var msg;
	switch (event.errorCode) {
		case WebKitErrors.ERR_SYS_FILE_DOESNT_EXIST:
			msg = $L('File does not exist.');
			break;
		case WebKitErrors.ERR_CURL_COULDNT_RESOLVE_HOST:
			msg = $L('Unable to resolve host.');
			break;
		case WebKitErrors.ERR_CURL_SSL_CACERT:
			msg = $L('Unable to verify certificate.');
			break;
		default:
			Mojo.Log.warn("Got an error with no mapping to a message '%s'", event.message);
			msg = $L('Error loading page.');
	}

	if (msg.length > 110) {
		// Trying to limit the number of lines to approximately 3.
		msg = msg.truncate(110);
	}
	else if (!msg.endsWith('.')) {
		msg += '.';
	}

	// Display the error.
	this.controller.showDialog({
		template: 'page/loadfailed-dialog',
		assistant: this.errorDialogAssistant,
		message: msg,
		errorCode: event.errorCode,
		failingURL: event.failingURL
	});
};


/**
 * Get's called multiple times when loading a page.
 * @param {Object} event
 */
PageAssistant.prototype._onDidFinishDocumentLoad = function(event) {
	
	// Moved thumbnail creation code to on recieving a LoadStopped event.
 };

PageAssistant.prototype._onLoadStopped = function(event) {

	/*
	 * On a stop message we switch state to STOP (which
	 * is the IDLE view)
	 */
	this._handleAction('stop');

	// If the page has loaded then hide and disable chrome on
	// the URL bar ONLY if the user doesn't have the URL bar
	// active.
	if (!this._addressBar.hasFocus()) {
		this._addressBar.hide();
		this.chrome.hide();
	}

	// Take a thumbnail snapshot after load is completed.
	try {
		if (this.loadingPage) {
			this.loadingPage = false;
			if (this.currPageBookmark) {
				// Only update the thumbnail image if we are not
				// a default bookmark.
				if (!this.currPageBookmark.defaultEntry) {
					this._updateCurrentBookmarkThumbnailImage();
				}
			}
		}
	} catch (e) {
		Mojo.Log.logException(e, '_onLoadStopped');
	}
};

PageAssistant.prototype._updateHistoryState = function() {
	
	var backForward = function(back, forward) {

		this._wkCanGoBack = back;
		this._wkCanGoForward = forward;

		// Update the UI back/forward - We can always go back.
		this._pageControls.updateBackForward(true, this._wkCanGoForward);

	}.bind(this);

	try {
		this._webView.mojo.getHistoryState(backForward);
	} catch (e) {
		Mojo.Log.logException(e, '_updateHistoryState');
	}	
};

PageAssistant.prototype._onUpdateHistory = function(event) {

	this._updateHistoryState();
};

/**
 * Update the thumbnail image for the current bookmark.
 */
PageAssistant.prototype._updateCurrentBookmarkThumbnailImage = function() {

	Mojo.Log.info("Updating page thumbnail");
	var now = new Date();
	var oldThumbnail = this.currPageBookmark.thumbnailFile;
	this.currPageBookmark.thumbnailFile = CustomizeiconAssistant.createBrowserImageName('thumbnail', now.getTime());
	var bookmark = this.currPageBookmark;

	delete this.currPageBookmark;
	try {
		var webView = this.controller.get('web_view');
		webView.mojo.saveViewToFile(bookmark.thumbnailFile, 0, 0, this.kBookmarkSrcWidth, this.kBookmarkSrcHeight);
		// Now save it to the database
		this.bookmarkStore.updateThumbnail(bookmark, function() {
			Mojo.Log.info("Successfully updated thumbnail to %s.", bookmark.thumbnailFile);
			this.deleteImage(oldThumbnail);
		}.bind(this), function() {
			Mojo.Log.error("Failed to update thumbnail.");
			this.deleteImage(bookmark.thumbnailFile);
			bookmark.thumbnailFile = oldThumbnail;
		}.bind(this));
	}
	catch (e) {
		Mojo.Log.logException(e, '_updateCurrentBookmarkThumbnailImage');
		bookmark.thumbnailFile = oldThumbnail;
	}

};

PageAssistant.prototype._onCreatePage = function(event){
	Mojo.Log.info("PageAssistant#_onCreatePage: " + event.pageIdentifier);
	this._newBrowserPage(undefined, event.pageIdentifier);
};

/**
 * Create a new browser page stage.
 *
 * @param {String} url The URL to which this page should originally display (undefined = default)
 * @param {String} pageIdentifier The page identifier (used when responding to BrowserServer request to
 *        create a new page).
 */
PageAssistant.prototype._newBrowserPage = function(url, pageIdentifier){

	var params = {scene: 'page'};
	if (url !== undefined) {
		params.url = url;
	}
	if (pageIdentifier !== undefined) {
		params.pageIdentifier = pageIdentifier;
	}

	this.controller.serviceRequest('palm://com.palm.applicationManager', {
		method: 'open',
		parameters: {
			'id': 'com.palm.app.browser',
			'params': params
		}
	});
};

/**
 * Sometimes we need to delete an image in a scene which has no WebView widget and hence
 * no
 * @param {String} filename The full path to the file to delete. Can be null/undefined if
 *                  the caller just wants to do all the deferred deletes.
 */
PageAssistant.prototype.deleteImage = function(filename) {

	try {
		var webView = this.controller.get('web_view');	// May be undefined.

		if (filename) {
			webView.mojo.deleteImage(filename);
		}

		while (this.imagesToDelete.length > 0) {
			filename = this.imagesToDelete.shift();
			Mojo.Log.info("Deleting (deferred) '%s'", filename);
			webView.mojo.deleteImage(filename);
		}
	}
	catch (e) {
		Mojo.Log.info("Unable to delete '%s', will try later.", filename);
		this.imagesToDelete.push(filename);
	}
};

/**
 * Share the current page with an email recipient.
 */
PageAssistant.prototype._shareCurrentPage = function() {

	if (this.currentUrl === undefined) {
		return;
	}

	// Create a screen capture
	var captureFile = '/tmp/captures/browser_page.png';

	this.controller.get('web_view').mojo.saveViewToFile(captureFile);

	var msg = $L('Hey you gotta check out ') + '<a href="' + this.currentUrl + '">' + this._getDisplayTitle() + '</a>';
	var parameters = {
		id: 'com.palm.app.email',
		params: {
			summary: $L('Check out this web page...'),
			text: msg
		}
	};

	parameters.params.attachments = [{fullPath: captureFile}];

	this.controller.serviceRequest('palm://com.palm.applicationManager', {
		method: 'open',
		parameters: parameters
	});
};

/**
 * Called whenever the scroller is moved.
 */
PageAssistant.prototype.moved = function() {

	// Extract the current position and use the value to determine how
	// the UrlBar should be displayed.
	// We can keep calling animate because if an existing animation
	// on and elements attributes already exists it automagically
	// gets cancelled.
	var pos = this.controller.getSceneScroller().mojo.getScrollPosition();

	if (pos.top < 0) {
		// We have moved the scene above (0,0) BUT we only hide the
		// URL bar IF if doesn't have focus.
		if (!this._addressBar.hasFocus()) {
			this._addressBar.hide();
			this.chrome.hide();

			// Only focus this webview widget if this assistant has
			// focus. All page-assisants can receive 'moved' events
			// so we have to be careful when explicitly setting the
			// focus.
			if (this.controller.stageController.focused) {
				this._webView.mojo.focus();
			}
		}
		else {
			//Mojo.Log.info("-> TOP < 0 AND FOCUS SO NOT HIDING");
			// We always dispose of the spacer
			this.chrome.hide();
		}



	} else if (pos.top > 0) {
		// We always animate the spacer and show the bar if we
		// have dragged off the top.
		//Mojo.Log.info("-> TOP > 0 SO SHOWING");
		this._addressBar.show();
		this.chrome.show();

	} else {
		//Mojo.Log.info("-> TOP == 0 Do Nothing.");
		// Do nothing.
	}
};

PageAssistant.prototype._goBack = function() {

	if (this._wkCanGoBack) {
		try {
			this._handleAction('userback');
			// For fresh pages we make sure the editorInPageFocused flag is cleared.
			// and let the callback event tell us otherwise.
			this._editorInPageFocused = false;
			this._addressBar.enableTitleView();
			this._webView.mojo.goBack();
		} catch (e) {
			Mojo.Log.logException(e, "Page Back");
			this._handleAction('userstop');
		}
	}
	else {
		this._showStartPage();
	}
};

PageAssistant.prototype._goForward = function() {

	try {
		this._handleAction('userforward');
		// For fresh pages we make sure the editorInPageFocused flag is cleared.
		// and let the callback event tell us otherwise.
		this._editorInPageFocused = false;
		this._addressBar.enableTitleView();
		this._webView.mojo.goForward();
	} catch (e) {
		Mojo.Log.logException(e,  "Page Foward");
		this._handleAction('userstop');
	}
};

PageAssistant.prototype._reload = function() {

	try {
		this._handleAction('userreload');
		this._addressBar.enableTitleView();
		this._webView.mojo.reloadPage();
	} catch (e) {
		Mojo.Log.logException(e, "Page Reload");
		this._handleAction('userstop');
	}
};

PageAssistant.prototype._stopLoad = function() {

	try {
		this._handleAction('userstop');
		this._addressBar.enableTitleView();
		this._webView.mojo.stopLoad();
	} catch (e) {
		Mojo.Log.logException(e, 'User Stop');
	}
};

/**
 * handle a menu command.
 */
PageAssistant.prototype.handleCommand = function(event) {
	try {
		var urlReference;

		if (event.type == Mojo.Event.back) {
			// Halt any loading currently running.
			this._webView.mojo.stopLoad();

			// On the first BACK we dismiss any search list and
			// switch the URL bar to title mode.
			//
			// If no search list is visible but the URL bar IS
			// visible and 'chrome/spacer' is NOT visible we
			// switch the title mode and dismiss the URL bar.
			//
			// If no search list is visible but the URL bar IS
			// visible and 'chrome/spacer' IS visible we
			// switch to title mode and keep the URL bar in view.
			//
			// If no search list is visible and the URL bar IS
			// visible and in title mode and 'chrome/spacer' IS
			// visible we perform the default back operations.
			//
			// Otherwise the default back operation is performed.
			//
			if (this._addressBar.areSearchResultsVisible()) {

				this._addressBar.enableTitleView();
				this._addressBar.closeSearchResults();
				if (!this.chrome.isVisible()) {
					this._addressBar.hide();
					this._webView.mojo.focus();
				}
				event.preventDefault();
				event.stopPropagation();
			}
			else if (this._addressBar.isVisible()) {
				// If we have the spacer visible then switch to title mode.
				// If we are in title mode then we go back a page.
				if (this.chrome.isVisible()) {
					if (this._addressBar.isInUrlView()) {
						this._addessBar.enabelTitleView();
						this._webView.mojo.focus();
						event.preventDefault();
						event.stopPropagation();

					} else if (this._wkCanGoBack) {
						event.preventDefault();
						event.stopPropagation();
						this._goBack();

					} else {
						// If we can't go back further in history we show the startpage.
						event.preventDefault();
						event.stopPropagation();
						this._showStartPage();
					}
				} else {
					this._addressBar.hide();
					this.chrome.hide();
					this._webView.mojo.focus();
					event.preventDefault();
					event.stopPropagation();
				}
			}
			else if (this._wkCanGoBack) {
				event.preventDefault();
				event.stopPropagation();
				this._goBack();
			} else {
				// If we can't go back further in history we show the startpage.
				event.preventDefault();
				event.stopPropagation();
				this._showStartPage();
			}
		}
		else if (event.type == Mojo.Event.forward) {

			// NOTE: According to HI we should keep this simple 
			// and just move forward.
			if (this._wkCanGoForward) {
				this._addressBar.enableTitleView();
				this._addressBar.closeSearchResults();
				this._webView.mojo.focus();

				event.preventDefault();
				event.stopPropagation();
				this._goForward();
			}			
		}
		else if (event.type == Mojo.Event.command) {

			switch (event.command) {
				case MenuData.ApplicationMenu.ShowHistory.command:
					this._openHistoryView();
					break;

				case MenuData.ApplicationMenu.ShowBookmarks.command:
					this._openBookmarkView();
					break;

				case MenuData.ApplicationMenu.NewCard.command:
					this._newBrowserPage();
					break;

				case MenuData.ApplicationMenu.AddBookmark.command:
					urlReference = new UrlReference(this.currentUrl, this.currentTitle, new Date());
					this._createDefaultBookmarkImages(urlReference);
					this.showBookmarkDialog(BookmarkDialogAssistant.createBookmarkTask, urlReference);
					break;

				case MenuData.ApplicationMenu.AddToLauncher.command:
					// Bookmarks can deal with one that has no title, but not the launcher
					urlReference = new UrlReference(this.currentUrl, this._getDisplayTitle(), new Date());
					this._createDefaultBookmarkImages(urlReference);
					this.showBookmarkDialog(BookmarkDialogAssistant.createLaunchpointTask, urlReference);
					break;

				case MenuData.ApplicationMenu.SharePage.command:
					this._shareCurrentPage();
					break;

				case MenuData.NavigationMenu.Back.command:
					this._goBack();
					break;

				case MenuData.NavigationMenu.Forward.command:
					this._goForward();
					break;

				case MenuData.NavigationMenu.Reload.command:
					this._reload();
					break;

				case MenuData.NavigationMenu.Stop.command:
					this._stopLoad();
					break;

				case UrlBar.Menu.TitleTap.command:
					this._processTitleTapCommand();
					break;

				case UrlBar.Menu.Go.command:
					this._processGoCommand();
					break;

				case Mojo.Menu.prefsCmd:
					this._openPreferencesView();
					break;

				case Mojo.Menu.helpCmd:
					this._showHelpCommand();
					break;
			}
		}
		else {

			if (event.type === Mojo.Event.commandEnable) {

				// Standard Application Menu commands.
				if (event.command === Mojo.Menu.prefsCmd ||
					event.command === Mojo.Menu.helpCmd) {

					event.stopPropagation(); // Enable the chosen menuitems
					return;
				}

				// Application specific Applicaiton Menu commands.
				if (event.command === MenuData.ApplicationMenu.SharePage.command) {
					// Only enable the menu items if we have a valid
					// pages... (define a valid page)
					if (!this.currentUrl) {
						event.preventDefault();
						return;
					}
				}

				if (event.command === MenuData.ApplicationMenu.AddToLauncher.command ||
					event.command === MenuData.ApplicationMenu.AddBookmark.command) {

					// Only enable the menu items if we have a valid
					// pages... (define a valid page)
					if (!this.currentUrl) {
						event.preventDefault();
						return;
					}

					// If we have a bookmark/launcher dialog up then disable the
					// app menu
					if (this._bookmarkDialog) {
						event.preventDefault();
						return;
					}
				}
			}
		}
	}
	catch (e) {
		Mojo.Log.logException(e, 'handleCommand');
	}
};

/**
 * Create the images for a bookmark: 1) thumbnail, 2) 32x32 pixel icon, and 3) 64x64 pixel image.
 * @param {UrlReference} urlReference The URL reference to create the images for.
 */
PageAssistant.prototype._createDefaultBookmarkImages = function(urlReference)
{
	var now = urlReference.date.getTime();

	var webView = this.controller.get('web_view');

	// First save out the page thumbnail image
	urlReference.thumbnailFile = CustomizeiconAssistant.createBrowserImageName('thumbnail', now);
	webView.mojo.saveViewToFile(urlReference.thumbnailFile, 0, 0, this.kBookmarkSrcWidth, this.kBookmarkSrcHeight);
	webView.mojo.resizeImage(urlReference.thumbnailFile, urlReference.thumbnailFile, this.kStartPageImageWidth, this.kStartPageImageHeight);

	// Now generate the default icon that the user can then customize
	webView.mojo.saveViewToFile(CustomizeiconAssistant.tmpCaptureFile, 0, 0, this.kStartPageImageWidth, this.kStartPageImageWidth /*yes we want it square*/);
	urlReference.tmpIconFile64 = CustomizeiconAssistant.createBrowserImageName('icon64', now);
	webView.mojo.generateIconFromFile(CustomizeiconAssistant.tmpCaptureFile, urlReference.tmpIconFile64, 0, 0,
			CustomizeiconAssistant.kIconCropRectWidth, CustomizeiconAssistant.kIconCropRectHeight);

	urlReference.tmpIconFile32 = CustomizeiconAssistant.createBrowserImageName('icon32', now);
	webView.mojo.saveViewToFile(CustomizeiconAssistant.tmpCaptureFile, 0, 0, CustomizeiconAssistant.kIconCropRectHeight, CustomizeiconAssistant.kIconCropRectHeight);
	webView.mojo.resizeImage(CustomizeiconAssistant.tmpCaptureFile, urlReference.tmpIconFile32, 32, 32);
	this.deleteImage(CustomizeiconAssistant.tmpCaptureFile);
};

/**
 * Display the bookmark dialog.
 *
 * @param {String} task	The task to perform (See BookmarkDialogAssistant).
 * @param {UrlReference} urlReference The URL reference to add. If undefined then new bookmark/launchpoint will be added.
 */
PageAssistant.prototype.showBookmarkDialog = function(task, urlReference) {

	Mojo.assert(urlReference, "Must supply valid URL reference");

	// Only show the bookmark dialog widget once...
	if (!this._bookmarkDialog) {
		// Force the dialog into portrait/up orientation.
		var targetWindow = this.controller.window;
		if (targetWindow.PalmSystem && targetWindow.PalmSystem.setWindowOrientation) {
			// Set to the default orientation on activation?
			targetWindow.PalmSystem.setWindowOrientation('up');
		}

		var closed = false;
		var onClosed = function(){
			this._bookmarkDialog = null;
			if (!closed) {
				closed = true;
			}
		}.bind(this);

		var params = {
			task: task,
			urlReference: urlReference,
			sceneController: this.controller,
			bookmarkStore: this.bookmarkStore,
			onClose: onClosed,
			deleteImage: this.deleteImage.bind(this)
		};
		
		// NOTE: Currently this dialog is non-cancellable so needs to be handled
		// directly and not rely on the framework to dismiss them on card deactivates.
		// NOTE: Any attempt to display a cancellable dialog will be automatically
		// dismissed while this dialog is visible. (see frameworks container stack).  
		this._bookmarkDialog = BookmarkDialogAssistant.showDialog(params);
	}
};


PageAssistant.prototype.kUrlInputTextFieldName = 'urlInputField';
PageAssistant.prototype.kMaxHistoryDays = 21;
PageAssistant.prototype.kStartPageImageWidth = 90;		// Keep in sync with startPageBookmark CSS class
PageAssistant.prototype.kStartPageImageHeight = 120;	// Keep in sync with startPageBookmark CSS class
PageAssistant.prototype.kBookmarkSrcWidth = 180;		// Scaled to kStartPageImageWidth
PageAssistant.prototype.kBookmarkSrcHeight = 240;		// Scaled to kStartPageImageHeight

/**
 * keydown handler. We currently use this to record key presses for later use
 * in the keypress handler until we have the modifier key fixes. We also handle
 * redirects to the URL bar if it doesn't already have focus and their are no
 * input fields  the have focus in the webview.
 * @param {Object} event
 */
PageAssistant.prototype._onKeyDownEvent = function(event) {

	// With the background scrim on dialogs we no longer have to
	// do our own popup reference count to protect against jumping
	// to the URL bar on keydown events.
		
	// If the URL textfield has the focus then do nothing.
	// If the webview has focus AND the editorInPageFocused
	// flag has NOT been set then jump to the URL text field.
	//Mojo.Log.info("@@@@ AddressBar HASFOCUS: ", this._addressBar.hasFocus());
	//Mojo.Log.info("EDITOR IN PAGE FOCUSED = " + this._editorInPageFocused);
	if (!this._addressBar.hasFocus() && !this._editorInPageFocused) {
		// Only jump to the URL bar IF we are a valid keycode that is
		// allowed to trigger the bar.
		if (this._addressBar.isAGotoAddressBarEvent(event.originalEvent)) {
			this._gotoUrlBar();
		}
	}
};

/**
 * keyup events are now ignored. 
 * @param {Object} event
 */
PageAssistant.prototype._onKeyUpEvent = function(event) {

};

/**
 * keypress events are now ignored.
 *
 * @param {Object} event
 */
PageAssistant.prototype._onKeyPressEvent = function(event){

};

/**
 * Helper function that will put the Url bar into URL input mode
 * and then set the input focus.
 */
PageAssistant.prototype._gotoUrlBar = function(url) {

	try {

		if (this._orientation === 'up') {

			// Dismiss any currently viewed searchlist
			this._addressBar.closeSearchResults();

			// Create/Show the URL bar and set input
			this._addressBar.setAddressAndTitle(url, UrlUtil.cleanup(url));
			this._addressBar.enableUrlView();
			this._addressBar.show();
			this._addressBar.focus();
		} else {
			// We do nothing for other orientations.
		}
	}
	catch (e) {
		Mojo.Log.logException(e, "PageAssistant#_gotoUrlBar()");
	}
};

PageAssistant.prototype._openBookmarkView = function() {

	Mojo.Log.info("Opening Bookmark View.");
	var targetWindow = this.controller.window;
	if (targetWindow.PalmSystem && targetWindow.PalmSystem.setWindowOrientation) {
		targetWindow.PalmSystem.setWindowOrientation("up");
	}

	this.controller.stageController.pushScene('bookmarks', AppAssistant.WebPreferences);
};

PageAssistant.prototype._openHistoryView = function() {

	Mojo.Log.info("Opening History View.");
	var targetWindow = this.controller.window;
	if (targetWindow.PalmSystem && targetWindow.PalmSystem.setWindowOrientation) {
		targetWindow.PalmSystem.setWindowOrientation('up');
	}

	this.controller.stageController.pushScene('history', AppAssistant.WebPreferences);
};

PageAssistant.prototype._openPreferencesView = function(){

	var targetWindow = this.controller.window;
	if (targetWindow.PalmSystem && targetWindow.PalmSystem.setWindowOrientation) {
		targetWindow.PalmSystem.setWindowOrientation('up');
	}

	this.controller.stageController.pushScene('preferences', AppAssistant.WebPreferences);
};

PageAssistant.prototype._openFindView = function() {
	Mojo.Log.info("Opening Find View.");
};

PageAssistant.prototype._launchNewBrowser = function() {
	Mojo.Log.info("Launching new browser.");
	this._newBrowserPage();
};
/**
 * End of keyboard shortcut handlers.
 */

PageAssistant.prototype._processGoCommand = function() {

	try {
		var value = this._addressBar.getValue();
		if (value) {
			this._addressBar.hide();
			this.chrome.hide();
			this._webView.mojo.focus();
			this.openUrl(this._addressBar.convertInputToUrl(value));
		}
		else {
			// We did nothing so switch focus back to the URL bar
			this._addressBar.focus();
		}
	} catch (e) {
		Mojo.Log.logException(e, 'PageAssistant#_processGoCommand()');
	}
};

PageAssistant.prototype._processTitleTapCommand = function() {

	this._addressBar.enableUrlView();
	this._addressBar.focus();
	this._addressBar.select();
};

PageAssistant.prototype._setOrientation = function(orientation) {

	try {
		// Notify the URL bar of orientation change
		this._addressBar.setOrientation(orientation);
		this.chrome.setOrientation(orientation);
	}
	catch (e) {
		Mojo.Log.logException(e, "PageAssistant#_setOrientation");
	}
};

PageAssistant.prototype._showHelpCommand = function() {

	// Launch the help system.
	AppAssistant.launchHelp();
};


/******************************************************************
 * Our state machine for navigation and page animations
 * @param {Object} contract
 */
PageAssistant.prototype._initialState = 'Idle';
PageAssistant.prototype._currentState = PageAssistant.prototype._initialState;

var PA = PA || {};
PA.Log = {};
PA.Log.info = function() {}; //Mojo.Log.info;
PA.Log.warn = Mojo.Log.warn;
PA.Log.error = Mojo.Log.error;
PA.Log.logException = Mojo.Log.logException;

PageAssistant.prototype._handleAction = function(someAction, details) {

	try {
		details = details || {};

		var nextState;
		var anActionFunction = this._actionFunctions[this._currentState][someAction];
		if (!anActionFunction) {
			PA.Log.warn("UNEXPECTED ACTION - STATE MACHINE: (CUR STATE: %s, ACTION: %s)", this._currentState, someAction);
			anActionFunction = this._undefinedAction;
		}

		PA.Log.info("STATE MACHINE: (CUR STATE: %s, ACTION: %s)", this._currentState, someAction);

		// call the state-machines action function with the current instance context.
		nextState = anActionFunction.call(this, details);

		PA.Log.info("STATE MACHINE: (%s -> %s -> %s)", this._currentState, someAction, nextState);
		if (!nextState) {
			nextState = this._currentState;
		}

		if (!this._actionFunctions[nextState]) {
			nextState = this._undefinedState(someAction, nextState);
		}

		this._currentState = nextState;
	} catch (e) {
		Mojo.Log.logException(e, '#_handleAction()');
	}
};

PageAssistant.prototype._undefinedAction = function(details) {

	PA.Log.warn("PageAssistant handled unexpected action");

	this._pageControls.showIdle();

	return this._initialState;
};

PageAssistant.prototype._undefinedState = function(someAction, state) {

	PA.Log.error("PageAssistant handled unexpected state: %s, for action: %s", state, someAction);

	this._pageControls.showIdle();

	return this._initialState;
};

PageAssistant.prototype._actionFunctions = {

	Idle: {

		stop: function() {
			return this._currentState;
		},

		start: function() {
			this._pageControls.showSearch();
			return 'Progress';
		},

		useropen: function() {
			this._pageControls.showSearch();
			return 'Pending';
		},

		userreload: function() {
			this._pageControls.showSearch();
			return 'Pending';
		},

		userforward: function() {
			this._pageControls.showSearch();
			return 'Pending';
		},

		userback: function() {
			this._pageControls.showSearch();
			return 'Pending';
		},

		userstop: function() {
			return this._currentState;
		},

		progress: function() {
			return this._currentState;
		}
	},

	Pending: {

		stop: function() {
			return this._currentState;
		},

		start: function() {
			this._pageControls.showSearch();
			return 'Progress';
		},

		useropen: function() {
			return this._currentState;
		},

		userreload: function() {
			return this._currentState;
		},

		userforward: function() {
			return this._currentState;
		},

		userback: function() {
			return this._currentState;
		},

		userstop: function() {
			this._pageControls.showIdle();
			return 'Idle';
		},

		progress: function() {
			this._pageControls.showProgress();
			return 'Progress';
		}
	},

	Progress: {

		stop: function() {
			this._pageControls.showIdle();
			return 'Idle';
		},

		start: function() {
			return this._currentState;
		},

		useropen: function() {
			this._pageControls.showSearch();
			return 'Pending';
		},

		userreload: function() {
			this._pageControls.showSearch();
			return 'Pending';
		},

		userforward: function() {
			this._pageControls.showSearch();
			return 'Pending';
		},

		userback: function() {
			this._pageControls.showSearch();
			return 'Pending';
		},

		userstop: function() {
			this._pageControls.showIdle();
			return 'Idle';
		},

		progress: function(details) {
			if (details.progress > 10) {
				// On a start progress is immediately set to 10
				// even though we've not downloaded content yet
				// nor are we sure the server is live.
				this._pageControls.showProgress();
				this._pageControls.updateProgress(details.progress);
				return 'Progress10';
			}
			return this._currentState;
		}
	},

	Progress10: {

		stop: function() {
			this._pageControls.showIdle();
			return 'Idle';
		},

		start: function() {
			return this._currentState;
		},

		useropen: function() {
			this._pageControls.showSearch();
			return 'Pending';
		},

		userreload: function() {
			this._pageControls.showSearch();
			return 'Pending';
		},

		userforward: function() {
			this._pageControls.showSearch();
			return 'Pending';
		},

		userback: function() {
			this._pageControls.showSearch();
			return 'Pending';
		},

		userstop: function() {
			this._pageControls.showIdle();
			return 'Idle';
		},

		progress: function(details) {
			this._pageControls.updateProgress(details.progress);
			return this._currentState;
		}
	}
};

BrowserServerStatus = function(controller) {
	this._controller = controller;
	this._adapterConnected = true;
};

BrowserServerStatus.prototype.setup = function(params) {

	this._onConnect = params.onConnect || function() {};
	this._controller.setupWidget('server-disconnected-spinner', {spinnerSize:Mojo.Widget.spinnerLarge});
	this._disconnectedScrim = this._controller.get('server-disconnected');
	this._disconnectedScrim.hide();
	this._disconnectedSpinner = this._controller.get('server-disconnected-spinner');
};

BrowserServerStatus.prototype.showActivateState = function() {

	this._activated = true;

	if (!this._adapterConnected) {
		this._disconnectedSpinner.mojo.start();
		this._disconnectedScrim.show();
	}
};

BrowserServerStatus.prototype.showDeactivateState = function() {

	this._activated = false;
	if (!this._adapterConnected) {
		this._disconnectedSpinner.mojo.stop();
	}
};

BrowserServerStatus.prototype.connected = function() {

	if (!this._adapterConnected) {
		this._disconnectedSpinner.mojo.stop();
		this._disconnectedScrim.hide();
		this._onConnect();
	}
	this._adapterConnected = true;
};

BrowserServerStatus.prototype.disconnected = function() {

	if (this._activated && this._adapterConnected) {
		this._disconnectedSpinner.mojo.start();
	}
	this._disconnectedScrim.show();
	this._adapterConnected = false;
};
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

// The applications custom menu command  
MenuData = {};
MenuData.ApplicationMenu = {

	NewCard: {
		label: $L('New Card'),
		command: 'new-page-cmd'
	},
	
	SharePage: {
		label: $L('Share'),
		command: 'share-page-cmd',
		checkEnabled: true
	},
	
	AddToLauncher: {
		label: $L('Add to Launcher'),
		command: 'add-launch-icon-cmd',
		checkEnabled: true
	},
	
	AddBookmark: {
		label: $L('Add Bookmark'),
		command: 'add-bookmark-cmd',
		checkEnabled: true
	},
	
	ShowBookmarks: {
		label: $L('Bookmarks'),
		command: 'show-bookmarks-cmd',
		checkEnabled: true
	},
	
	ShowHistory: {
		label: $L('History'),
		command: 'show-history-cmd',
		checkEnabled: true
	}
};

/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

MenuData.NavigationMenu = {
	
	Back: {
		label: $L('Go back'),
		icon: 'back',
		command: 'back'
	},
	
	Forward: {
		label: $L('Go forward'),
		icon: 'forward',
		command: 'forward'
	},
	
	Stop: {
		label: $L('Stop'),
		icon: 'load-progress',
		command: 'stop'
	},
	
	Reload: {
		label: $L('Reload'),
		icon: 'refresh',
		command: 'refresh'
	},
		
	Search: {
		label: $L('Search'),
		icon: 'load-search',
		command: 'stop'
	}
};
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

PageProgressAnimation = Class.create({

	initialize: function(details) {
		
		Mojo.require(details.controller, "PageProgressAnimation requires a controller");
		Mojo.require(details.model, "PageProgressAnimation requires a model");

		this._controller = details.controller;
		this._model = details.model;
		this._iconClass = details.model.icon;
		this._iconSelector = '.' + details.model.icon;
		this._frameHeight = details.frameHeight || 48;

		this._currentFrameIndex = 0;
		this._currentPercentImage = 0;
	},
	
	_getElement: function() {

		var icon = this._controller.select(this._iconSelector);
		if (icon) {
			icon = icon[0];
		}

		return icon;
	},
	
	start: function(progress) {
		
		var element = this._getElement();		
		if (element) {
			// Get a handle onto the main animation queue.
			this._animationQueue = Mojo.Animation.queueForElement(element);
			this.setProgress(progress);
		}		
	},
	
	stop: function() {

		if (this._loadProgressAnimator) {
			this._loadProgressAnimator.cancel();
			delete this._loadProgressAnimator;
		}
	},
	
	/**
 	 * Set the page load progress state.
 	 *
 	 * @param {Object} percent 0..100.
	 */	
	setProgress: function(percent) {

		try {
			if (percent > 100) {
				percent = 100;
			}
			else if (percent < 0) {
				percent = 0;
			}
			
			// Convert the percentage complete to an image number
			// Image must be from 0 to 25 (26 images available)
			var image = Math.round(percent / 3.85);
			if (image > 25) {
				image = 25;
			}
			
			// Has the progress changed?
			if (this._animationQueue && (this._currentPercentImage < image)) {
				// Cancel the existing animator if there is one
				if (this._loadProgressAnimator) {
					this._loadProgressAnimator.cancel();
					delete this._loadProgressAnimator;
				}
				
				// Animate from the current value to the new value
				this._loadProgressAnimator = Mojo.Animation.animateValue(this._animationQueue, 
					"linear", 
					this._updateLoadProgress.bind(this), 
					{
						from: this._currentPercentImage,
						to: image,
						duration: 0.5
					});
			}
		} 
		catch (e) {
			Mojo.Log.logException(e, "#setProgress");
		}
	},

	/**
 	 * Update the load progress icon
 	 *
 	 * @param {Object} 0..23
 	 */
	_updateLoadProgress: function(image) {
		
		try {
			// Find the progress image
			image = Math.round(image);
			
			// Don't do anything if the progress is already displayed
			if (this._currentPercentImage == image) {
				return;
			}
			
			var element = this._getElement();
			if (element) {
				if (this._model._progress) {
					element.removeClassName(this._model._progress);					
				}
				var progress = 'progress-' + image;
				element.addClassName(progress);
				this._model.icon = this._iconClass + ' ' +progress;
				this._model._progress = progress;
			}			
			this._currentPercentImage = image;
			
		} catch (e) {
			Mojo.Log.logException(e, "#updateLoadProgress()");
		}
	}
});
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

PageSearchAnimation = Class.create({

	initialize: function(details) {
		
		Mojo.require(details.controller, "PageSearchAnimation requires a controller");
		Mojo.require(details.model, "PageSearchAnimation requires a model");
		this._controller = details.controller;
		this._model = details.model;
		this._iconClass = details.model.icon;
		this._iconSelector = '.' + details.model.icon;

		this._frameHeight = details.frameHeight || 48;
		this._frameCount = details.frameCount || 12;
		this._fps = details.fps || 12;
		
		this._drawInterval = Math.max(1, Mojo.Animation.targetFPS/this._fps);
		this._currentFrameIndex = 0;
		this._hasStarted = false;
	},
	
	_getElement: function() {
		
		var icon = this._controller.select(this._iconSelector);
		if (icon) {
			icon = icon[0];
		}
		return icon;
	},
	
	start: function() {

		// Add to the animation queue if the element exists.
		var element = this._getElement();
		if (element) {
			// Get a handle onto the main animation queue.
			this._animationQueue = Mojo.Animation.queueForElement(element);
		}

		// If the play period isn't defined the play forever.
		this._currentFrameIndex = 0;
		this._drawCount = 0;
		if (!this._hasStarted && this._animationQueue) {
			
			this._animationQueue.add(this);
			this._hasStarted = true;
		}
	},
	
	stop: function(){
		
		if (this._hasStarted && this._animationQueue) {
			this._animationQueue.remove(this);
		}
		this._hasStarted = false;				
	},
	
	animate: function() {
		
		if (this._drawCount < this._drawInterval) {
			this._drawCount++;
		} else {
			this._drawCount -= this._drawInterval;
			this._currentFrameIndex++;
			this._currentFrameIndex = this._currentFrameIndex % this._frameCount; 			
			this._setFrame(this._currentFrameIndex);
		}
	},

	_setFrame: function(index){

		var element = this._getElement();
		if (element) {
			if (this._model._progress) {
				element.removeClassName(this._model._progress);
			}
			var progress = 'progress-' + index;
			element.addClassName(progress);
			this._model.icon = this._iconClass + ' ' + progress;
			this._model._progress = progress;
		}			
	}
});
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

PageControls = Class.create({

	initialize: function(controller) {

		this._controller = controller;

		// We can always go back.
		this._back = true;
		this._forward = false;

		this._backModel = {
			disabled: !this._back,
			label: MenuData.NavigationMenu.Back.label,
			icon: MenuData.NavigationMenu.Back.icon,
			command: MenuData.NavigationMenu.Back.command
		};

		this._forwardModel = {
			disabled: !this._forward,
			label: MenuData.NavigationMenu.Forward.label,
			icon: MenuData.NavigationMenu.Forward.icon,
			command: MenuData.NavigationMenu.Forward.command
		};

		this._model = {
			visible: true,
			items: [this._backModel, {}, {}]
		};

		this._currentPageModel = {};
	},

	setup: function(withIdle) {

		if (withIdle) {

			var idleModel = {
				type: 'idle',
				label: MenuData.NavigationMenu.Reload.label,
				icon: MenuData.NavigationMenu.Reload.icon,
				command: MenuData.NavigationMenu.Reload.command
			};

			this._model.items.push(idleModel);
		}

		this._controller.setupWidget(Mojo.Menu.commandMenu, {menuClass: 'no-fade'}, this._model);
	},

	cleanup: function() {

		if (this._animation) {
			this._animation.stop();
			delete this._animation;
		}
	},

	_setBackForward: function(back, forward) {

		var items = $A();
		this._backModel.disabled = !back;
		items.push(this._backModel);

		if (forward) {
			this._forwardModel.disabled = !forward;
			items.push(this._forwardModel);
		} else {
			items.push({});
		}

		// The spacer... right aligns the following button.
		items.push({});

		this._back = back;
		this._forward = forward;

		return items;
	},

	updateBackForward: function(back, forward) {

		// Add the back/forward buttons
		this._model.items = this._setBackForward(back, forward);
		// The spacer... right aligns the following button.
		this._model.items.push(this._currentPageModel);

		this._controller.modelChanged(this._model);
	},

	showIdle: function() {

		//Mojo.Log.info("PageControls#showIdle()");
		var idleModel = {
			type: 'idle',
			label: MenuData.NavigationMenu.Reload.label,
			icon: MenuData.NavigationMenu.Reload.icon,
			command: MenuData.NavigationMenu.Reload.command
		};

		if (this._animation) {
			this._animation.stop();
			delete this._animation;
		}

		// Add the back/forward buttons
		this._model.items = this._setBackForward(this._back, this._forward);
		// The spacer... right aligns the following button.
		this._model.items.push(idleModel);
		this._controller.modelChanged(this._model);

		this._currentPageModel = idleModel;
	},

	showProgress: function() {

		try {

			//Mojo.Log.info("PageControls#showProgress()");
			var progressModel = {
				type: 'progress',
				label: MenuData.NavigationMenu.Stop.label,
				icon: MenuData.NavigationMenu.Stop.icon,
				command: MenuData.NavigationMenu.Stop.command
			};

			if (this._animation) {
				this._animation.stop();
			}

			this._animation = new PageProgressAnimation({
				controller: this._controller,
				model: progressModel
			});

			// Add the back/forward buttons
			this._model.items = this._setBackForward(this._back, this._forward);
			this._model.items.push(progressModel);
			this._controller.modelChanged(this._model);
			this._animation.start(0);

			this._currentPageModel = progressModel;

		} catch (e) {
			Mojo.Log.logException(e, "#showProgress()");
		}
	},

	updateProgress: function(progress) {

		try {
			if (this._currentPageModel.type === 'progress') {
				this._animation.setProgress(progress);
			}
		} catch (e) {
			Mojo.Log.logExcepton(e, '#updateProgress()');
		}
	},

	showSearch: function() {

		try {

			//Mojo.Log.info("PageControls#showSearch()");
			var searchModel = {
				type: 'search',
				label: MenuData.NavigationMenu.Search.label,
				icon: MenuData.NavigationMenu.Search.icon,
				command: MenuData.NavigationMenu.Search.command
			};

			if (this._animation) {
				this._animation.stop();
			}

			this._animation = new PageSearchAnimation({
				controller: this._controller,
				model: searchModel
			});

			// Add the back/forward buttons
			this._model.items = this._setBackForward(this._back, this._forward);
			this._model.items.push(searchModel);
			this._controller.modelChanged(this._model);
			this._animation.start();

			this._currentPageModel = searchModel;

		} catch (e) {
			Mojo.Log.logException(e, '#showSearch()');
		}
	}
});
/**
 * @name preferences-assistant.js
 * @fileOverview The assistant for the Preferences scene. Controls application level
 * settings.
 *
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * Constructor.
 * @param {PageAssistant} pageAssistant
 */
function PreferencesAssistant(webPreferences) {
	this._webPreferences = webPreferences;
	this._historyStore = new HistoryStore({
		database: Mojo.Controller.appController.assistant.getDatabase()
	});		
}

PreferencesAssistant.prototype.setup = function() {
	
    try {
		this._onCardActivateHandler = this._onCardActivate.bind(this);
		this._onCardDeactivateHandler = this._onCardDeactivate.bind(this);

		this.setupPopupsWidget();
		this.setupAcceptsCookiesWidget();
		this.setupJavaScriptWidget();
        
		this.controller.listen('clear_history_button', Mojo.Event.tap, this.clearHistory.bindAsEventListener(this));
		this.controller.listen('clear_cache_button', Mojo.Event.tap, this.clearCache.bindAsEventListener(this));
		this.controller.listen('clear_cookies_button', Mojo.Event.tap, this.clearCookies.bindAsEventListener(this));
    } 
    catch (e) {
        Mojo.Log.logException(e, 'PreferencesAssistant#setup');
    }
};

PreferencesAssistant.prototype.cleanup = function() {
	// Store the current settings for next launch.
	this._webPreferences.save();	
};

PreferencesAssistant.prototype.activate = function() {
	
	this.controller.document.addEventListener(Mojo.Event.activate, this._onCardActivateHandler, false);
	this.controller.document.addEventListener(Mojo.Event.deactivate, this._onCardDeactivateHandler, false);

	// Reload the preferences
	this.controller.modelChanged(this._webPreferences);
};

PreferencesAssistant.prototype.deactivate = function() {
	
	// Cleanup focus handlers.
	this.controller.document.removeEventListener(Mojo.Event.activate, this._onCardActivateHandler, false);
	this.controller.document.removeEventListener(Mojo.Event.deactivate, this._onCardDeactivateHandler, false);
};

PreferencesAssistant.prototype._onCardActivate = function(){

	// Reload the preferences
	this.controller.modelChanged(this._webPreferences);
};

PreferencesAssistant.prototype._onCardDeactivate = function(){
};

PreferencesAssistant.prototype.clearHistory = function(event) {
	this.controller.showAlertDialog({
		onChoose: function(value) {
			if (value === 'ok') {
				this._webPreferences.ClearHistory = true;
				
				// We also now clear the local history list.
				this._historyStore.deleteHistoryBefore(new Date(),
					function(){},
					function(msg, e) {
						Mojo.Log.error("History delete failed: %s", msg);
					});
			}
		}.bind(this),
		title:$L('Clear history'),
		message:$L('Clearing your history will permanently delete your browsing history. This operation is irreversible!'),
		cancelable:true,
		choices:[
			{label:$L('Clear history'), value:'ok'},
			{label:$L('Cancel'), value:'cancel'}
		]
	});
};

PreferencesAssistant.prototype.clearCache = function(event) {
	this.controller.showAlertDialog({
		onChoose: function(value) {
			try {
				if (value === 'ok') {
					this._webPreferences.ClearCache = true;
				}
			}
			catch (e) {
				Mojo.Log.logException(e, "clearing cache");
			}
		}.bind(this),
		title:$L('Clear cache'),
		message:$L('Clear your browser cache?'),
		cancelable:true,
		choices:[
		{label:$L('Clear cache'), value:'ok', type:'color'},
		{label:$L('Cancel'), value:'cancel'}
		    ]
	});
};

PreferencesAssistant.prototype.clearCookies = function(event) {
	this.controller.showAlertDialog({
		onChoose: function(value) {
			try {
				if (value === 'ok') {
					this._webPreferences.ClearCookies = true;
				}
			}
			catch (e) {
				Mojo.Log.logException(e, "clearing cookies");
			}
		}.bind(this),
		title:$L('Clear cookies'),
		message:$L('Clear your browser cookies?'),
		cancelable:true,
		choices:[
		{label:$L('Clear cookies'), value:'ok', type:'color'},
		{label:$L('Cancel'), value:'cancel'}
		    ]
	});
};

PreferencesAssistant.prototype.setupAcceptsCookiesWidget = function(event) {

	// This is supposed to be a tri-state setting, but WebKit only supports true/false
	// at present.
	var attributesModel = [
		{label:$L('Yes'), value:true},
		{label:$L('No'), value:false} ];

	var attributes = {
		modelProperty: "AcceptCookies",
		trueValue: true,
		trueLabel: $L('Yes'),
		falseValue: false,
		falseLabel: $L('No')
	};
	
	this.controller.setupWidget('acceptCookies', attributes, this._webPreferences);
};

PreferencesAssistant.prototype.setupPopupsWidget = function(event) {

	var attributes = {
		modelProperty: "BlockPopups",
		trueValue: true,
		trueLabel: $L('Yes'),
		falseValue: false,
		falseLabel: $L('No')
	};
	
	this.controller.setupWidget('blockPopups', attributes, this._webPreferences);
};

PreferencesAssistant.prototype.setupJavaScriptWidget = function(event) {

	var attributes = {
		modelProperty: "EnableJavaScript",
		trueValue: true,
		trueLabel: $L('On'),
		falseValue: false,
		falseLabel: $L('Off')
	};
	
	this.controller.setupWidget('javascriptEnabled', attributes, this._webPreferences);
};

/**
 * handle a menu command.
 */
PreferencesAssistant.prototype.handleCommand = function(event) {
	
	try {
		switch (event.type) {
		
			case Mojo.Event.command:
				
				Mojo.Log.info("Got command: %s", event.command);
				switch (event.command) {
										
					case Mojo.Menu.helpCmd:
						this._showHelpCommand();
						break;
				}
				break;
		
			case Mojo.Event.commandEnable:
		
				// Standard Application Menu commands.
				if (event.command === Mojo.Menu.helpCmd) {
				
					event.stopPropagation(); // Enable the chosen menuitems
					
				} 				
				break;
		}
	} 
	catch (e) {
		Mojo.Log.logException(e, 'handleCommand');
	}
};

PreferencesAssistant.prototype._showHelpCommand = function() {
	
	// Launch the help system.
	AppAssistant.launchHelp();
};
/**
 * Controls the behaviour of the search drop-down list when typing into the
 * URL address field.
 *
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * Constructor.
 * @param {PageAssistant} pageAssistant
 */
function UrlSearchController(controller){

	try {
		this.urlSearchListModel = $A();
		this.urlToBookmark = $H();
		this.controller = controller;
		this._urlSeenRecord = $H();
		this._maxSearchResults = 32;		
		this.searchListVisible = true;

		// Out sources of data for the list.
		this.historyStore = new HistoryStore({
			database: Mojo.Controller.appController.assistant.getDatabase()
		});
		this.bookmarkStore = new BookmarkStore({
			database: Mojo.Controller.appController.assistant.getDatabase()
		});

		this.controller.setupWidget('urlSearchScroller', {
			mode: 'vertical'
		});
		
		this._urlSearchScrollerWidget = this.controller.get('urlSearchScroller'); 

		this.controller.setupWidget('urlSearchList', {
			itemTemplate: 'urlsearch/urlsearch-entry',
			listTemplate: 'urlsearch/urlsearch-container',
			itemsCallback: this._getItemsCallback.bind(this),
			renderLimit: 16,
			lookahead: 8
		});

		this._urlSearchListWidget = this.controller.get('urlSearchList');
		this.controller.listen('urlSearchList', Mojo.Event.tap, this._handleSelection.bindAsEventListener(this));
		this._urlSearch = this.controller.get('urlSearch');

		this.matchTemplate = Mojo.View.render({
			object: {
				match: 'ZZZZ'
			},
			template: '../views/urlsearch/matched'
		});
	} catch (e) {
		Mojo.Log.logException(e, "UrlSearchController()");
	}
}

// Our search providers
UrlSearchController.SearchProviders = {
	google: {
		searchTitle: $L('Search Google'),
		searchTemplate: $L('Google "#{search}"'),
		urlTemplate: $L('http://www.google.com/m/search?client=ms-palm-webOS&channel=bm&q=#{query}')
	},
				
	wikipedia: {
		searchTitle: $L('Search Wikipedia'),
		searchTemplate: $L('Wikipedia "#{search}"'),
		urlTemplate: $L('http://en.m.wikipedia.org/wiki/Special:Search?search=#{query}')
	} 	
};

UrlSearchController.prototype.setup = function(params) {

	this._onEnableSceneScroller = params.onEnableSceneScroller || function(){};
	this._onSelect = params.onSelect || function(){};
};

UrlSearchController.prototype.cleanup = function() {

};

/**
 * Show the search list of not already visible.
 */
UrlSearchController.prototype.showSearchList = function() {

	if (this.searchListVisible) {
		return;
	}

	this.searchListVisible = true;
	this._onEnableSceneScroller(false);
};

/**
 * Hide the search list. Can be called multiple times.
 */
UrlSearchController.prototype.hideSearchList = function() {

	if (!this.searchListVisible) {
		return;
	}

	this._urlSearch.hide();
	this.searchListVisible = false;
	this._onEnableSceneScroller(true);
};

UrlSearchController.prototype.isSearchListVisible = function() {

	return this._urlSearch.style.display !== 'none';
};

/**
 * Substitute matching text with the templatetized version of that
 * matched text.
 *
 * @param {String} text Original text to match against. Cannot be HTML markup.
 * @param {String} ucMatchText The text to match (all uppercase)
 * @param {Object} matchTemplate
 *
 * @return {String} The input "text" string transformed if any portions of it match ucMatchText.
 *                  The return string is HTML markup and no longer plain text.
 */
UrlSearchController.prototype._subMatchString = function(text, ucMatchText, matchTemplate) {
	var idx = text.toLocaleUpperCase().indexOf(ucMatchText);
	if (idx != -1) {
		var matchedText = text.substr(idx, ucMatchText.length);
		var matchHLText = matchTemplate.replace('ZZZZ', matchedText);
		return text.replace(matchedText, matchHLText);
	}
	else {
		return text;
	}
};

/**
 * Add all matching items to the search list model.
 *
 * @param {Array} items        The list of UrlReference instances (Bookmark or History) to match against
 * @param {String} iconclass   The icon class to use for matching items.
 */
UrlSearchController.prototype._addToSearchListModel = function(items, iconclass, rowclass) {

	var ucMatchText = this.filterText.toLocaleUpperCase();
	var title, subTitle;
	var seenKey;

	for (var i = 0; i < items.length; i++) {
		var item = items[i];
		if (item instanceof UrlReference) {

			var alreadyInList = false;
			if (item.title) {
				title = item.title;
				subTitle = UrlUtil.cleanup(item.url);
				subTitle = this._subMatchString(subTitle.escapeHTML(), ucMatchText, this.matchTemplate);
				
				seenKey = subTitle;
			} else {
				title = UrlUtil.cleanup(item.url);
				subTitle = '&nbsp;';
				
				seenKey = title;
			}

			title = this._subMatchString(title.escapeHTML(), ucMatchText, this.matchTemplate);
			if (!this._urlSeenRecord.get(seenKey)) {
				this._urlSeenRecord.set(seenKey, 1);

				var modelItem = Mojo.Model.decorate(item);
				modelItem.title = title;
				modelItem.subTitle = subTitle;

				modelItem.iconclass = iconclass;
				modelItem.rowclass = rowclass;
				if (modelItem.iconFile32) {
					modelItem.style = 'background-image: url(' + modelItem.iconFile32 + ')';
				}
				this.urlSearchListModel.push(modelItem);
			}
		}
	}
};

/**
 * Add the stock searches to the list.
 * @param {String} searchText
 */
UrlSearchController.prototype._addStockSearchItems = function(searchText, withDivider) {

	var escapedSearchText = searchText.escapeHTML();
	var encodedUriComponent = encodeURIComponent(searchText);

	// Wikipedia block...
	var url   = UrlSearchController.SearchProviders.wikipedia.urlTemplate.interpolate({query: encodedUriComponent});
	var title = UrlSearchController.SearchProviders.wikipedia.searchTemplate.interpolate({search: escapedSearchText});
	
	var item = new UrlReference(url, title);
	item.subTitle = UrlSearchController.SearchProviders.wikipedia.searchTitle;
	item.iconclass = 'favicon wikipedia';
	if (withDivider) {
		item.rowclass = 'search last';
	} else {
		item.rowclass = 'search last only';
	}
	this.urlSearchListModel.unshift(item);

	// Google block....
	url   = UrlSearchController.SearchProviders.google.urlTemplate.interpolate({query: encodedUriComponent});
	title = UrlSearchController.SearchProviders.google.searchTemplate.interpolate({search: escapedSearchText});

	item = new UrlReference(url, title);
	item.subTitle = UrlSearchController.SearchProviders.google.searchTitle;
	item.iconclass = 'favicon google';
	item.rowclass = 'search first';
	
	this.urlSearchListModel.unshift(item);

};

UrlSearchController.prototype._clearList = function() {

	this._urlSeenRecord = $H();
	this.urlToBookmark = $H();
	this.urlSearchListModel = $A();
};

/**
 * Start filling the search list (which is an asynchronous operation).
 *
 * @param {String} filterText
 */
UrlSearchController.prototype.startFillingList = function(filterText) {

	this.filterText = null;
	if (filterText && filterText.length > 0) {
		this.filterText = filterText;
	}
	
	// Clear any old search list entries and add the
	// search options if the filter looks to be a URL.
	this._clearList();
	
	// Start with bookmarks first because they take higher precedence over history
	this.bookmarkStore.readBookmarks(new BookmarkFolder(), 
		this.filterText, 
		this._onBookmarksReadSuccess.bind(this), 
		this._onBookmarksReadFailure.bind(this), 
		0, 
		this._maxSearchResults);
};

/**
 * Called
 * @param {BookmarkFolder} bookmarksFolder
 */
UrlSearchController.prototype._onBookmarksReadSuccess = function(bookmarksFolder) {

	bookmarksFolder.contents.each(function(item) {
		if (item instanceof UrlReference) {
			this.urlToBookmark.set(item.url, item);
		}
	}.bind(this));

	this._addToSearchListModel(bookmarksFolder.contents, 'urlsearch-icon', 'urlsearch-bookmark');
	this._startFillingHistory();
};

UrlSearchController.prototype._onBookmarksReadFailure = function() {

	Mojo.Log.error("Error reading bookmarks.");
	this._startFillingHistory();
};

UrlSearchController.prototype._startFillingHistory = function() {

	var count = this._maxSearchResults - this.urlSearchListModel.length;

	// Only attempt to load some history if we have not
	// met the URL search quota
	if (count > 0) {
		this.historyStore.getHistory(new HistoryList(), 
			this.filterText, 
			this._onHistoryReadSuccess.bind(this), 
			this._onHistoryReadFailure.bind(this), 
			0, 
			count);
	} else {
		this._fillListComplete();
	}
};

UrlSearchController.prototype._onHistoryReadSuccess = function(historyList, offset, limit) {

	for (var d = 0; d < historyList.days.length; d++) {
		var day = historyList.days[d];
		this._addToSearchListModel(day.items, 'favicon history', 'urlsearch-history');
	}

	this._fillListComplete();
};

UrlSearchController.prototype._onHistoryReadFailure = function() {

	Mojo.Log.error("Failed to read history");
	this._fillListComplete();
};

/**
 * Called when the list filling operation is finished (good or bad).
 */
UrlSearchController.prototype._fillListComplete = function() {

	var listWidget = this.controller.get('urlSearchList');

	if (!UrlUtil.isWellFormedUrl(this.filterText)) {
		if (this.urlSearchListModel.length > 0) {
			this._addStockSearchItems(this.filterText, true);
		} else {
			this._addStockSearchItems(this.filterText, false);
		}
	} else {
		if (this.urlSearchListModel.length > 0) {
			firstItem = this.urlSearchListModel[0];
			firstItem.rowclass = 'url first';
			if (this.urlSearchListModel.length == 1) {
				firstItem.rowclass = 'url last';
			}
		}
	}

	// Update the list with the new model and set the new length.
	// Don't show the DIV if we have no entries.
	// On an update move the scroller to 0,0 (fixed style issue?)
	this._urlSearchScrollerWidget.mojo.setScrollPosition({x: 0, y:0});
	listWidget.mojo.setLengthAndInvalidate(this.urlSearchListModel.length);
	listWidget.mojo.revealItem(0, false);
	if ((this.urlSearchListModel.length > 0) && this.searchListVisible) {
		this._urlSearch.show();
	}else {
		// Hide the list.
		this._urlSearch.hide();
	}
};

/**
 * Called when a list selection is made.
 * @param {Object} event
 */
UrlSearchController.prototype._handleSelection = function(event) {
	var targetRow = this.controller.get(event.target);
	while (targetRow !== undefined && !targetRow.hasAttribute('x-app-url')) {
		targetRow = targetRow.up();
	}
	if (targetRow === undefined) {
		Mojo.Log.warn("Can't find row attribute");
	}
	else {
	    var url = targetRow.readAttribute('x-app-url');
		var bookmark = this.urlToBookmark.get(url);	// Will be undefined if item not a bookmark
		if (bookmark !== undefined) {
			this._onSelect(bookmark);
		}
		else {
			this._onSelect(url);
		}
	}
};

UrlSearchController.formatDefaultSearchUrl = function(query) {
	
	return UrlSearchController.SearchProviders.google.urlTemplate.interpolate({query: encodeURIComponent(query)});
};

/**
 * Get the URL to use for the given URL for the default action (like hitting return key).
 */
UrlSearchController.getDefaultUrl = function(url) {
	if (UrlUtil.appearsToBeUrl(url)) {
		return url;
	}
	else {
		return UrlSearchController.formatDefaultSearchUrl(url);
	}
};

UrlSearchController.prototype._getItemsCallback = function(listWidget, offset, count) {

	// Use the model to update the list but lazily.
	var model = this.urlSearchListModel.slice(offset, offset+count);
	listWidget.mojo.noticeUpdatedItems(offset, model);
};
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * The assistant for the browser page scene.
 */
function CustomizeiconAssistant( params ) {
	this.urlReference = params.urlReference;
}

/**
 * Setup this scene.
 */
CustomizeiconAssistant.prototype.setup = function() {

    try {
        this._setupWebView();
    } 
    catch (e) {
        Mojo.Log.logException(e, 'CustomizeiconAssistant#setup');
    }
};
	
CustomizeiconAssistant.prototype.cleanup = function() {
};


CustomizeiconAssistant.prototype.activate = function() {
    try {
        var webView = this.controller.get('icon_web_view');
		this.controller.get('saveIconButton').addEventListener(Mojo.Event.tap, this.handleSaveIcon.bindAsEventListener(this));
	  } 
    catch (e) {
        Mojo.Log.logException(e, 'activate');
    }
	this.d = this.controller.document.querySelector('div[x-mojo-element=_Dialog]');
	this.d.hide();

};

CustomizeiconAssistant.prototype.deactivate = function() {
	if (this.d) {
		this.d.show();
		if (this.urlReference.tmpIconFile64) {
			var img = Mojo.View.render({object: {icon: this.urlReference.tmpIconFile64}, template: "bookmarks/imageicondiventry"});
			this.controller.get('imageicondiv').update(img);
		}
	}
};

/**
 * Setup the web view.
 */
CustomizeiconAssistant.prototype._setupWebView = function() {
	var params = { cacheAdapter: false };	// Caching uses too much memory.
	params.url = this.urlReference.url;
	this.controller.setupWidget('icon_web_view', params);
};

CustomizeiconAssistant.prototype.handleSaveIcon = function(){

	try {
		var webView = this.controller.get('icon_web_view');
		
		var now = Date.now(); // To create unique filename.
		webView.mojo.saveViewToFile(CustomizeiconAssistant.tmpCaptureFile);
		if (this.urlReference.tmpIconFile64 !== this.urlReference.iconFile64) {
			webView.mojo.deleteImage(this.urlReference.tmpIconFile64); // Delete old temp 64x64 pixel image
		}
	
		var frame =  this.controller.get('frame');
		var frameDim = frame.getDimensions();

		var frameLeft = parseInt(frame.getStyle('left'), 10);
		var frameTop = parseInt(frame.getStyle('top'), 10);

		this.urlReference.tmpIconFile64 = CustomizeiconAssistant.createBrowserImageName('icon64', now);
		webView.mojo.generateIconFromFile(CustomizeiconAssistant.tmpCaptureFile, this.urlReference.tmpIconFile64, 
			frameLeft, frameTop, frameLeft + frameDim.width, frameTop + frameDim.height);
	
		if (this.urlReference.tmpIconFile32 !== this.urlReference.iconFile32) {
			webView.mojo.deleteImage(this.urlReference.tmpIconFile32); // Delete old temp 32x32 pixel image
		}

		var scenePos = this.controller.getSceneScroller().mojo.getScrollPosition();
		frameLeft = -scenePos.left;
		frameTop = -scenePos.top;
		webView.mojo.saveViewToFile(CustomizeiconAssistant.tmpCaptureFile,
				frameLeft, frameTop, frameLeft + frameDim.width, frameTop + frameDim.height);
		
		this.urlReference.tmpIconFile32 = CustomizeiconAssistant.createBrowserImageName('icon32', now);
		webView.mojo.resizeImage(CustomizeiconAssistant.tmpCaptureFile, this.urlReference.tmpIconFile32, 32, 32);
		this.controller.stageController.popScene({
			type: 'customizeicon'
		});
	} 
	catch (e) {
		var that = this;
		Mojo.Log.logException(e);
		this.controller.showAlertDialog({
			onChoose: function(value){
				that.controller.stageController.popScene({
					type: 'customizeicon'
				});
			},
			title: $L("Error"),
			message: $L("Failed to save icon: ") + e.message,
			choices: [{
				label: $L('Done'),
				value: 'dismiss',
				type: 'alert'
			}]
		});
	}
};

/**
 * Create the full filename for a browser image.
 * 
 * @param {String} basename	A basename for the file. Usually 'icon' or 'thumbnail'
 * @param {String} unique A unique filename differntiator like a random number, etc.
 */
CustomizeiconAssistant.createBrowserImageName = function(basename, unique) {
	// If you change this directory then make sure to change the 
	// desktop-binaries installation for the simulator.
	return '/var/luna/data/browser/icons/' + basename + '-' + unique + '.png';
};

/**
 * handle a menu command.
 */
CustomizeiconAssistant.prototype.handleCommand = function(event) {
	
	try {
		switch (event.type) {
		
			case Mojo.Event.command:
				
				Mojo.Log.info("Got command: %s", event.command);
				switch (event.command) {
										
					case Mojo.Menu.helpCmd:
						this._showHelpCommand();
						break;
				}
				break;
		
			case Mojo.Event.commandEnable:
		
				// Standard Application Menu commands.
				if (event.command == Mojo.Menu.helpCmd) {
				
					event.stopPropagation(); // Enable the chosen menuitems
					
				} 				
				break;
		}
	} 
	catch (e) {
		Mojo.Log.logException(e, 'handleCommand');
	}
};

CustomizeiconAssistant.prototype._showHelpCommand = function() {
	
	// Launch the help system.
	AppAssistant.launchHelp();
};

CustomizeiconAssistant.tmpCaptureFile = '/tmp/BrowserTempCapture.png';
CustomizeiconAssistant.kIconCropRectWidth  = 128;	// Must match GUI
CustomizeiconAssistant.kIconCropRectHeight = 128;	// Must match GUI

/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * A network dialog assistant for display of current network status.
 */
NetworkDialogAssistant = Class.create({
	
	initialize: function(params) {
		this.onDismiss = params.onDismiss;
		this.controller= params.sceneAssistant.controller;
		
		// Button handlers.
		this.onDismissHandler = this.handleDismiss.bindAsEventListener(this);
	},
	
	setup: function(widget) {
		this.widget = widget;
		this.controller.get('dismissButton').addEventListener(Mojo.Event.tap, this.onDismissHandler);
		this.controller.get('dismissButton').focus();
	},
	
	handleDismiss: function() {
		this.onDismiss();
		delete this.onDismiss;
		this.widget.mojo.close();
	},
	
	cleanup: function() {
		Mojo.Log.info("NetworkDialogAssistant#cleanup()");
		Mojo.Event.stopListening(this.controller.get('dismissButton'), Mojo.Event.tap, this.onDismissHandler);
		
		// Send a dismiss if NOT already sent a response
		if (this.onDismiss) {
			this.onDismiss();
		}	
	}
});
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

function UrlBar(controller) {
	this.controller = controller;

	// Any updates to the URL input field.
	this._onPropertyChangeHandler = this._onPropertyChange.bind(this);
	this._onInputFocusHandler = this._onInputFocus.bind(this);
	this._onInputBlurHandler = this._onInputBlur.bind(this);
	this._onPropertyChange = function(){};
	this._urlBarFocused = false;
	this._enableSSLLock = false;

	// Default orientation is portrait
	this._configOrientation('up');
}

UrlBar.defaultHintText = $L('Enter URL or search terms');

UrlBar.Menu = {

	Go: {
		template: 'page/url-go',
		command: 'urlbar-go-pressed'
	},

	TitleTap: {
		template: 'page/page-title',
		command: 'urlbar-title-tap',
		title: UrlBar.defaultHintText,
		sslDisplay: 'none'
	}
};

UrlBar.prototype.validGotoUrlBarChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$-_.+!*'(),{}|\\^~[]`<>#%\";/?:@&=";

UrlBar.prototype.isAGotoUrlBarKey = function(keycode) {
	//Mojo.Log.info("keyCode: ", keycode);
	//Mojo.Log.info("CHAR: ", String.fromCharCode(keycode));
	return (this.validGotoUrlBarChars.indexOf(String.fromCharCode(keycode)) < 0) ? false : true;
};

UrlBar.prototype.setOrientation = function(orientation) {

	if (orientation !== 'up') {
		// In landscape and down we ALWAYS hide the BAR so we make sure it's
		// already hidden by calling' 'hide'.
		this._hidePortrait();
	}

	this._configOrientation(orientation);
};

UrlBar.prototype._configOrientation = function(orientation) {

	if (orientation !== 'up') {
		this.show = this._showNoOp;
		this.hide = this._hideNoOp;
	} else {
		// Default is portrait.
		this.show = this._showPortrait;
		this.hide = this._hidePortrait;
	}

	this._orientation = orientation;
};

UrlBar.prototype.setup = function(properties) {

	var visible = properties.visible || false;
	this._onPropertyChange = properties.onPropertyChange || function(){};
	this._configOrientation(properties.orientation || 'up');

	this.models = {
		urlInput: {
			template: 'page/url-field',
			attr: {
				hintText: UrlBar.defaultHintText,
				modelProperty: 'url',
				modifierState: 'none',
				focusMode: Mojo.Widget.focusInsertMode,
				requiresEnterKey: true,
				textFieldName: "urlbar-textfield-input",
				changeOnKeyPress: true,
				textReplacement: false
			},
			model: {
				url: ''
			},

			width: 271
		},

		// Beware: These are shallow copies.
		title: Object.clone(UrlBar.Menu.TitleTap),
		urlGo: Object.clone(UrlBar.Menu.Go)
	};

	this.controller.setupWidget('urlbar-input',
		this.models.urlInput.attr,
		this.models.urlInput.model);

	this.currentModel = {
		mode: 'url',
		model: {
			visible: visible,
			items:[{items: [this.models.urlInput, this.models.urlGo]}, {}]
		}
	};

	this.controller.setupWidget(Mojo.Menu.viewMenu,
		{menuClass:'no-fade'},
		this.currentModel.model);
};

UrlBar.prototype.cleanup = function() {

};

UrlBar.prototype.setTitle = function(title) {
	
	title = title || UrlBar.defaultHintText;
	this.models.title.title = title;
};

UrlBar.prototype.setUrl = function(url) {
	
	url = url || '';
	this.models.urlInput.model.url = url;
};

UrlBar.prototype.enableTitleView = function() {
	
	// During a model change remove any BLUR observers.
	this._stopObservingFocusBlurs();

	// Model changes cause a blur.
	this._urlBarFocused = false;

	// Force a model change ?
	var menuAssistant = this.controller._menu.assistant;
	var spacerHeight = menuAssistant.viewSpacerHeight;

	this.currentModel.mode = 'title';
	this.currentModel.model.visible = true;
	this.currentModel.model.items = [this.models.title];

	menuAssistant.viewSpacerHeight = 0;
	this.models.title.sslDisplay = this._enableSSLLock ? 'block' : 'none';
	this.controller.modelChanged(this.currentModel.model);

	// Add the observers
	this.startObserving();	
};

UrlBar.prototype.enableUrlView = function() {
	
	// During a model change remove any BLUR observers.
	this._stopObservingFocusBlurs();

	// Model changes cause a blur
	this._urlBarFocused = false;

	// Force a model change ?
	var menuAssistant = this.controller._menu.assistant;
	var spacerHeight = menuAssistant.viewSpacerHeight;

	this.currentModel.mode = 'url';
	this.currentModel.model.visible = true;
	this.currentModel.model.items = [{items: [this.models.urlInput, this.models.urlGo]}, {}];

	menuAssistant.viewSpacerHeight = 0;
	this.controller.modelChanged(this.currentModel.model);
	this.controller.modelChanged(this.models.urlInput.model);

	// If we have a URL enable the Go button.
	var url = this.models.urlInput.model.url;
	this._enableGoButton((url && (url.length > 0)));

	// Add the observers
	this.startObserving();
};

UrlBar.prototype.changeModeToTitle = function(newTitle, visible){

	var title = newTitle || UrlBar.defaultHintText;

	// During a model change remove any BLUR observers.
	this._stopObservingFocusBlurs();

	// Model changes cause a blur.
	this._urlBarFocused = false;

	// Force a model change ?
	var menuAssistant = this.controller._menu.assistant;
	var spacerHeight = menuAssistant.viewSpacerHeight;

	this.currentModel.mode = 'title';
	this.currentModel.model.visible = visible;
	this.currentModel.model.items = [this.models.title];

	menuAssistant.viewSpacerHeight = 0;
	this.models.title.title = title;
	this.models.title.sslDisplay = this._enableSSLLock ? 'block' : 'none';
	this.controller.modelChanged(this.currentModel.model);

	// Add the observers
	this.startObserving();
};

UrlBar.prototype.changeModeToUrl = function(newUrl, visible) {

	// During a model change remove any BLUR observers.
	this._stopObservingFocusBlurs();

	// Model changes cause a blur
	this._urlBarFocused = false;

	// Force a model change ?
	var menuAssistant = this.controller._menu.assistant;
	var spacerHeight = menuAssistant.viewSpacerHeight;

	this.currentModel.mode = 'url';
	this.currentModel.model.visible = visible;
	this.currentModel.model.items = [{items: [this.models.urlInput, this.models.urlGo]}, {}];

	menuAssistant.viewSpacerHeight = 0;
	this.controller.modelChanged(this.currentModel.model);
	this.models.urlInput.model.url = newUrl || '';

	this.controller.modelChanged(this.models.urlInput.model);

	// If we have a URL enable the Go button.
	this._enableGoButton((newUrl && (newUrl.length > 0)));

	// Add the observers
	this.startObserving();
};

UrlBar.prototype.enableSSLLock = function(enable) {

	this._enableSSLLock = enable;
	this._updateSSLLock();
};

UrlBar.prototype._updateSSLLock = function() {

	this.models.title.sslDisplay = this._enableSSLLock ? 'block' : 'none';
	if (this.currentModel.mode !== 'url') {
		this.controller.modelChanged(this.currentModel.model);
	}
};

UrlBar.prototype.startObserving = function() {

	if (this.currentModel.mode === 'url') {
		this._addAllInputObservers();
	} else {
		this._addAllTitleObservers();
	}
};

UrlBar.prototype.stopObserving = function() {

	if (this.currentModel.mode === 'url') {
		this._removeAllInputObservers();
	} else {
		this._removeAllTitleObservers();
	}
};

UrlBar.prototype._stopObservingFocusBlurs = function() {

	if (this.currentModel.mode === 'url') {
		this._removeInputFocusBlurObservers();
	} else {
		this._removeTitleFocusBlurObservers();
	}
};

UrlBar.prototype._addAllTitleObservers = function() {

	var title = this.controller.get('urlbar-title');
	if (title) {
		title.observe(Mojo.Event.tap, this._onTitleTapHandler);
	}

};

UrlBar.prototype._removeAllTitleObservers = function() {

	var title = this.controller.get('urlbar-title');
	if (title) {
		title.stopObserving(Mojo.Event.tap, this._onTitleTapHandler);
	}
};

UrlBar.prototype._addAllInputObservers = function(){

	var element;
	var input = this.controller.get('urlbar-input');
	if (input) {
		input.observe(Mojo.Event.propertyChange, this._onPropertyChangeHandler);

		// Observe the textarea inside it.
		element = input.querySelector('[name="urlbar-textfield-input"]');
		if (element) {
			element.observe('focus', this._onInputFocusHandler);
			element.observe('blur', this._onInputBlurHandler);
		}
	}
};

UrlBar.prototype._removeAllInputObservers = function() {

	var element;
	var input = this.controller.get('urlbar-input');
	if (input) {
		input.stopObserving(Mojo.Event.propertyChange, this._onPropertyChangeHandler);
		element = input.querySelector('[name="urlbar-textfield-input"]');
		if (element) {
			element.stopObserving("focus", this._onInputFocusHandler);
			element.stopObserving("blur", this._onInputBlurHandler);
		}
	}
};

UrlBar.prototype._removeTitleFocusBlurObservers = function() {
	// Do nothing .
};

UrlBar.prototype._removeInputFocusBlurObservers = function() {

	var element;
	var input = this.controller.get('urlbar-input');
	if (input) {

		element = input.querySelector('[name="urlbar-textfield-input"]');
		if (element) {

			element.stopObserving("focus", this._onInputFocusHandler);
			element.stopObserving("blur", this._onInputBlurHandler);
		}
	}
};

UrlBar.prototype._activateGoButton = function(activate) {

	var value;
	var goButton = this.controller.select('div.urlbar-menu-go-button');
	if (goButton && goButton[0] && this.currentModel.mode === 'url') {
		if (activate) {
			goButton[0].addClassName('active');
			this._enableGoButton(!!this.getValue());

		} else {
			goButton[0].removeClassName('active');
			goButton = this.controller.select('div.urlbar-go-button');
			if (goButton && goButton[0]) {
				this._enableGoButton(!!this.getValue());
			}
		}
	}
};

UrlBar.prototype._enableGoButton = function(enabled) {

	var goButtons = this.controller.select('div.urlbar-go-button');
	if (goButtons && goButtons[0] && this.currentModel.mode === 'url') {
		if (enabled) {
			goButtons[0].removeClassName('disabled');
			goButtons[0].addClassName('enabled');
		} else {
			goButtons[0].removeClassName('enabled');
			goButtons[0].addClassName('disabled');
		}
	}
};

UrlBar.prototype._onPropertyChange = function(event) {

	try {
		// We use the property change handler to determine the state of
		// the Go buttons.
		this._enableGoButton(event.value.length > 0);

		// Now we call the listener
		this._onPropertyChange(event);
	}
	catch (e) {
		Mojo.Log.logException(e, 'UrlBar#_onPropertyChange');
	}
};

// Default orientation is portrait mode.
UrlBar.prototype.show = UrlBar.prototype._showPortrait;
UrlBar.prototype.hide = UrlBar.prototype._hidePortrait;

UrlBar.prototype._showPortrait = function() {

	var menuAssistant = this.controller._menu.assistant;
	var spacerHeight = menuAssistant.viewSpacerHeight;

	menuAssistant.viewSpacerHeight = 0;
	this.controller.setMenuVisible(Mojo.Menu.viewMenu, true);
};

UrlBar.prototype._hidePortrait = function() {

	var menuAssistant = this.controller._menu.assistant;
	var spacerHeight = menuAssistant.viewSpacerHeight;

	menuAssistant.viewSpacerHeight = 0;
	this.controller.setMenuVisible(Mojo.Menu.viewMenu, false);
};

UrlBar.prototype._showNoOp = function() {
	// DO NOTHING - We don't show anything in landscape.
};

UrlBar.prototype._hideNoOp = function() {
	// DO NOTHING - We should not be displayed in landscape.
};

UrlBar.prototype.isVisible = function() {
	return this.controller.getMenuVisible(Mojo.Menu.viewMenu);
};

UrlBar.prototype.focus = function() {

	switch (this.currentModel.mode) {

		case 'url':
			this.controller.get('urlbar-input').mojo.focus();
			break;

		case 'title':
			this.controller.get('urlbar-title').focus();
			break;
		default:
			Mojo.Log.error("Unknown model for FOCUS");
			break;
	}
};

UrlBar.prototype.blur = function() {

	switch (this.currentModel.mode) {

		case 'url':
			this.controller.get('urlbar-input').mojo.blur();
			break;

		case 'title':
			this.controller.get('urlbar-title').blur();
			break;
		default:
			Mojo.Log.error("Unknown model for BLUR");
			break;
	}
};

UrlBar.prototype.select = function() {

	var element;
	var input;

	if (this.currentModel.mode === 'url') {

		input = this.controller.get('urlbar-input');
		if (input) {
			element = input.querySelector('[name="urlbar-textfield-input"]');
			if (element) {
				element.select();
			}
		}
	}
};

UrlBar.prototype._onInputFocus = function(event) {

	this._urlBarFocused = true;
	this._activateGoButton(true);
};

UrlBar.prototype._onInputBlur = function(event) {

	this._urlBarFocused = false;

	// Switch the GO button view
	this._activateGoButton(false);

	// Notify the owner of a blur through the propertyChange
	// callback.
	this._onPropertyChange(event);
};

UrlBar.prototype.hasFocus = function() {

	return this._urlBarFocused;
};

UrlBar.prototype.getValue = function() {

	// We use the stored value rather that the element
	// because our models can change.
	return this.models.urlInput.model.url;
};

UrlBar.prototype.isInUrlMode = function() {
	var urlFlag = (this.currentModel.mode === 'url');
	return urlFlag;
};

UrlBar.prototype.getBarHeight = function() {

	return this.controller._menu.assistant.kMenuHeight;
};
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

UrlUtil = {};

/**
 * Convert "domain.tld" to "www.domain.tld". This is a pretty
 * stupid function at present and only looks for a single dot.
 * 
 * @param hostname The hostname to "fixup".
 */
UrlUtil.fixupHost = function(hostname) {

	try {
		var numDots = 0;
		for (var i = 0; i < hostname.length; i++) {
			if (hostname[i] === '.') {
				numDots = numDots + 1;
			}
		}

		if ( numDots === 1 ) {
			return 'www.' + hostname;
		}
		else {
			return hostname;
		}
	}
	catch (e) {
		return hostname;
	}
};

/**
 * Fixup the resource portion of a URL. By fix
 * @param {String} resource
 */
UrlUtil.fixupResource = function(resource) {
	var len = resource.length;
	if (len < 1) {
		return resource;
	}
	if (resource[len-1] == '/') {
		return resource.substr(0, len-1);
	}
	else {
		return resource;
	}
};

/**
 * Used only for debugging purposes.
 * @param {Object} The *parsed* URL to log.
 * @private
 */
UrlUtil.logUri = function(url) {
	Mojo.Log.info("Protocol: '%s'", url.getProtocol());
	Mojo.Log.info("host: '%s'", url.getHost());
	Mojo.Log.info("path: '%s'", url.getPathname());
	Mojo.Log.info("qs: '%s'", url.getQuerystring());
	Mojo.Log.info("frag: '%s'", url.getFragment());
	Mojo.Log.info("uname: '%s'", url.getUsername());
	Mojo.Log.info("pwd: '%s'", url.getPassword());
};

/**
 * Test if a URL is equal to the one in this instance.
 *
 * http://www.foo.com == http://www.foo.com/
 * http://foo.com == http://www.foo.com
 *
 * @param {Poly9.URLParser} urla 
 * @param {Poly9.URLParser} urlb
 */
UrlUtil.urlParsedEquals = function(urla, urlb) {

	if (urla === urlb) {
		return true;
	}
	
	try {
		if (urla.getProtocol() !== urlb.getProtocol()) {
			return false;
		}
		if (urla.getQuerystring() !== urlb.getQuerystring()) {
			return false;
		}
		if (urla.getUsername() !== urlb.getUsername()) {
			return false;
		}
		if (urla.getPassword() !== urlb.getPassword()) {
			return false;
		}
		if (urla.getFragment() !== urlb.getFragment()) {
			return false;
		}
		if (UrlUtil.fixupHost(urla.getHost()) !== UrlUtil.fixupHost(urlb.getHost())) {
			return false;
		}
		if (UrlUtil.fixupResource(urla.getPathname()) !== UrlUtil.fixupResource(urlb.getPathname())) {
			return false;
		}

		return true;
	}
	catch (e) {
		return false;
	}
};

UrlUtil.isResource = function(url) {
	var lc = url.toLowerCase();
	return lc.startsWith("http:") || lc.startsWith("https:") || lc.startsWith("ftp:");
};

UrlUtil.isCommand = function(url) {
	return !UrlUtil.isResource(url);
};

/**
 * Test if a URL is equal to the one in this instance.
 *
 * http://www.foo.com == http://www.foo.com/
 * http://foo.com == http://www.foo.com
 * 
 * @param {String} urla
 * @param {String} urlb
 */
UrlUtil.urlEquals = function(urla, urlb) {

	if (urla === urlb) {
		return true;
	}
	try {
		return UrlUtil.urlParsedEquals(new Poly9.URLParser(urla), new Poly9.URLParser(urlb));
	}
	catch (e) {
		return false;
	}
};

/**
 * Determine if the string appears to be a URL.
 * @param {String} url
 * @return true if it is a valid url, false if not.
 */
UrlUtil.appearsToBeUrl = function(url) {
	
	if (url.include(' ')) {
		return false;
	}
	else if (url === "localhost") {
		return true;
	}
	else if (url.startsWith("about:")) {
		return true;
	}
	else {
		return url.length >= 2 && (url.match(/^w+$/i) !== null || url.include(":/") || url.include("."));
	}
};

/**
 * Determine if the string passed could possibly be a URL.
 *
 * @remarks Remember because of our auto appending, etc. even short strings that
 *          don't look like URL's can still be made into one.
 *
 * @param {String} url
 * @return true if url appears to be a URL, false if not.
 */
UrlUtil.canBeUrl = function(url) {
	return url && url.length >= 2 && !url.include(" ");
};

/**
 * Determine if the string is a well formed URL (including the scheme).
 * @param {String} url
 * @return true if it is a well formed URL url, false if not.
 */
UrlUtil.isWellFormedUrl = function(url) {

	return UrlUtil.canBeUrl(url) && url.include("://");
};

/**
 * Cleanup a URL.
 */
UrlUtil.cleanup = function(url)
{
	if (url) {
		var matches = url.match(/^https?:\/\/(www\.)?(.*)$/i);
		if (matches) {
			return matches[2];
		}
		else {
			return url;
		}
	}
	else {
		return url;
	}
};

/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

function Chrome(controller) {
	this.controller = controller;
	
	// Default orientation is portrait
	this._elementName = undefined;
	this._maxHeight = 0;
	this._currentHeight = 0;
	
	// Set the default orienation
	this._configOrientation('up');
}

Chrome.prototype.setOrientation = function(orientation) {
	
	if ((orientation !== 'up')) {
		// In landscape and down we ALWAYS hide the chrome and force a slide off.
		this._hidePortrait();
	}

	this._configOrientation(orientation);
};

Chrome.prototype._configOrientation = function(orientation) {
	
	if ((orientation !== 'up')) {
		this.show = this._showNoOp;
		this.hide = this._hideNoOp;
	} else {
		// Default is portrait.
		this.show = this._showPortrait;
		this.hide = this._hidePortrait;
	}
	
	this._orientation = orientation;	
};

Chrome.prototype.setup = function(properties) {

	this._elementName = properties.elementName;
	
	var expanded = !!properties.expanded;
	if (this._elementName) {
	
		var element = this.controller.get(this._elementName);
		if (element) {
			this._maxHeight = parseInt(element.getStyle('height'), 10);
			this._currentHeight = expanded ? this._maxHeight : 0; 
			element.setStyle({
				'height': this._currentHeight + 'px'
			});
		}
	}
		
	this._configOrientation(properties.orientation || 'up');
};

Chrome.prototype.cleanup = function() {

};

// Default orientation is portrait mode.
Chrome.prototype.show = UrlBar.prototype._showPortrait;
Chrome.prototype.hide = UrlBar.prototype._hidePortrait;

Chrome.prototype._showPortrait = function() {
	
	var animator;
	var element = this.controller.get(this._elementName);
	
	// If we are visible then the current spacer height is 
	if (element && (this._currentHeight === 0)) {
		animator = Mojo.Animation.animateStyle(element, 'height', 'linear', {
			from: 0,
			to: this._maxHeight,
			duration: 0.15,
			reverse: false
		});
	
		this._currentHeight = this._maxHeight;
	}
};

Chrome.prototype._hidePortrait = function() {

	var animator;
	var element = this.controller.get(this._elementName);

	if (element && (this._currentHeight > 0)) {
		animator = Mojo.Animation.animateStyle(element, 'height', 'linear', {
			from: 0,
			to: this._maxHeight,
			duration: 0.15,
			reverse: true
		});
	
	
		this._currentHeight = 0;
	}
};

Chrome.prototype._showNoOp = function() {
	// DO NOTHING - We don't show anything in landscape.	
};

Chrome.prototype._hideNoOp = function() {
	// DO NOTHING - We should not be displayed in landscape.
};

Chrome.prototype.isVisible = function() {
	return (this._currentHeight > 0);
};
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

function StartpageAssistant(params) {

	this._destinationUrl = params.lastUrl;
	this._orientation = params.orientation || 'up';
	this._bookmarks = [];
	this._maxShowCount = 12;
	this.store = new BookmarkStore({
		database: Mojo.Controller.appController.assistant.getDatabase()
	});

	this._onStartPageTapHandler = this._onStartPageTap.bind(this);
	this._onKeyDownEventHandler = this._onKeyDownEvent.bind(this);
	this._onCardActivateHandler = this._onCardActivate.bind(this);
	this._onCardDeactivateHandler = this._onCardDeactivate.bind(this);
}

StartpageAssistant.prototype.setup = function() {

	var targetWindow = this.controller.window;
	if (targetWindow.PalmSystem && targetWindow.PalmSystem.setWindowOrientation) {
		this._orientation = Mojo.Controller.appController.getScreenOrientation();
		targetWindow.PalmSystem.setWindowOrientation(this._orientation);
	}

	// Allow override of 'back' gesture in landscape mode.
	this.controller.useLandscapePageUpDown(true);

	// Listen for scene scope keyboard events.
	this.controller.listen(this.controller.sceneElement, Mojo.Event.keydown, this._onKeyDownEventHandler);

	// Create the address bar
	this._addressBar = new AddressBar(this.controller);
	this._addressBar.setup({
		onPropertyChange: this._onAddressBarPropertyChange.bind(this),
		orientation: this._orientation,
		visible: true,
		onSelect: this._onAddressBarSelect.bind(this),
		onEnableSceneScroller: function(enable){}
	});

	// Setup the application menus.
	this.appMenuModel = {
		visible: true,
		items: [
			MenuData.ApplicationMenu.NewCard,
			MenuData.ApplicationMenu.AddBookmark,
			{
				label: $L("Page"),
				items: [
					MenuData.ApplicationMenu.AddToLauncher,
					MenuData.ApplicationMenu.SharePage]
			},
			MenuData.ApplicationMenu.ShowBookmarks,
			MenuData.ApplicationMenu.ShowHistory]
	};

	this.controller.setupWidget(Mojo.Menu.appMenu, undefined, this.appMenuModel);

	this._backModel = {
		label: MenuData.NavigationMenu.Back.label,
		icon: MenuData.NavigationMenu.Back.icon,
		command: MenuData.NavigationMenu.Back.command
	};

	this._forwardModel = {
		label: MenuData.NavigationMenu.Forward.label,
		icon: MenuData.NavigationMenu.Forward.icon,
		command: MenuData.NavigationMenu.Forward.command
	};

	this.cmdMenuModel = {
		visible: true,
		items: [
			this._backModel,
			this._forwardModel,
			{}]};

	this.controller.setupWidget(Mojo.Menu.commandMenu, {menuClass:'no-fade'}, this.cmdMenuModel);
};

StartpageAssistant.prototype.cleanup = function() {

};

StartpageAssistant.prototype._onAddressBarSelect = function(selection) {

	if (selection instanceof UrlReference) {
		// It's a bookmark
		this._openBookmark(selection);
	} else {
		// It's a simple string URL otherwise.
		this._openUrl(selection);
	}
};

StartpageAssistant.prototype._onKeyDownEvent = function(event) {

	if ((this._orientation === 'up') && !this._addressBar.hasFocus()) {
		// Only jump to the address bar IF we are a valid event that is
		// allowed to trigger the bar.
		if (this._addressBar.isAGotoAddressBarEvent(event.originalEvent)) {

			// Dismiss any currently viewed searchlist
			this._addressBar.closeSearchResults();

 			// Create/Show the adress bar and set input
			this._addressBar.setAddressAndTitle('', '');
			this._addressBar.enableUrlView();
			this._addressBar.show();
			this._addressBar.focus();
		}
	}
};

StartpageAssistant.prototype.ready = function() {

	var self = this;
	
	// Define the network alert function that's run when we have no
	// network available.
	var netAlert = function(params){

		// Show a custom network alert dialog.
		self.controller.showAlertDialog({
			
			onChoose: function(value) {
	
				if (value === 'help') {
					AppAssistant.launchNetworkHelp();
				}
			},
			
			title: $L('No Internet Connection'),
			message: $L('Please enable networking before using the browser.'),
			choices: [{
					label: $L('Help'),
					value: 'help',
					type: 'dismiss',
					buttonClass: 'secondary'
				}, {
					label: $L('OK'),
					value: 'OK',
					type: 'alert',
					buttonClass: 'primary'
				}]
		});
		
		// We have shown a dialog so defocus anything on the scene to avoid
		// input.
		self._addressBar.blur();		
	};

	AppAssistant.Network.addNetworkCheckedMethods({
		target: this,
		methods: ['_openUrl', '_processForwardCommand', '_openBookmark'],
		onNetworkDown: netAlert
	});

	// On first launch we alway attempt to connect to the network if we
	// are not online.
	if (!AppAssistant.Network.online) {
		
		ConnectionWidget.connect({
			type: 'data',
			onSuccess: function(response){
				// If the connection widget was previously cancelled and we
				// are under its internal timeout limit then popup the network
				// alert dialog.
				if (response === "WiFi-UserCancelled") {
					// Show the simple alert
					netAlert({});
				}
			}
		}, self.controller.stageController);
	}
	
	this._addressBar.closeSearchResults();
};

StartpageAssistant.prototype.activate = function(message) {

	// Listen for taps on the start page icons
	Mojo.Event.listen(this.controller.get('bookmarks'), Mojo.Event.tap, this._onStartPageTapHandler, true);
	this.controller.document.addEventListener(Mojo.Event.activate, this._onCardActivateHandler, false);
	this.controller.document.addEventListener(Mojo.Event.deactivate, this._onCardDeactivateHandler, false);

	this._isActivated = true;	
	this._addressBar.enableUrlView();
	this._setOrientation(this._orientation);
	if (message) {
		switch (message.type) {

			case 'history':
				this._openUrl.bind(this, message.payload).defer();
				break;

			case 'bookmarks':
				this._openBookmark.bind(this, message.payload).defer();
				break;

			default:
				this.refreshView();
				this._addressBar.show();
				this._addressBar.startObserving();
		}
	} else {

		this.refreshView();
		this._addressBar.show();
		this._addressBar.startObserving();
	}
};

StartpageAssistant.prototype.deactivate = function() {

	// Stop listening for taps on the start page icons
	Mojo.Event.stopListening(this.controller.get('bookmarks'), Mojo.Event.tap, this._onStartPageTapHandler, true);

	// Cleanup focus handlers.
	this.controller.document.removeEventListener(Mojo.Event.activate, this._onCardActivateHandler, false);
	this.controller.document.removeEventListener(Mojo.Event.deactivate, this._onCardDeactivateHandler, false);

	this._isActivated = false;
	this._addressBar.stopObserving();
	this._addressBar.closeSearchResults();
};

StartpageAssistant.prototype._refreshControls = function() {

	this.cmdMenuModel.items = $A();

	// If the assistant was constructed with a destination URL
	// then we need to activate and enable the forward button.
	if (this._destinationUrl) {
		this._backModel.disabled = true;
		this.cmdMenuModel.items.push(this._backModel);
		this._forwardModel.disabled = false;
		this.cmdMenuModel.items.push(this._forwardModel);
	} else {
		this.cmdMenuModel.items.push({});
		this.cmdMenuModel.items.push({});
	}
	this.cmdMenuModel.items.push({});

	this.controller.modelChanged(this.cmdMenuModel);
};

StartpageAssistant.prototype.orientationChanged = function(orientation) {

	// Switch the orientation of the menu spacer or other components..
	var targetWindow = this.controller.window;
	if (this._orientation === orientation) {
		return;
	}

	// Update the web browser UI components to reflect the new orientation.
	// Setting 'free' after an override doesn't currently work so we are
	// always explicit.
	this._orientation = orientation;
	if (targetWindow.PalmSystem && targetWindow.PalmSystem.setWindowOrientation && this._isActivated) {
		// Calling this will cause another orientationChanged event to we
		// protect against recursion by testing current orientation against
		// the new setting on re-entry.
		targetWindow.PalmSystem.setWindowOrientation(this._orientation);
	}

	this._setOrientation(this._orientation);
};

StartpageAssistant.prototype._setOrientation = function(orientation) {

	try {
		// Notify the URL bar of orientation change
		this._addressBar.setOrientation(orientation);
		
		// 3 modes - portrait-up, landscape and portrait down  - Need to think
		// about this a little more to encapsulate it better.
		var have_bookmarks = this.controller.get('have-bookmarks');
		if (orientation === 'left' || orientation === 'right') {
			// Force a hide of any visible spacer.
			have_bookmarks.addClassName('landscape');
			have_bookmarks.removeClassName('down');
		} else if (orientation === 'down') {
			// Force a hide of any visible spacer.
			have_bookmarks.removeClassName('landscape');
			have_bookmarks.addClassName('down');
		}
		else {
			have_bookmarks.removeClassName('landscape');
			have_bookmarks.removeClassName('down');
			// Special case is the start page. If we get an orientation
			// change in the startpage we have to redisplay the URL bar
			this._addressBar.show();
			this._addressBar.focus();
		}
	}
	catch (e) {
		Mojo.Log.logException(e, "StartpageAssistant#_setOrientation");
	}
};

StartpageAssistant.prototype._onCardActivate = function(event) {

	// Update the startpage. It could of been changed by another
	// instance. 
	this.refreshView();
};

StartpageAssistant.prototype._onCardDeactivate = function(event) {

};

StartpageAssistant.prototype.handleCommand = function(event) {

	switch (event.type) {
		case Mojo.Event.back:
			// If the searchlist is showing then hide it.
			// If it's not showing then switch to card view.
			if (this._addressBar.areSearchResultsVisible()) {
				this._addressBar.closeSearchResults();
				event.preventDefault();
			}
			else if (this._addressBar.hasFocus()) {
				this._addressBar.enableTitleView();
				event.preventDefault();
			} else {
				this.controller.stageController.deactivate();
			}
			event.stopPropagation();

			break;
			
		case Mojo.Event.forward: 

			// NOTE: According to HI we should keep this simple and 
			// just move forward.
			if (this._destinationUrl) {
				this._addressBar.enableTitleView();
				this._addressBar.closeSearchResults();

				event.preventDefault();
				event.stopPropagation();
				this._processForwardCommand();			
			}
			break;

		case Mojo.Event.command:

			switch (event.command) {

				case MenuData.ApplicationMenu.ShowHistory.command:
					this._openHistoryView();
					break;

				case MenuData.ApplicationMenu.ShowBookmarks.command:
					this._openBookmarkView();
					break;

				case MenuData.ApplicationMenu.NewCard.command:
					this._openNewBrowser();
					break;

				case MenuData.NavigationMenu.Forward.command:
					this._processForwardCommand();
					break;

				case UrlBar.Menu.TitleTap.command:
					this._processTitleTapCommand();
					break;

				case UrlBar.Menu.Go.command:
					this._processGoCommand();
					break;

				case Mojo.Menu.prefsCmd:
					this._openPreferencesView();
					break;

				case Mojo.Menu.helpCmd:
					this._showHelpCommand();
					break;
			}
			break;

		case Mojo.Event.commandEnable:

			// Standard Application Menu commands.
			if (event.command === Mojo.Menu.prefsCmd ||
				event.command === Mojo.Menu.helpCmd) {

				event.stopPropagation(); // Enable the chosen menuitems
				return;
			}

			// Application specific Applicaiton Menu commands.
			if (event.command === MenuData.ApplicationMenu.SharePage.command) {

				event.preventDefault();
				return;
			}

			if (event.command === MenuData.ApplicationMenu.AddToLauncher.command ||
				event.command === MenuData.ApplicationMenu.AddBookmark.command) {

				event.preventDefault();
				return;
			}
			break;
	}
};

StartpageAssistant.prototype._processForwardCommand = function() {

	this.controller.stageController.popScene({type: 'startpage'});
};

StartpageAssistant.prototype._processTitleTapCommand = function() {

	try {
		this._addressBar.enableUrlView();
		this._addressBar.focus();
		this._addressBar.select();
	} catch (e) {
		Mojo.Log.logException(e, '_processTitleTapCommand');
	}
};

StartpageAssistant.prototype._processGoCommand = function() {

	var value = this._addressBar.getValue();
	if (value) {
		this._openUrl(this._addressBar.convertInputToUrl(value));
	} else {
		// We did nothing so switch focus back to the URL bar
		this._addressBar.focus();
	}
};

StartpageAssistant.prototype.refreshView = function() {

	// Enable/Disable the user navigation controls.
	this._refreshControls();

	// Read the bookmarks and render the content
	this.store.readBookmarks(
		new BookmarkFolder(),
		null,
		this._render.bind(this),
		function() {
			Mojo.Log.error("Error reading bookmarks");
		},
		0,
		this._maxShowCount);
};

StartpageAssistant.prototype._render = function(bookmarkFolder) {

	// Warning:
	// This is the callback to a bookmark request to the bookmarks
	// database. As such it's async so we need to be sure the controller
	// and startpage elements still exist before we try and update.
	// This can happen if switch from a webpage to a startpage and
	// back again in quick succession.
	try {
		if (this.controller) {
			var bookmarksDiv = this.controller.get('bookmarks');
			var no_bookmarks = this.controller.get('no-bookmarks');
			var have_bookmarks = this.controller.get('have-bookmarks');
			if (bookmarksDiv && no_bookmarks && have_bookmarks) {
				var count = 0;
				this._bookmarks = [];

				// We don't load all bookmarks into the start page - just a subset.
				for (var d = 0; d < bookmarkFolder.contents.length && d < this._maxShowCount; d++) {

					var item = bookmarkFolder.contents[d];
					if (item instanceof UrlReference) {

						item.startPageIndex = count++;
						if (!item.title || item.title.empty()) {
							item.title = "&nbsp;";
						}
						else {
							item.title = item.title.escapeHTML();
						}

						this._bookmarks.push(item);
					}
				}

				// Render the bookmarks into a list
				var items = Mojo.View.render({
					collection: this._bookmarks,
					template: 'page/start-page'
				});

				if (count <= 0) {
					no_bookmarks.style.display = 'block';
					have_bookmarks.style.display = 'none';
				}
				else {
					// Insert the bookmarks into the DOM
					bookmarksDiv.update(items);
					no_bookmarks.style.display = 'none';
					have_bookmarks.style.display = 'block';
				}
			}
		}
	}
	catch (e) {
		Mojo.Log.logException(e, "_render");
	}
};

StartpageAssistant.prototype._onStartPageTap = function(event) {

	try {
		// Make sure the tap was on a bookmark (not the space between them)
		if (!event.down.target || !event.down.target.title) {
			return;
		}

		// Open the URL
		this._openBookmark(this._bookmarks[parseInt(event.down.target.title, 10)]);

	} catch (e) {

		Mojo.Log.logException(e, "#_handleStartPageTap()");
	}
};

StartpageAssistant.prototype._onAddressBarPropertyChange = function(propertyChangeEvent) {

	try {
		// With the new TextField widget we can query the action that
		// caused this event on the URL text field by looking at the
		// originalEvent property. Nice.
		var url;
		switch (propertyChangeEvent.type) {

			case 'keyCode':

				url = propertyChangeEvent.value;
				
				// Update the title ...
				this._addressBar.setAddressAndTitle(url, UrlUtil.cleanup(url));

				if (propertyChangeEvent.keyCode === Mojo.Char.enter) {

					// Clear the timers and hide the search list.
					this._addressBar.closeSearchResults();

					// Check we have a URL and if not then don't do anything.
					if (url) {
						this._openUrl(this._addressBar.convertInputToUrl(url));
					}
				}
				break;

			case 'blur':

				// Always switch to title mode on a blur.
				this._addressBar.enableTitleView();
				this._addressBar.closeSearchResults();
				break;

			case 'mojo-property-change':
			
				// Update the title with the new URL resulting from a copy/paste action.
				url = propertyChangeEvent.value;
				this._addressBar.setAddressAndTitle(url, UrlUtil.cleanup(url));
				break;

			default:
				Mojo.Log.warn("Unknown propertyChangeEvent");
				break;
		}
	}
	catch (e) {
		Mojo.Log.logException(e, '_onAddressBarPropertyChange');
	}
};

StartpageAssistant.prototype._openUrl = function(url) {

	// Application could of now been launched as just a startpage so
	// we need to be aware how we should handle open requests.
	this._addressBar.setAddressAndTitle(url, UrlUtil.cleanup(url));
	this._addressBar.enableTitleView();
	if (this._destinationUrl) {
		this.controller.stageController.popScene({
			type: 'startpage',
			payload: url
		});
	}
	else {
		
		this.controller.stageController.swapScene({
				name: 'page',
				transition: Mojo.Transition.crossFade
			}, 
			{
				launchParams: {
					target: url,
					orientation: this._orientation	
				}
			});
	}
};

StartpageAssistant.prototype._openBookmark = function(bookmark) {

	// Application could of now been launched as just a startpage so
	// we need to be aware how we should handle open requests.
	var url = bookmark.url;
	this._addressBar.setAddressAndTitle(url, UrlUtil.cleanup(url));
	this._addressBar.enableTitleView();
	if (this._destinationUrl) {
		this.controller.stageController.popScene({
			type: 'startpage-bookmark',
			payload: bookmark
		});
	}
	else {
		// Currently, a Launch will not handle the update count so we
		// perform this task ourselves before creating the page assistant.
		this.store.updateVisitCount(
			bookmark,
			function() {},
			function() {});

		this.controller.stageController.swapScene({
				name: 'page',
				transition: Mojo.Transition.crossFade
			}, 
			{
				launchParams: {
					target: url,
					bookmark: bookmark,
					orientation: this._orientation	
				}
			});
	}
};

StartpageAssistant.prototype._openBookmarkView = function() {

	var targetWindow = this.controller.window;
	if (targetWindow.PalmSystem && targetWindow.PalmSystem.setWindowOrientation) {
		targetWindow.PalmSystem.setWindowOrientation("up");
	}

	this.controller.stageController.pushScene('bookmarks', AppAssistant.WebPreferences);
};

StartpageAssistant.prototype._openHistoryView = function() {

	var targetWindow = this.controller.window;
	if (targetWindow.PalmSystem && targetWindow.PalmSystem.setWindowOrientation) {
		targetWindow.PalmSystem.setWindowOrientation('up');
	}

	this.controller.stageController.pushScene('history', AppAssistant.WebPreferences);
};

StartpageAssistant.prototype._openPreferencesView = function(){

	var targetWindow = this.controller.window;
	if (targetWindow.PalmSystem && targetWindow.PalmSystem.setWindowOrientation) {
		targetWindow.PalmSystem.setWindowOrientation('up');
	}

	this.controller.stageController.pushScene('preferences', AppAssistant.WebPreferences);
};

StartpageAssistant.prototype._openNewBrowser = function() {

	var params = {};
	this.controller.serviceRequest('palm://com.palm.applicationManager', {
		method: 'open',
		parameters: {
			'id': 'com.palm.app.browser',
			'params': params
		}
	});
};

StartpageAssistant.prototype._showHelpCommand = function() {

	// Launch the help system.
	AppAssistant.launchHelp();
};
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * The data source for the bookmark list (BigList).
 */
BookmarkDataSource = Class.create({

	bookmarkModel: undefined,

	/**
	 * Constructor.
	 */
	initialize : function(bookmarkModel) {
		this.items = $A();
		this.limit = this.offset = 0;
		this.bookmarkModel = bookmarkModel;

		for (var i = 0; i < bookmarkModel.contents.length; i++) {
			var item = bookmarkModel.contents[i];
			if (item instanceof UrlReference) {
				this.items.push({title:item.title, url:item.url});
			}
		}
	},

	updateOffsetAndLimit: function(offset, limit) {
	    this.offset = offset;
	    this.limit = limit;
	    this.sendNewData();
	},

	dataAsResultSet: function() {
		var lastIndex = this.offset + this.limit;
		if (lastIndex > this.items.length) {
			lastIndex = this.items.length;
		}
		var offset = this.offset;
		if (offset > this.items.length) {
			offset = this.items.length;
		}
		var resultSet = {
			offset: offset,
			limit: lastIndex,
			list: this.items.slice(offset, lastIndex),
			total: this.items.length};
		var itemIndex = offset + 1;
		resultSet.list.each(function(item) {item.index = itemIndex;++itemIndex;});
		return resultSet;
	},

	sendNewData: function() {
		if (this.controller && this.controller.handleNewData) {
			var resultSet = this.dataAsResultSet();
			this.controller.handleNewData.bind(this.controller).delay(0.2, resultSet);
		}
	}
});

/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * A folder of bookmarks and other folders
 */
BookmarkFolder = Class.create({

	/**
	 * Constructor.
	 */
	initialize: function(name) {
		this.contents = $A();
		this.name = name;
	},

	/**
	 * Add a UrlReference or BookmarkFolder to this folder. The caller
	 * is responsible for checking for duplicates.
	 * 
	 * @param {UrlReference} item	The item to add. Caller is responsible for ensuring no dups.
	 */
	addItem: function(item) {
		this.contents.push(item);
	},

	/**
	 * Remove the item (UrlReference or BookmarkFolder) from this folder.
	 * 
	 * * @param {UrlReference} item The item to remove.
	 */
	removeItem: function(item) {
		for (var i = 0; i < this.contents.length; i++) {
			if (this.contents[i] == item) {
				this.contents.splice(i, 1);
				return;
			}
		}
	},

	/**
	 * Looks for a UrlReference recursively in this folder and all subfolders
	 *  and returns it if found.
	 *
	 * @return The item, or undefined if not found.
	 */
	findUrlRecursive: function(url) {
		// TODO - Need to implement recursive function.
		return this.findUrl(url);
	},

	/**
	 * Looks for a UrlReference and returns it if found.
	 *
	 * @return The item, or undefined if not found.
	 */
	findUrl: function(url) {
		for (var i = 0; i < this.contents.length; i++) {
			var item = this.contents[i];
			if (item instanceof UrlReference) {
				if (item.url == url) {
					return item;
				}
			}
		}
	},

	/**
	 * Looks for a BookmarkFolder and returns it if found.
	 *
	 * @return The folder, or undefined if not found.
	 */
	findFolder: function(folderName) {
		for (var i = 0; i < this.contents.length; i++) {
			var item = this.contents[i];
			if (item instanceof BookmarkFolder) {
				if (item.name == folderName) {
					return item;
				}
			}
		}
	}
});


/**
 * An object to store the bookmarks in the HTML5 database.
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */
/**
 * @constructor
 *
 * @param {Object} options
 * @param {function} onSuccess function to call if database is successfully opened/created.
 * @param {function} onFailure fucntion to call if there is an error opening the database.
 */
function BookmarkStore(options, onSuccess, onFailure){
	
	try {
		this.displayName = options.displayName || this.name;
		this.estimatedSize = options.estimatedSize;
		this.database = options.database;
				
		this.database.transaction(this._createTable.bind(this, onSuccess, onFailure));
	} catch (e) {
		Mojo.Log.logException(e, "BookmarkStore()");
	}
}

BookmarkStore.prototype._cleanupWrite = function(){
	delete this.writeTransaction;
	delete this.writeItems;
	delete this.onWriteSuccess;
	delete this.onWriteFailure;
};

/** @private */
BookmarkStore.prototype._writeSuccess = function(){
	if (this.writeItems.length === 0) {
		var successFunc = this.onWriteSuccess;
		this._cleanupWrite();
		successFunc();
	}
	else {
		this._writeNextBookmark();
	}
};

/** @private */
BookmarkStore.prototype._writeFailure = function(e, msg){
	Mojo.Log.error("_writeFailure");
	var failFunc = this.onWriteFailure;
	this._cleanupWrite();
	failFunc(e, msg);
};

/** @private */
BookmarkStore.prototype._formatDate = function(date){
	if (!date) {
		date = new Date();
	}
	return date.getUTC().getTime();
};

/** @private */
BookmarkStore.prototype._writeNextBookmark = function(){
	try {
		if (this.writeItems.length) {
			var item = this.writeItems.pop();
			
			if (item instanceof UrlReference) {
				var utcDateCreated = this._formatDate(item.date);
				var utcLastVisited = this._formatDate(item.lastVisited);
				var parent = 0; // For when we add support for folders.
				var index = item.index || 0;
				
				var defEntry = 0;
				if (item.defaultEntry) {
					defEntry = 1;
				}
				
				var sql = "INSERT INTO 'bookmarks' (url, title, date, parent, idx, defaultEntry, iconFile32, iconFile64, thumbnailFile, visitCount, lastVisited)" +
				" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
				this.writeTransaction.executeSql(sql, [item.url, item.title, utcDateCreated, parent, index, defEntry, item.iconFile32, item.iconFile64, item.thumbnailFile, item.visitCount, utcLastVisited], this._writeSuccess.bind(this), this._writeFailure.bind(this));
			}
			else {
				Mojo.Log.warn("Don't have a UrlReference, skipping");
				this._writeSuccess();
			}
		}
		else {
			this._writeSuccess();
		}
	} 
	catch (e) {
		this._writeFailure(e, '_writeNextBookmark');
		throw e;
	}
};

/** @private */
BookmarkStore.prototype._writeAllBookmarks = function(transaction){

	this.writeTransaction = transaction;
	this._writeNextBookmark();
};

/**
 * Write the bookmark folder to the database.
 * @param {BookmarkFolder} folder
 * @param {function} onSuccess Function to call if this operation completes successfully.
 * @param {function} onFailure Function to call if this operation fails.
 */
BookmarkStore.prototype.write = function(folder, onSuccess, onFailure){
	try {
		if (this.writeTransaction === undefined) {
			this.writeItems = $A().concat(folder.contents);
			this.onWriteSuccess = onSuccess;
			this.onWriteFailure = onFailure;
			this.database.transaction(this._writeAllBookmarks.bind(this));
		}
		else {
			onFailure('Transaction already in progress', undefined);
		}
	} 
	catch (e) {
		Mojo.Log.logException(e);
		onFailure(undefined, e);
	}
};

/**
 * Increment the sort order index of the bookmarks and then insert a new bookmark.
 *
 * @param {UrlReference} urlRef
 * @param {function} onSuccess Function to call if this operation completes successfully.
 * @param {function} onFailure Function to call if this operation fails.
 *
 * @private
 */
BookmarkStore.prototype._incrementIndex = function(urlRef, onSuccess, onFailure, transaction) {
	var success = function() {
		this._insertBookmark(urlRef, onSuccess, onFailure, transaction);
	}.bind(this);
	this._executeSql(transaction, success, onFailure, "UPDATE bookmarks SET idx = idx + 1");
};

/**
 * Add a bookmark.
 *
 * @param {UrlReference} urlRef
 * @param {function} onSuccess Function to call if this operation completes successfully.
 * @param {function} onFailure Function to call if this operation fails.
 *
 * @private
 */
BookmarkStore.prototype._insertBookmark = function(urlRef, onSuccess, onFailure, transaction){

	var utcDateCreated = this._formatDate(urlRef.date);
	var utcLastVisited = this._formatDate(urlRef.lastVisited);
	var parent = 0; // For when we add support for folders.
	var index = urlRef.index || 0;
	
	var sql = "INSERT INTO 'bookmarks' (url, title, date, parent, idx, defaultEntry, iconFile32, iconFile64, thumbnailFile, visitCount, lastVisited)" +
	" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
	this._executeSql(transaction, onSuccess, onFailure, sql, [urlRef.url, urlRef.title, utcDateCreated, parent, index, urlRef.defaultEntry ? 1 : 0, urlRef.iconFile32, urlRef.iconFile64, urlRef.thumbnailFile, urlRef.visitCount, utcLastVisited]);
};

BookmarkStore.prototype._updateVisitCount = function(urlRef, onSuccess, onFailure, transaction){
	Mojo.assert(urlRef.id !== undefined, "bookmark not from database.");
	urlRef.lastVisited = new Date();
	urlRef.visitCount++;
	
	var sql = 'UPDATE bookmarks SET visitCount = visitCount + 1, lastVisited = ' +
	this._formatDate(urlRef.lastVisited) +
	' WHERE id = ' +
	urlRef.id;
	
	this._executeSql(transaction, onSuccess, onFailure, sql);
};

BookmarkStore.prototype.updateVisitCount = function(urlRef, onSuccess, onFailure){

	try {
		this.database.transaction(this._updateVisitCount.bind(this, urlRef, onSuccess, onFailure));
	} 
	catch (e) {
		Mojo.Log.logException(e);
		onFailure(undefined, e);
	}
};

BookmarkStore.prototype._updateThumbnail = function(urlRef, onSuccess, onFailure, transaction){
	Mojo.assert(urlRef.id !== undefined, "bookmark not from database.");
	
	var sql = "UPDATE bookmarks SET thumbnailFile = '" + urlRef.thumbnailFile +
	"' WHERE id = " +
	urlRef.id;
	
	this._executeSql(transaction, onSuccess, onFailure, sql);
};

BookmarkStore.prototype.updateThumbnail = function(urlRef, onSuccess, onFailure){

	try {
		this.database.transaction(this._updateThumbnail.bind(this, urlRef, onSuccess, onFailure));
	} 
	catch (e) {
		Mojo.Log.logException(e);
		onFailure(undefined, e);
	}
};

BookmarkStore.prototype._escapeString = function(str)
{
	return str.replace("'", "''");
};

/**
 * Update a bookmark.
 *
 * @param {UrlReference} urlRef
 * @param {function} onSuccess
 * @param {function} onFailure
 *
 * @private
 */
BookmarkStore.prototype._updateBookmark = function(urlRef, onSuccess, onFailure, transaction){

	var utcDateCreated = this._formatDate(urlRef.date);
	var utcLastVisited = this._formatDate(urlRef.lastVisited);
	
	var parent = 0; // For when we add support for folders.
	var index = urlRef.index || 0;
	
	var defEntry = 0;
	if (urlRef.defaultEntry) {
		defEntry = 1;
	}
	
	var sql = "UPDATE 'bookmarks' ";
	sql += "SET date=" + utcDateCreated + ", ";
	sql += "url='" + urlRef.url + "', ";
	sql += "title='" + this._escapeString(urlRef.title) + "', ";
	sql += "parent=" + parent + ", ";
	sql += "idx=" + index + ", ";
	sql += "defaultEntry=" + defEntry + ", ";
	sql += "iconFile32='" + urlRef.iconFile32 + "', ";
	sql += "iconFile64='" + urlRef.iconFile64 + "', ";
	sql += "thumbnailFile='" + urlRef.thumbnailFile + "', ";
	sql += "visitCount=" + urlRef.visitCount + ", ";
	sql += "lastVisited=" + utcLastVisited + " ";
	
	sql += "WHERE id=" + urlRef.id;
	this._executeSql(transaction, onSuccess, onFailure, sql);
};

/**
 * Delete <em>all</em> default bookmark.
 *
 * @param {UrlReference} id
 * @param {function} onSuccess
 * @param {function} onFailure
 *
 * @private
 */
BookmarkStore.prototype._deleteDefaultBookmarks = function(onSuccess, onFailure, transaction){
	var sql = "DELETE from 'bookmarks' WHERE defaultEntry <> 0";
	this._executeSql(transaction, onSuccess, onFailure, sql);
};

/**
 * Delete <em>all</em> default bookmark.
 *
 * @param {function} onSuccess
 * @param {function} onFailure
 */
BookmarkStore.prototype.deleteDefaultBookmarks = function(onSuccess, onFailure){
	this.database.transaction(this._deleteDefaultBookmarks.bind(this, onSuccess, onFailure));
};

/**
 * Delete a bookmark.
 *
 * @param {Number} id
 * @param {function} onSuccess
 * @param {function} onFailure
 *
 */
BookmarkStore.prototype.deleteBookmark = function(id, onSuccess, onFailure){
	if (id) {
		this.database.transaction(this._deleteBookmark.bind(this, id, onSuccess, onFailure));
	}
};

/**
 * Delete a bookmark.
 *
 * @param {Number} id
 * @param {function} onSuccess
 * @param {function} onFailure
 *
 * @private
 */
BookmarkStore.prototype._deleteBookmark = function(id, onSuccess, onFailure, transaction){
	var sql = "DELETE from 'bookmarks'";
	if (id === 'ALL') {
		Mojo.Log.info("Deleting all bookmarks");
	}
	else {
		sql += " WHERE id=" + id;
	}
	this._executeSql(transaction, onSuccess, onFailure, sql);
};

/**
 * Insert or update a bookmark. If the URL reference (bookmark) has an ID property then
 * we know it came from a database and we do an update, else we do an insert incrementing all
 * existing bookmark indexes.
 *
 * @param {UrlReference} urlRef
 * @param {function} onSuccess
 * @param {function} onFailure
 */
BookmarkStore.prototype.insertOrUpdateBookmark = function(urlRef, onSuccess, onFailure){

	try {
		if (urlRef.id === undefined) {
			this.database.transaction(this._incrementIndex.bind(this, urlRef, onSuccess, onFailure));
		}
		else {
			this.database.transaction(this._updateBookmark.bind(this, urlRef, onSuccess, onFailure));
		}
	} 
	catch (e) {
		Mojo.Log.logException(e);
		onFailure(undefined, e);
	}
};

/** @private */
BookmarkStore.prototype._extractBookmarks = function(folder, onSuccess, transaction, resultSet){
	try {
		for (var i = 0; i < resultSet.rows.length; i++) {
			var item = resultSet.rows.item(i);
			
			var date = new Date(item.date);
			date = date.addMinutes(date.getTimezoneOffset()); // Convert to local time
			var bookmark = new UrlReference(item.url, item.title, date);
			bookmark.index = item.idx;
			bookmark.defaultEntry = item.defaultEntry === undefined ? false : item.defaultEntry !== 0;
			bookmark.iconFile32 = item.iconFile32;
			bookmark.iconFile64 = item.iconFile64;
			bookmark.thumbnailFile = item.thumbnailFile;
			bookmark.visitCount = item.visitCount;
			bookmark.id = item.id; // We save the DB ID which is now we can tell that a bookmark
			// was already saved.
			if (item.lastVisited) {
				date = new Date(item.lastVisited);
				bookmark.lastVisited = date.addMinutes(date.getTimezoneOffset()); // Convert to local time
			}
			
			folder.addItem(bookmark);
		}
		onSuccess(folder);
	} 
	catch (e) {
		Mojo.Log.logException(e, '_extractBookmarks');
		throw e;
	}
};

/** @private */
BookmarkStore.prototype._readBookmarks = function(folder, filterText, onSuccess, onFailure, offset, limit, transaction){
	var sql;
	if (filterText) {
		sql = "SELECT * FROM bookmarks WHERE (url LIKE '%" + filterText +
		"%') or (title LIKE '%" +
		filterText +
		"%') ORDER BY idx ASC";
	}
	else {
		sql = "SELECT * FROM 'bookmarks' ORDER BY idx ASC";
	}
	
	// Add the limits
	if (limit !== undefined) {
		sql = sql + " LIMIT " + limit;
		if (offset !== undefined) {
			sql = sql + " OFFSET " + offset;
		}
	}

	this._executeSql(transaction, this._extractBookmarks.bind(this, folder, onSuccess), onFailure, sql);
};

/**
 * Read all bookmarks from the database.
 *
 * @param {BookmarkFolder} folder
 * @param {String} filterText
 * @param {function} onSuccess
 * @param {function} onFailure
 */
BookmarkStore.prototype.readBookmarks = function(folder, filterText, onSuccess, onFailure, offset, limit){
	
	this.database.transaction(this._readBookmarks.bind(this, folder, filterText, onSuccess, onFailure, offset, limit));
};

/** @private */
BookmarkStore.prototype._executeSql = function(transaction, onSuccess, onFailure, sqlString, valuesList){
	
	onSuccess = onSuccess || function() {};
	onFailure = onFailure || function() {};
	valuesList = valuesList || [];
	try {
		transaction.executeSql(sqlString, valuesList, onSuccess, onFailure);
	} 
	catch (e) {
		onFailure(e, '_executeSql');
		throw e;
	}
};

/** @private */
BookmarkStore.prototype._createTable = function(onSuccess, onFailure, transaction){
	var sql = "CREATE TABLE IF NOT EXISTS 'bookmarks' " +
	"('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
	"'url' TEXT NOT NULL UNIQUE ON CONFLICT REPLACE DEFAULT '', " +
	"'title' TEXT DEFAULT '', " +
	"'date' INTEGER(8) NOT NULL DEFAULT '0', " +
	"'parent' INTEGER NOT NULL DEFAULT '0', " +
	"'idx' INTEGER NOT NULL DEFAULT '0', " +
	"'defaultEntry' INTEGER NOT NULL DEFAULT '0', " +
	"'iconFile32' TEXT DEFAULT NULL, " +
	"'iconFile64' TEXT DEFAULT NULL, " +
	"'visitCount' INTEGER NOT NULL DEFAULT '0', " +
	"'thumbnailFile' TEXT DEFAULT NULL, " +
	"'lastVisited' INTEGER(8) NOT NULL DEFAULT '0', " +
	"'startIdx' INTEGER(8) NOT NULL DEFAULT '0')";
	this._executeSql(transaction, onSuccess, onFailure, sql);
};
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * The data source for the history list (BigList).
 */
HistoryDataSource = Class.create({

	histModel: undefined,

	/**
	 * Constructor.
	 */
	initialize : function(histModel) {
		this.histModel = histModel;

		this.createItems();

		if (this.controller) {
			this.controller.dataAddedAtEnd.bind(this.controller).delay(0.2, this.items.length);
		}
	},

	createItems: function() {

		this.items = $A();
		this.limit = this.offset = 0;

		var prevDate = new Date(0);
		var visible;

		for (var d = 0; d < this.histModel.days.length; d++) {
			var day = this.histModel.days[d];
			for (var i = 0; i < day.items.length; i++) {
				var item = day.items[i];
				if ( this.datesMatch(prevDate, item.date )) {
					visible = "visibility: hidden; height:0";
				}
				else {
					visible = "visibility: visible";
				}
				var hdr = item.date.toLocaleString();
				this.items.push({title:item.title, url:item.url, 'hdr-visible':visible, hdr:hdr});
				prevDate = item.date;
			}
		}

		if (this.items.length === 0) {
			visible = "visibility: hidden; height:0";
			this.items.push({title:"Empty", url:"", 'hdr-visible':visible, hdr:""});
		}
	},

	datesMatch: function(date1, date2) {
		return (date1.getFullYear() === date2.getFullYear()) &&
				(date1.getMonth() === date2.getMonth()) &&
				(date1.getDate() === date2.getDate());
	},

	updateOffsetAndLimit: function(offset, limit) {
	    this.offset = offset;
	    this.limit = limit;
	    this.sendNewData();
	},

	dataAsResultSet: function() {
		var lastIndex = this.offset + this.limit;
		if (lastIndex > this.items.length) {
			lastIndex = this.items.length;
		}
		var offset = this.offset;
		if (offset > this.items.length) {
			offset = this.items.length;
		}
		var resultSet = {
			offset: offset,
			limit: lastIndex,
			list: this.items.slice(offset, lastIndex),
			total: this.items.length};
		var itemIndex = offset + 1;
		resultSet.list.each(function(item) {item.index = itemIndex;++itemIndex;});
		return resultSet;
	},

	sendNewData: function() {
		if (this.controller && this.controller.handleNewData) {
			var resultSet = this.dataAsResultSet();
			this.controller.handleNewData.bind(this.controller).delay(0.2, resultSet);
		}
	}
});

/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * A collection of history items all of which were visited on the same
 * day (but not same time).
 */
HistoryDay = Class.create({

	/**
	 * Constructor.
	 */
	initialize: function(date) {
		/**
		 * The date (but not the time) for which all of these UrlReference's
		 * are on. We only look at the year/month/day of this value.
		 */
		this.date = date;

		/**
		 * An array of UrlReference instances.
		 */
		this.items = $A();
	},

	/**
	 * A callback to Array.sort for sorting items by date.
	 */
	sortItemCallback: function(itemA, itemB) {
		if ( itemA.date > itemB.date ) {
			return -1;
		}
		else if ( itemA.date < itemB.date ) {
			return 1;
		}
		else {
			return 0;
		}
	},

	/**
	 * Does this date (just the date portion) match the supplied date?
	 *
	 * @return true if matches, false if not.
	 */
	matchesDate: function(date) {
		return (this.date.getFullYear() === date.getFullYear()) &&
				(this.date.getMonth() === date.getMonth()) &&
				(this.date.getDate() === date.getDate());
	},

	/**
	 * Finds a history item for the specified url.
	 *
	 * @return The UrlReference or undefined if not found.
	 */
	findItem: function(url) {

		for (var i = 0; i < this.items.length; i++) {
			var item = this.items[i];
			if (item.urlEquals(url)) {
				return item;
			}
		}

		return undefined;
	},

	/**
	 * Add the UrlReference to this day. The caller has already verified
	 * that the day of the item matches this day.
	 *
	 * A URL will only exist in the same day once. The last time the page was
	 * visited is the date/time that it is given. So if the same URL is added
	 * a second time then only the date is updated in the URL already in
	 * this list.
	 */
	addItem: function(item) {
		var found = this.findItem(item.url);
		if (Object.isUndefined(found)) {
			// Add a new history item.
			this.items[this.items.length] = item;
		}
		else {
			// Just update the date
			if (item.date > found.date) {
				found.date = item.date;
			}
		}
		this.items.sort(this.sortItemCallback);
	},

	addNewItem: function(item) {
		// Add a new history item.
		this.items[this.items.length] = item;
		this.items.sort(this.sortItemCallback);
	}
});
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * This represents the entire browser history.
 */
HistoryList = Class.create({

	MAX_HISTORY_DAYS: 21,

	/**
	 * Constructor.
	 */
	initialize: function() {
		this.days = $A();
	},

	/**
	 * Find the HistoryDay instance (if there is one) for the
	 * specified date.
	 *
	 * @return Reference to the HistoryDay object, or undefined if not found.
	 */
	findDay: function(date) {

		for (var i = 0; i < this.days.length; i++) {
			var day = this.days[i];
			if (day.matchesDate(date)) {
				return day;
			}
		}

		return undefined;
	},

	/**
	 * Remove the history that is old that we don't care about anymore.
	 */
	trimOldHistory: function() {
		while (this.days.length > this.MAX_HISTORY_DAYS) {
			this.days.pop();
		}
	},

	/**
	 * A callback to Array.sort for sorting items by date.
	 */
	sortDayCallback: function(dayA, dayB) {
		if ( dayA.date > dayB.date ) {
			return -1;
		}
		else if ( dayA.date < dayB.date ) {
			return 1;
		}
		else {
			return 0;
		}
	},

	/**
	 * Sort the days reverse chronological order.
	 */
	sort: function() {
		this.days.sort(this.sortDayCallback);
	},

	/**
	 * Add a page to the history. Days will be sorted but Will not trim off old history.
	 */
	addPage: function(url, title, date) {
		if (Object.isNumber(date)) {
			var utc = date;
			date = new Date();
			date.setUTCTime(utc);
		}
		var day = this.findDay(date);
		if (Object.isUndefined(day)) {
			// Start a new day
			var dayDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());	// date, but not time
			day = new HistoryDay(dayDate);
			this.days.push(day);
			this.sort();
		}
		day.addItem(new UrlReference(url, title, date));
	},

	addNewPage: function(url, title, date) {
		if (Object.isNumber(date)) {
			var utc = date;
			date = new Date();
			date.setUTCTime(utc);
		}
		var day = this.findDay(date);
		if (Object.isUndefined(day)) {
			// Start a new day
			var dayDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());	// date, but not time
			day = new HistoryDay(dayDate);
			this.days.push(day);
			this.sort();
		}
		day.addNewItem(new UrlReference(url, title, date));
	}
});
/**
 * An object to store the history data in the HTML5 database.
 *
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * @constructor
 *
 * @param {Object} options
 * @param {Object} onSuccess
 * @param {Object} onFailure
 */
function HistoryStore(options, onSuccess, onFailure) {

	try {
		this.displayName = options.displayName || this.name;
		this.estimatedSize = options.estimatedSize;
		this.database = options.database;
		this.database.transaction(this._createTable.bind(this, onSuccess, onFailure));
	} catch (e) {
		Mojo.Log.logException(e, 'HistoryStore()');
	}
}

/** @private */
HistoryStore.prototype._addHistoryEntry = function(url, title, date, onSuccess, onFailure, transaction) {

	var utcNow = date.getUTC().getTime();
	var utcMidnight = date.getUTC().getMidnight().getTime();

	// I hope these operations happen in order. I haven't been able to verify that they
	// will be. If not then we will have to marshal the INSERT parameters to _deleteSuccess
	// so that it can initiate the second call.
	this._executeSql(transaction, function() {}, function(transaction, err) {
			Mojo.Log.error("Error deleting history item: code: %d, message:'%s'.", err.code, err.message);
		},
		"DELETE FROM 'history' WHERE date > ? AND url = ?", [utcMidnight, url] );

	this._executeSql(transaction, onSuccess, onFailure,
		"INSERT INTO 'history' (date, url, title) VALUES(?, ?, ?)", [utcNow, url, title]);
};

/**
 * Add a history entry.
 *
 * @param {String} url
 * @param {String} title
 * @param {Date} date (localtime)
 * @param {function} onSuccess
 * @param {function} onFailure
 */
HistoryStore.prototype.addHistoryEntry = function(url, title, date, onSuccess, onFailure) {

	try	{
		if (!url || (url.length === 0)) {
			onFailure("No URL", null);
			return;
		}
		this.database.transaction(this._addHistoryEntry.bind(this, url, title, date, onSuccess, onFailure));
	} catch (e) {
		Mojo.Log.logException(e);
		onFailure(undefined, e);
	}
};

HistoryStore.prototype._deleteHistoryBefore = function(date, onSuccess, onFailure, transaction) {
	var utc = date;
	if (date instanceof Date) {
		utc = date.getUTC().getTime();
	}

	this._executeSql(transaction, onSuccess, onFailure,
				"DELETE FROM 'history' WHERE date < ?", [utc]);
};

/**
 * Delete all history entries older than the supplied date.
 *
 * @param {Date} date The date to delete all history before. Can be a Date object or a number.
 *                     If it's a date object then the UTC time is used. If it's a number then
 *                     it must be UTC epoch value.
 */
HistoryStore.prototype.deleteHistoryBefore = function(date, onSuccess, onFailure) {

	try	{
		this.database.transaction(this._deleteHistoryBefore.bind(this, date, onSuccess, onFailure));
	} catch (e) {
		Mojo.Log.logException(e, 'deleteHistoryBefore');
		onFailure(undefined, e);
	}
};

/** @private */
HistoryStore.prototype._extractHistory = function(historyList, onSuccess, onFailure, offset, limit, transaction, resultSet) {
	try {

		for (var i = 0; i < resultSet.rows.length; i++) {
			var uriRef = resultSet.rows.item(i);

			var title = uriRef.title || null;
			historyList.addPage(uriRef.url, title, uriRef.date);
		}

		onSuccess(offset, historyList);

	} catch (e) {
		Mojo.Log.logException(e, '_extractHistory');
		onFailure(transaction, {code:0, message: e.message});
	}
};

/** @private */
HistoryStore.prototype._readHistory = function(historyList, filterText, onSuccess, onFailure, transaction, offset, limit) {
	var sql;
	if (filterText) {
		sql = "SELECT * FROM history WHERE (url LIKE '%" + filterText +
			"%') or (title LIKE '%" + filterText + "%') ORDER BY date DESC";
	}
	else {
		sql = "SELECT * FROM 'history'";
	}
	this._executeSql(transaction, this._extractHistory.bind(this, historyList, onSuccess, onFailure, offset, limit), onFailure, sql);
};

/**
 * Read all history from the database.
 *
 * @param {HistoryList} historyList
 * @param {String} filterText
 * @param {function} onSuccess
 * @param {function} onFailure
 */
HistoryStore.prototype.readHistory = function(historyList, filterText, onSuccess, onFailure, offset, limit) {
	this.database.transaction(this._readHistory.bind(this, historyList, filterText, onSuccess, onFailure, offset, limit));
};

/** @private */
HistoryStore.prototype._executeSql = function(transaction, onSuccess, onFailure, sqlString, valuesList) {
	valuesList = valuesList || [];
	onSuccess = onSuccess || function() {};
	onFailure = onFailure || function() {};
	
	try {
   		transaction.executeSql(sqlString, valuesList, onSuccess, onFailure);
	}
	catch (e) {
		onFailure(e, '_executeSql');
		throw e;
	}
};

/** @private */
HistoryStore.prototype._createTable = function(onSuccess, onFailure, transaction) {
	var sql = "CREATE TABLE IF NOT EXISTS 'history' " +
		"('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
		"'date' INTEGER(8) NOT NULL DEFAULT '', 'url' TEXT NOT NULL DEFAULT '', 'title' TEXT NULL DEFAULT NULL)";
	this._executeSql(transaction, onSuccess, onFailure, sql);
};

HistoryStore.prototype.addHistory = function(url, title, date, onSuccess, onFailure) {

	try {
		if (!url || (url.length === 0)) {
			onFailure("No URL", null);
			return;
		}
		
		var utcNow = date.getUTC().getTime();
		var delcmd = "DELETE FROM history WHERE url = ?";
		var inscmd = "INSERT INTO history (date, url, title) VALUES(?, ?, ?)";

		this.database.transaction(function(transaction) {
			onSuccess = onSuccess || function(){};
			onFailure = onFailure || function(){};
			transaction.executeSql(delcmd, [url], onSuccess, onFailure);
			transaction.executeSql(inscmd, [utcNow, url, title], onSuccess, onFailure);
		});

	} catch (e) {

		onFailure(e.message, -1);
	}
};

HistoryStore.prototype.getCount = function(filterText, onSuccess, onFailure) {

	try {
		var count = "History Count";
		var cntcmd = "SELECT COUNT(1) as '" + count + "' FROM history";
		if (filterText && (filterText.length > 0)) {
			cntmd += " WHERE (url LIKE '%" + filterText + "%') or (title LIKE '%" + filterText + "%')";
		}
		
		this.database.transaction(function(transaction){
			onSuccess = onSuccess || function(){};
			onFailure = onFailure || function(){};
			transaction.executeSql(cntcmd, [], function(transaction, results){
				onSuccess(results.rows.item(0)[count]);
			}, function(transaction, error){
				onFailure(error.message, error.code);
			});
		});
	} catch (e) {
		
		onFailure(e.message, -1);
	}
};

HistoryStore.prototype.getHistory = function(historyList, filterText, onSuccess, onFailure, offset, limit) {

	try {
		var sqlcmd;
		if (filterText) {
			sqlcmd = "SELECT * FROM history WHERE (url LIKE '%" + filterText + "%') or (title LIKE '%" + filterText + "%') ORDER BY date DESC";
		} else {
			sqlcmd = "SELECT * FROM history ORDER BY date DESC";
		}

		// Add the limits
		if (limit !== undefined) {
			sqlcmd = sqlcmd + " LIMIT " + limit;
			if (offset !== undefined) {
				sqlcmd = sqlcmd + " OFFSET " + offset;
			}
		}

		this.database.transaction(function(transaction) {

			onSuccess = onSuccess || function(){};
			onFailure = onFailure || function(){};
			transaction.executeSql(sqlcmd,
				[],
				function(transaction, results) {

					for (var i = 0; i < results.rows.length; i++) {
						var uriRef = results.rows.item(i);
						var title = uriRef.title || null;
						historyList.addNewPage(uriRef.url, title, uriRef.date);
					}

					onSuccess(historyList, offset, limit);
				},
				onFailure);
		});

	} catch (e) {
		onFailure(e.message, -1);
	}
};
/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

/**
 * Constructor.
 * 
 * @param {String} url
 * @param {String} title
 * @param {Date} date this reference was created (not last visited)
 */
function UrlReference(url, title, date) {
	this.url = url;				///< The page's URL.
	this.parsedUrl = undefined;	///< Lazily created when needed.
	this.title = title;			///< The page title.
	this.date = date;			///< Date this reference was made
	this.defaultEntry = false;	///< Default entries are those that come from C&C database
	this.iconFile32 = null;		///< Full path to 32x32 pixel icon file
	this.iconFile64 = null;		///< Full path to 64x64 pixel icon file
	this.thumbnailFile = null;	///< full path to page thumbnail fail
	this.visitCount = 0;		///< The number of times this URL is visited.
	this.lastVisited = date;	///< The date this page was last visited.
	if (this.date === undefined) {
		this.date = new Date();	// Now
	}
}

/**
 * Test if a URL is equal to the one in this instance.
 *
 * http://www.foo.com == http://www.foo.com/
 * http://foo.com == http://www.foo.com
 */
UrlReference.prototype.urlRefEquals = function(urlRef) {
	
	try {
		if (urlRef.parsedUrl === undefined) {
			urlRef.parsedUrl = new Poly9.URLParser(urlRef.url);			
		}
		
		if (this.parsedUrl === undefined) {
			this.parsedUrl = new Poly9.URLParser(this.url);
		}
		return UrlUtil.urlParsedEquals(this.parsedUrl, urlRef.parsedUrl);
	}
	catch (e) {
		return false;
	}	
};


/**
 * Test if a URL is equal to the one in this instance.
 *
 * http://www.foo.com == http://www.foo.com/
 * http://foo.com == http://www.foo.com
 */
UrlReference.prototype.urlEquals = function(url) {

	if (url instanceof UrlReference) {
		return this.urlRefEquals(url);
	}

	if (this.url === url) {
		return true;
	}
	try {
		if (this.parsedUrl === undefined) {
			this.parsedUrl = new Poly9.URLParser(this.url);
		}
		return UrlUtil.urlParsedEquals(this.parsedUrl, new Poly9.URLParser(url));
	}
	catch (e) {
		return false;
	}
};


/** 
* @projectDescription 	Poly9's polyvalent URLParser class
*
* @author	Denis Laprise - denis@poly9.com - http://poly9.com
* @version	0.1 
* @namespace	Poly9
*
* Usage: var p = new Poly9.URLParser('http://user:password@poly9.com/pathname?arguments=1#fragment');
* p.getHost() == 'poly9.com';
* p.getProtocol() == 'http';
* p.getPathname() == '/pathname';
* p.getQuerystring() == 'arguments=1';
* p.getFragment() == 'fragment';
* p.getUsername() == 'user';
* p.getPassword() == 'password';
*
* See the unit test file for more examples.
* URLParser is freely distributable under the terms of an MIT-style license.
*/

if (typeof Poly9 == 'undefined')
 var Poly9 = {};

/**
 * Creates an URLParser instance
 *
 * @classDescription	Creates an URLParser instance
 * @return {Object}	return an URLParser object
 * @param {String} url	The url to parse
 * @constructor
 * @exception {String}  Throws an exception if the specified url is invalid
 */
Poly9.URLParser = function(url) {
 this._fields = {'Username' : 5, 'Password' : 6, 'Port' : 7, 'Protocol' : 2, 'Host' : 7, 'Pathname' : 9, 'URL' : 0, 'Querystring' : 10, 'Fragment' : 11};
 this._values = {};
 this._regex = null;
 this.version = 0.1;
 this._regex = /^((\w+):(\/\/)?)?((\w+):?(\w+)?@)?([^\/\?:]+):?(\d+)?(\/?[^\?#]+)?\??([^#]+)?#?(\w*)/;
 for(var f in this._fields)
  this['get' + f] = this._makeGetter(f);
 if (typeof url != 'undefined')
  this._parse(url);
}
 
/**
 * @method 
 * @param {String} url	The url to parse
 * @exception {String} 	Throws an exception if the specified url is invalid
 */
Poly9.URLParser.prototype.setURL = function(url) {
  this._parse(url);
}

Poly9.URLParser.prototype._initValues = function() {
   for(var f in this._fields)
   this._values[f] = '';
}

Poly9.URLParser.prototype._parse = function(url) {
  this._initValues();
  var r = this._regex.exec(url);
  if (!r) throw "DPURLParser::_parse -> Invalid URL"
  for(var f in this._fields) if (typeof r[this._fields[f]] != 'undefined')
   this._values[f] = r[this._fields[f]];
}

Poly9.URLParser.prototype._makeGetter = function(field) {
 return function() {
  return this._values[field];
 }
}

/*
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */

AddressBar = Class.create({

	initialize: function(controller) {
	
		this._controller = controller;
		this._urlBar = new UrlBar(this._controller);
		this._urlSearch = new UrlSearchController(this._controller);
		this._hideTimeout = 0.50;		

		this._invalidFilters = ['ht', 'htt', 'http', 'http:', 'http:/', 'http://'];
	},
	
	setup: function(params) {

		// A filter function to allow a cleaner callback function
		var onFilteredPropertyChange = function(propertyChangeEvent) {
			
			// Take whatever was typed and strip whitespace from either
			// end of the string before passing onto the application and
			// search engines.
			var url = propertyChangeEvent.value;			
			url = url && url.strip();
			var value = {
				value: url,
				type: 'unknown'
			};
			
			if (propertyChangeEvent.originalEvent && propertyChangeEvent.originalEvent.keyCode) {
				
				value.type = 'keyCode';
				value.keyCode =  propertyChangeEvent.originalEvent.keyCode;
				if (propertyChangeEvent.originalEvent.keyCode !== Mojo.Char.enter) {
					if (this.isValidFilterText(url)) {
						this.openSearchResults(url);
					} else {
						this.closeSearchResults();
					}
				}
			} else if (propertyChangeEvent.type === 'blur') {
				
				value.type = 'blur';
			} else if (propertyChangeEvent.type === 'mojo-property-change') {
				
				value.type = 'mojo-property-change';
				if (this.isValidFilterText(url)) {
					this.openSearchResults(url);
				} else {
					this.closeSearchResults();
				}
			}
			
			// Forward onto the caller.			
			params.onPropertyChange(value);
		}.bind(this);

		// Initialize the URL bar and search controllers with
		// the filters versions of the callbacks.
		this._urlBar.setup({
			onPropertyChange: onFilteredPropertyChange,
			orientation: params.orientation,
			visible: params.visible
		});
		
		// Setup the url search controller.
		this._urlSearch.setup({
			onSelect: params.onSelect,
			onEnableSceneScroller: params.onEnableSceneScroller
		});		
	},
	
	isValidFilterText: function(url) {

		if (url === null || url.length < 2) {
			return false;
		}
		
		url = url.toLowerCase();
		if (this._invalidFilters.indexOf(url) !== -1) {
			return false;
		}
		
		return true;	
	},
	
	startObserving: function() {
		
		this._urlBar.startObserving();
	},
	
	stopObserving: function() {
		this._urlBar.stopObserving();
	},
	
	cleanup: function() {
		
	},
	
	getValue: function() {
	
		return this._urlBar.getValue();
	},
	
	hasFocus: function() {
	
		return this._urlBar.hasFocus();
	},
	
	focus: function() {
		
		this._urlBar.focus();
	},
	
	blur: function() {
		
		this._urlBar.blur();
	},
	
	select: function() {
		
		this._urlBar.select();
	},

	setAddressAndTitle: function(address, title) {
		
		this._urlBar.setTitle(title);
		this._urlBar.setUrl(address);
	},
	
	enableTitleView: function() {
		
		this._urlBar.enableTitleView();
	},
	
	enableUrlView: function() {
		
		this._urlBar.enableUrlView();
	},
	
	show: function() {
		
		if (this._hideTimerId) {
			// Clear any timeout for hide. 
			window.clearTimeout(this._hideTimerId);
			// Remove the masking 'hide' function.
			delete this.hide;
			delete this._hideTimerId;
		}

		this._urlBar.show();
	},
	
	isVisible: function() {
		
		return this._urlBar.isVisible();
	},
	
	isInUrlView: function() {
		
		return this._urlBar.isInUrlMode();
	},
	
	hide: function() {
		
		// We are being called so let's mask the hide until
		// we've completed the delayed hide call.
		this.hide = function() {};
		
		// Once the timer is fired then determine what we should
		// actually do
		var hideCallback = function() {

			// Hide both the URL bar and the search list.
			if (!this._urlBar.hasFocus()) {
				this.closeSearchResults();
				this._urlBar.hide();
			}

			// Completed the callback so we remove the masked 'hide'.
			delete this._hideTimerId;
			delete this.hide;
		}.bind(this);
			
		this._hideTimerId = hideCallback.bind(this).delay(this._hideTimeout);
	},
	
	closeSearchResults: function() {
		
		this._clearUrlTypeDelay();
		this._urlSearch.hideSearchList();
	},
	
	openSearchResults: function(url) {
		
		this._clearUrlTypeDelay();
		this._urlSearch.showSearchList();
		this._updateSearchResults(url);
	},
	
	convertInputToUrl: function(url) {
		
		return UrlSearchController.getDefaultUrl(url);
	},
	
	areSearchResultsVisible: function() {
	
		return this._urlSearch.isSearchListVisible();
	},
	
	isAGotoAddressBarEvent: function(event) {
		
		return this._urlBar.isAGotoUrlBarKey(event.keyCode);
	},

	setOrientation: function(orientation) {
		
		this._urlBar.setOrientation(orientation);
		if (orientation !== 'up') {
			this.closeSearchResults();
		}
	},
	
	_clearUrlTypeDelay: function() {
		
		if (this._urlSearchTypeDelayId) {
			window.clearTimeout(this._urlSearchTypeDelayId);
			delete this._urlSearchTypeDelayId;
		}
	},
	
	_updateSearchResults: function(url) {
		
		// Should provide sources of data ?
		this._urlSearchTypeDelayId = this._urlSearch.startFillingList.bind(this._urlSearch).delay(0.25, url);
	},
	
	enableSSLLock: function(enable) {
		this._urlBar.enableSSLLock(enable);	
	}
});

AddressBar.defaultHintText = UrlBar.defaultHintText;
