Repository navigation
Core Data: Validate the parsed-blocks cache against registered block types #81809
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,85 @@ | ||
| import { select } from '@wordpress/data'; | ||
| import { store as blocksStore } from '@wordpress/blocks'; | ||
|
|
||
| /** | ||
| * Shared cache of blocks parsed from an entity's `content` string, keyed by | ||
| * `kind:name:id`. Populated both eagerly by the `getEntityRecord` resolver | ||
| * (when the sync manager parses content for transient edits) and lazily by | ||
| * `useEntityBlockEditor`. The stored `content` string acts as a validator so | ||
| * stale entries are discarded when the underlying record changes. | ||
| * Shared cache of blocks parsed from an entity's `content` string. Populated | ||
| * both eagerly by the `getEntityRecord` resolver (when the sync manager | ||
| * parses content for transient edits) and lazily by `useEntityBlockEditor`. | ||
| * | ||
| * A parse result is a function of the content and of the block types | ||
| * registered at the time: a block whose type is not registered yet is | ||
| * dropped or replaced, not kept. The cache is therefore two levels — the | ||
| * outer one keyed by the registered block types list, the inner one by | ||
| * `kind:name:id`, holding the blocks with the `content` string they were | ||
| * parsed from as the remaining validator. A record resolved before the | ||
| * editor's assets have registered the block types — a resolver can run | ||
| * that early — lands under a list that registration then replaces. | ||
| * | ||
| * The outer level is a WeakMap keyed by the list itself: the blocks store | ||
| * memoizes it until a type is added or removed, then produces a new one and | ||
| * drops the old, taking every entry parsed against it with it. Nothing is | ||
| * invalidated by hand and nothing stale is retained. | ||
| */ | ||
| export const parsedBlocksCache = new Map(); | ||
| const caches = new WeakMap(); | ||
|
|
||
| export function getCacheKey( kind, name, id ) { | ||
| function getCacheKey( kind, name, id ) { | ||
| return `${ kind }:${ name }:${ id }`; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the registered block types, from the registry `parse` reads. | ||
| * | ||
| * Block types are registered on the default registry, so the cache is | ||
| * partitioned by that registry's list rather than one from the registry | ||
| * hosting the entities. | ||
| * | ||
| * @return {Array} Registered block types. | ||
| */ | ||
| function getBlockTypes() { | ||
| return select( blocksStore ).getBlockTypes(); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the blocks cached for an entity, if they were parsed from the | ||
| * given content with the block types registered right now. | ||
| * | ||
| * @param {string} kind Entity kind. | ||
| * @param {string} name Entity name. | ||
| * @param {string|number} id Record ID. | ||
| * @param {string} content The record's current `content` string. | ||
| * | ||
| * @return {Array|undefined} The cached blocks, or undefined without a valid | ||
| * entry. | ||
| */ | ||
| export function getCachedBlocks( kind, name, id, content ) { | ||
| const cached = caches | ||
| .get( getBlockTypes() ) | ||
| ?.get( getCacheKey( kind, name, id ) ); | ||
|
|
||
| if ( cached && cached.content === content ) { | ||
| return cached.blocks; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Caches the blocks parsed for an entity, under the block types they were | ||
| * parsed with. | ||
| * | ||
| * @param {string} kind Entity kind. | ||
| * @param {string} name Entity name. | ||
| * @param {string|number} id Record ID. | ||
| * @param {string} content The `content` string the blocks were parsed | ||
| * from. | ||
| * @param {Array} blocks The parsed blocks. | ||
| */ | ||
| export function setCachedBlocks( kind, name, id, content, blocks ) { | ||
| const blockTypes = getBlockTypes(); | ||
| let cache = caches.get( blockTypes ); | ||
|
|
||
| if ( ! cache ) { | ||
| cache = new Map(); | ||
| caches.set( blockTypes, cache ); | ||
| } | ||
|
|
||
| cache.set( getCacheKey( kind, name, id ), { content, blocks } ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; | ||
| import { getCachedBlocks, setCachedBlocks } from '../parsed-blocks-cache'; | ||
|
|
||
| describe( 'parsed blocks cache', () => { | ||
| const content = '<!-- wp:test/block /-->'; | ||
| const blocks = []; | ||
|
|
||
| it( 'returns an entry parsed from the same content with the same block types', () => { | ||
| setCachedBlocks( 'postType', 'page', 1, content, blocks ); | ||
|
|
||
| expect( getCachedBlocks( 'postType', 'page', 1, content ) ).toBe( | ||
| blocks | ||
| ); | ||
| } ); | ||
|
|
||
| it( 'discards an entry when the content changed', () => { | ||
| setCachedBlocks( 'postType', 'page', 2, content, blocks ); | ||
|
|
||
| expect( | ||
| getCachedBlocks( 'postType', 'page', 2, '<!-- wp:test/other /-->' ) | ||
| ).toBeUndefined(); | ||
| } ); | ||
|
|
||
| it( 'discards an entry once block types register, so blocks parsed too early are not served', () => { | ||
| // Parsed and cached while `test/block` was not registered — as happens | ||
| // when a record resolves before the editor's assets have loaded. | ||
| setCachedBlocks( 'postType', 'page', 3, content, blocks ); | ||
|
|
||
| registerBlockType( 'test/block', { | ||
| apiVersion: 3, | ||
| title: 'Test Block', | ||
| category: 'text', | ||
| } ); | ||
|
|
||
| try { | ||
| expect( | ||
| getCachedBlocks( 'postType', 'page', 3, content ) | ||
| ).toBeUndefined(); | ||
| } finally { | ||
| unregisterBlockType( 'test/block' ); | ||
| } | ||
| } ); | ||
| } ); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here an old array returned by
getBlockTypeswill be forever trapped inside the cache, instead of being garbage collected. A better structure would be a two level map:Changing the block types registration immediately invalidates the weakmap record in the cache.