We had a rule on Chromium that we simply did not do this — the costs (technical and political) of maintaining a fork of an upstream dependency were simply too high for us to accept.
Instead, we worked with upstream to try and find some type of compromise. Sometimes this involved modifying the work we wanted to do, or doing more work than we alone needed (e.g., to make our change work in additional scenarios that mattered to upstream but not us).
Sometimes it involved creating abstraction layers upstream such that we could plug in our functionality on the Chromium side without disturbing upstream. One good example of this was networking: WebKit was originally implemented to directly rely upon a network stack that was linked into the binary. But in Chromium we wanted to rely on a network stack in a different process via IPC. The compromise we arrived at was to add an abstraction layer that allowed the network stack to be plugged in at runtime. In Chromium, we plugged in our IPC-based implementation.