Fixing this will require a design change and it won't be a quick fix. But I think there might be a solution along the following lines.
Firstly, introduce a class such as NamespaceBindingSet representing an immutable set of namespace bindings (possibly a map from prefixes to URIs, but probably not implemented as a general-purpose map). There could be multiple implementations, and the existing NamespaceBinding class could be used to represent a singleton set.
Change the Receiver interface so that the namespace() method accepts a NamespaceBindingSet rather than a NamespaceBinding - that is, it allows multiple namespaces to be passed with a single call.
Change the NodeInfo interface so that instead of (or perhaps as well as) the current getDeclaredNamespaces() (which returns namespace declarations and undeclarations on one particular element), it returns a NamespaceBindingSet containing all the in-scope namespaces for one element.
For the TinyTree and LinkedTree, we could implement this as follows. Let's assume that only a very small proportion of elements contain any namespace declarations. For the vast majority of elements, the NamespaceBindingSet is therefore the same object as for the parent element; the tree as a whole only contains as many NamespaceBindingSet objects as there are elements with namespace declarations, and this means we can probably afford to hold namespaces redundantly across multiple NamespaceBindingSets. However, a NamespaceBindingSet object could also be implemented as a delta relative to another NamespaceBindingSet.
Now xsl:copy for an element can simply pass the relevant NamespaceBindingSet straight down the Receiver pipeline until it hits a NamespaceReducer. The NamespaceReducer should be able to quickly discover that the same NamespaceBindingSet was used by the parent element, and therefore the whole thing can be ignored without picking it apart. Only where the namespaces are different between the child element and the parent is there any need to do any work.
The same approach should also benefit literal result elements, if we make sure that the compiled stylesheet uses common NamespaceBindingSets for literal result elements with the same set of in-scope namespaces.
It's not worth doing any of this if it only benefits pathological cases with 100 namespaces declared. But I think there could also be benefits for routine workloads, say copying a document with five namespaces all declared on the root element.
The main case where the current NodeInfo.getDeclaredNamespaces() is used (without also getting the ancestor namespaces) is for deep copy (xsl:copy-of) where at each level we only pass the namespace differences down the pipeline. We need to make sure there is no regression for this path. One way to do this is if the NamespaceBindingSet is actually maintained as a delta. If the NamespaceReducer calls some method to find the delta between the child namespaces and the parent namespaces, this can have a very efficient implementation for the common case where the NamespaceBindingSet for the child is actually implemented as a delta from the parent.