--- a/net/minecraft/world/inventory/InventoryMenu.java
+++ b/net/minecraft/world/inventory/InventoryMenu.java
@@ -44,9 +_,13 @@
     private static final EquipmentSlot[] SLOT_IDS = new EquipmentSlot[]{EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET};
     public final boolean active;
     private final Player owner;
+    private @javax.annotation.Nullable org.bukkit.craftbukkit.inventory.CraftInventoryView view = null; // CraftBukkit
 
     public InventoryMenu(Inventory playerInventory, boolean active, final Player owner) {
-        super(null, 0, 2, 2);
+        // CraftBukkit start
+        super(null, 0, 2, 2, playerInventory); // CraftBukkit
+        this.setTitle(net.minecraft.network.chat.Component.translatable("container.crafting")); // SPIGOT-4722: Allocate title for player inventory
+        // CraftBukkit end
         this.active = active;
         this.owner = owner;
         this.addResultSlot(owner, 154, 28);
@@ -188,4 +_,54 @@
     protected Player owner() {
         return this.owner;
     }
+
+    // CraftBukkit start
+    @Override
+    public org.bukkit.craftbukkit.inventory.CraftInventoryView getBukkitView() {
+        if (this.view != null) {
+            return this.view;
+        }
+
+        org.bukkit.craftbukkit.inventory.CraftInventoryCrafting inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryCrafting(this.craftSlots, this.resultSlots);
+        this.view = new org.bukkit.craftbukkit.inventory.CraftInventoryView(this.owner.getBukkitEntity(), inventory, this);
+        return this.view;
+    }
+
+    @Override
+    public void forceHeldSlot(final net.minecraft.world.InteractionHand hand) {
+        // If ever needed, a config option for instead synchronizing the full inventory can be added here to call this.sendAllDataToRemote();
+        // Otherwise, only resync the hand slot
+        final int slot = hand == net.minecraft.world.InteractionHand.MAIN_HAND ? this.owner.getInventory().getSelectedSlot() : Inventory.SLOT_OFFHAND;
+        this.forceSlot(this.owner.getInventory(), slot);
+    }
+
+    @Override
+    public void forceHeldSlotAndArmor(final net.minecraft.world.InteractionHand hand) {
+        this.forceHeldSlot(hand);
+
+        final int size = net.minecraft.world.entity.player.Inventory.INVENTORY_SIZE;
+        final net.minecraft.world.entity.player.Inventory inventory = this.owner.getInventory();
+        this.forceSlot(inventory, net.minecraft.world.entity.EquipmentSlot.FEET.getIndex(size));
+        this.forceSlot(inventory, net.minecraft.world.entity.EquipmentSlot.LEGS.getIndex(size));
+        this.forceSlot(inventory, net.minecraft.world.entity.EquipmentSlot.CHEST.getIndex(size));
+        this.forceSlot(inventory, net.minecraft.world.entity.EquipmentSlot.HEAD.getIndex(size));
+    }
+
+    // CraftBukkit end
+
+    // Paper start - utility methods for synchronizing slots usually not synchronized by the container menus
+    public void broadcastNonContainerSlotChanges() {
+        for (int i = RESULT_SLOT; i < ARMOR_SLOT_END; i++) {
+            this.broadcastSlotChange(i);
+        }
+        this.broadcastSlotChange(SHIELD_SLOT);
+    }
+
+    private void broadcastSlotChange(int slot) {
+        ItemStack item = this.slots.get(slot).getItem();
+        java.util.function.Supplier<net.minecraft.world.item.ItemStack> supplier = com.google.common.base.Suppliers.memoize(item::copy);
+        this.triggerSlotListeners(slot, item, supplier);
+        this.synchronizeSlotToRemote(slot, item, supplier);
+    }
+    // Paper end - utility methods for synchronizing slots usually not synchronized by the container menus
 }
