docs: function generateRelatedLinks(serviceRegistry, item, exclusions, commandRegistry, alternateItem) {
  		var globalArguments = arguments;
  		var contentTypesCache;
  
  		function getContentTypes() {
  			if (contentTypesCache) {
  				return contentTypesCache;
  			}
  			var contentTypeService = serviceRegistry.getService("orion.core.contentTypeRegistry"); //$NON-NLS-0$
  			// TODO Shouldn't really be making service selection decisions at this level. See bug 337740
  			if (!contentTypeService) {
  				contentTypeService = new mContentTypes.ContentTypeRegistry(serviceRegistry);
  				contentTypeService = serviceRegistry.getService("orion.core.contentTypeRegistry"); //$NON-NLS-0$
  			}
  			return contentTypeService.getContentTypes().then(function (ct) {
  				contentTypesCache = ct;
  				return contentTypesCache;
  			});
  		}
  		function getServiceProperties(serviceReference) {
  			var info = {}, keys = serviceReference.getPropertyKeys(), key;
  			for (var i=0; i < keys.length; i++) {
  				key = keys[i];
  				info[key] = serviceReference.getProperty(key);
  			}
  			return info;
  		}
  		function getNavCommandRef(navCommandsRefs, id) {
  			var commandRef = null;
  			navCommandsRefs.some(function(ref) {
  				if (id === ref.getProperty("id")) { //$NON-NLS-0$
  					return !!(commandRef = ref);
  				}
  				return false;
  			});
  			return commandRef;
  		}
  		var alternateItemDeferred;
  		/**
  		 * Creates a CommandInvocation for given commandItem.
  		 * @returns {orion.Promise} A promise resolving to: commandItem with its "invocation" field set, or undefined if we failed
  		 */
  		function setInvocation(commandItem) {
  			var command = commandItem.command;
  			if (!command.visibleWhen || command.visibleWhen(item)) {
  				commandItem.invocation = new mCommands.CommandInvocation(item, item, null, command, commandRegistry);
  				return new Deferred().resolve(commandItem);
  			} else if (typeof alternateItem === "function") {
  				if (!alternateItemDeferred) {
  					alternateItemDeferred = alternateItem();
  				}
  				return Deferred.when(alternateItemDeferred, function (newItem) {
  					if (newItem && (item === pageItem)) {
  						// there is an alternate, and it still applies to the current page target
  						if (!command.visibleWhen || command.visibleWhen(newItem)) {
  							commandItem.invocation = new mCommands.CommandInvocation(newItem, newItem, null, command, commandRegistry);
  							return commandItem;
  						}
  					}
  				});
  			}
  			return new Deferred().resolve();
  		}
  		/**
  		 * @returns {orion.Promise} resolving to a commandItem { relatedLink: Object, command: Command}
  		*/
  		function commandItem(relatedLink, commandOptionsPromise, command) {
  			if (command) {
  				return new Deferred().resolve({ relatedLink: relatedLink, command: command});
  			}
  			return commandOptionsPromise.then(function(commandOptions) {
  				return { relatedLink: relatedLink, command: new mCommands.Command(commandOptions) };
  			});
  		}
  
  		var contributedLinks = serviceRegistry && serviceRegistry.getServiceReferences("orion.page.link.related"); //$NON-NLS-0$
  		if (!contributedLinks || contributedLinks.length === 0) {
  			return;
  		}
  
  		var thisGlobalCommands = this;
  		Deferred.when(getContentTypes(), function () {
  			if (!customGlobalCommands.beforeGenerateRelatedLinks.apply(thisGlobalCommands, globalArguments)) {
  				return;
  			}
  
  			// assemble the related links.
  			var navCommands = serviceRegistry.getServiceReferences("orion.navigate.command"); //$NON-NLS-0$
  			var deferredCommandItems = [];
  			contributedLinks.forEach(function(contribution) {
  				var info = getServiceProperties(contribution);
  				if (!info.id) {
  					return; // skip
  				}
  				// First see if we have a uriTemplate and name, which is enough to build a command internally.
  				var commandOptionsPromise;
  				if (((info.nls && info.nameKey) || info.name) && info.uriTemplate) {
  					commandOptionsPromise = mExtensionCommands._createCommandOptions(info, contribution, serviceRegistry, contentTypesCache, true);
  					deferredCommandItems.push(commandItem(info, commandOptionsPromise, null));
  					return;
  				}
  				// Otherwise, check if this related link references an orion.navigate.command contribution
  				var navRef = getNavCommandRef(navCommands, info.id);
  				if (!navRef)
  					return; // skip: no nav command
  
  				// Build relatedLink info by merging the 2 contributions. info has "id", "category"; navInfo has everything else
  				var navInfo = getServiceProperties(navRef), relatedLink = {};
  				objects.mixin(relatedLink, navInfo, info); // {} <- navInfo <- info
  				var command;
  				if ((command = commandRegistry.findCommand(info.id))) {
  					// A Command exists already, use it
  					deferredCommandItems.push(commandItem(relatedLink, null, command));
  				} else {
  					// Create a new Command for the nav contribution
  					commandOptionsPromise = mExtensionCommands._createCommandOptions(navInfo, navRef, serviceRegistry, contentTypesCache, true);
  					deferredCommandItems.push(commandItem(relatedLink, commandOptionsPromise, null));
  				}
  			});
  
  			function continueOnError(error) {
  				return error;
  			}
  
  			Deferred.all(deferredCommandItems, continueOnError).then(function(commandItems) {
  				commandItems.sort(function(a, b) {
  					return a.command.name.localeCompare(b.command.name);
  				});
  				return Deferred.all(commandItems.map(setInvocation), continueOnError).then(function(invoked) {
  					var nonInvoked = [];
  					// Filter out any holes caused by setInvocation() failing
  					invoked = invoked.filter(function(item, i) {
  						if (item) {
  							return true;
  						}
  						nonInvoked.push(commandItems[i]);
  						return false;
  					});
  					// Finally pass the relatedLinks data to customGlobalCommands
  					customGlobalCommands.addRelatedLinkCommands.call(thisGlobalCommands, commandRegistry, invoked, nonInvoked, exclusions);
  					customGlobalCommands.afterGenerateRelatedLinks.apply(thisGlobalCommands, globalArguments);
  				});
  			});
  		});
  	}