Are we thinking of different methods? Unless if you override it, Block#getDrops is in the call hierarchy of PlayerInteractionManager#tryHarvestBlock, which also calls Block#removedByPlayer.
The default call hierachy, unless if I'm mistaken, goes:
Block#harvestBlock -> Block#dropBlockAsItem (if not silk touch) -> Block#dropBlockAsItemWithChance -> Block#getDrops
I've checked a bunch of popular mods like Thermal Expansion, Immersive Engineering, and EnderIO, and they all handle their drops in Block#getDrops, so I assumed that was where block drops were handled.
Note, I'm not talking about getting the drops from the tile entity or any container. I am aware of that tile entities are removed in Block#breakBlock, I'm talking about harvesting the block itself.
In mods I checked, if there is a block with a container that retains its contents with NBT data as an item, they would get the block item, add the NBT data to it based on the tile entity's data, and then return it in Block#getDrops. This works because getDrops is called before the tile entity is removed in breakBlock. However, the vanilla shulker box handles its drops entirely in breakBlock, where it generates the dropped shulker box item with NBT data. getDrops is never called because dropBlockAsItemWithChance is overriden.
I'm just wondering if the shulker box implementation is an anomaly or not, since I've never seen it done like that before, and there would be no way to check what it drops without actually simulating the drop itself.