registry::remove released the plug-in set before it moved the revision, as install does not. A removal that finished just before another thread took the set could still move the revision while that thread held it, which made an_install_changes_the_revision_only_while_it_holds_the_set fail intermittently.
A settings get no longer reads and parses settings.json each time. The
parsed settings are kept per plug-in folder and read again only when the
file's size or modified time changes. A set to the value a key already
has writes nothing, and every other change is written with an atomic
write before the call returns. plugin.remove drops the kept copy.
cacheBytes in plugin.list, plugin.info and plugin.install comes from a
total kept while the cache folder keeps its modified time, instead of a
listing of up to 8,192 items per call. A cache write still lists the
folder to enforce the caps.
docs/plugins.md states what a run counts and that a cache call moves a
whole entry in one step. docs/plugins.md and docs/mcp.md now say when a
later plugin.remove retries a leftover .removed folder.
A TIFF with more strips and planes than the decoder can count comes to a filter as "unknown"
with no pixels, instead of panicking in the metadata read. An output that writes a read-only
object's id as 17.0 now names that object, as other numbers in the object compare by value; a
fractional id still names no object.
Building a type input's runs and paragraphs stops at the first one past what is left of the text
and input totals, instead of building all of them before the size check.
A CMYK image's pixels are now its inks shown through the working CMYK profile, by a table built
the way the canvas builds its own, instead of the image decoder's own conversion to RGB.
An output lists at most 100,000 objects and must return every read-only object, so an input with
more than that many is now an object-budget limit before the plug-in starts.
A path or compound path that doesn't fit what is left of the input now fails the run with the
runtime's own "selection is too large to hand over" limit before the rest of the selection is
written, instead of later read-only objects reporting a size error. Objects that clip a clip
group are left out for every filter: compound paths and paths inside a group that clips one
reached filters before, as type, images and symbol instances did.
A piece linked to a global swatch is drawn in the swatch's current color at its own tint, and kept art and rasters follow a color or spot edit and its Undo. A value of `paintOutput` other than true is refused by a translated error. Tests in the effects crate pass the new document argument.
An import's instruction and time budgets are now host limits on Limits, as a heavy filter's are, so a test can give an importer a small budget. An importer past its instruction or time limit, and a file past the input limit, return their error and leave an open document and its undo history unchanged. The manifest fuzz varies extensions (missing, dotted, mixed-case, repeated, too long and too many).
A plug-in of the wrong kind now gets "is a plug-in of kind `exporter`, not an importer", with rows in every complete-message catalog. The importer failure test also covers an open, edited document. docs/plugins.md states that Open Recent does not remember Open With's importer or parameters.
The reference check now applies wherever a painted effect's geometry is
used: a result whose swatch or pattern is refused keeps its input instead
of giving its geometry. Pathfinder art of type, reshaped groups, compound
shapes, image fills, the renderer's bounds and Object > Expand look the
references up in the document, and the renderer drops its kept bounds and
geometry when the names of the swatches or patterns change.
A cache keyed by registry::revision could read the new revision before
the plug-in was in the set and keep the old list under it, so File > Open
and document.formats left out a newly installed importer until the next
install or removal. The revision now changes while install holds the
write lock, and a reader that sees it waits for the set.
A grant records the module's SHA-256, but only its permissions were
compared, so a module with other bytes under a granted id inherited the
grant when it listed no new permission.
A grant now covers a module only when the id, the SHA-256 and every
listed permission match. plugin.inspect reports needsApproval for a
changed module, plugin.install refuses it without a new grant and leaves
the installed module and its grant as they are, Install Plug-in and
File > Open ask again, and the plug-in store and plug-in folders skip it.
The same bytes install again without asking, and a module with no
permissions list or an empty one needs no grant. A grant whose hash is
not 64 lower-case hexadecimal digits covers nothing.
plugin.install still trusts the client that sends grant; the docs say so.
A key that a paint does not have, such as `opacity` on a solid fill, was
dropped when the paint was read, and the fill was drawn opaque. The
result is now refused and `lastError` names the field, for keys on the
paint and on its colors, gradient, stops, placement and freeform points.
A filter's input is now encoded within the half of the plug-in's memory
cap that the input and the parameters share. Every object counts toward
it in paint order, with its id, bounds, transform and name. Type runs
and image pixels that don't fit what is left come with truncated or
pixelsOmitted, and a type, image or symbol instance whose other fields
alone don't fit fails the run before the plug-in starts with
"memory budget (object N needs S bytes of input, L left)".
A filter manifest that lists `inputs` must now list `doc.read` in
`permissions`, so installing it asks the user to grant them and the
Additional Plug-ins Folder loads it only once granted. Left out, the
manifest does not install.
The pixels of all images in one filter input take at most 16 MiB of
base64, and the runs and paragraphs of all its type at most 16 MiB of
JSON. An image whose pixels do not fit what is left comes with
`pixelsOmitted`, checked before it is decoded, and type that does not
fit comes with `truncated`. The read-only objects are kept once and
written into the input from there.
A field a filter adds to a read-only object is named in the refusal
with control and bidirectional characters escaped, cut to 64
characters.
A filter manifest at `api` 2 may list `inputs` (`type`, `image`,
`symbolInstance`). Such a filter is also given the selected objects of
those kinds, read only: type with its runs and paragraph attributes,
images with their pixel size, color mode and, when embedded and small
enough, their RGBA pixels in base64 (never a linked file's pixels or
path), and symbol instances with their symbol and transform.
The output must return each read-only object as {"id": ...} alone or
exactly as given. A change, an omission or a duplicate is refused
before the edit, so the document and undo history stay as they were,
including for output handed in from a heavy run. A filter without
`inputs` gets the same input bytes as before.
A manifest that lists `inputs` below `api` 2, outside a filter, with an
unknown or repeated kind, or with a permission list that lacks
`doc.read` does not install.
Part of #4333
A live effect whose manifest has "api": 2 and "paintOutput": true returns
{"objects": [...]} as pieces, each drawn by itself in order with fills and
strokes of its own in the forms a filter returns. A piece without fills or
strokes keeps the ones the effect was given, and an empty list paints none.
An effect on a fill returns only fills, one on a stroke only strokes.
The whole result is checked before any of it is drawn: the output limits,
finite numbers in range with nothing clamped, known keys only, and every
swatch and pattern it names in the document. One fault draws that step of
the stack unchanged, keeps the effect record and puts the plug-in's name
and the field in plugin.info's lastError. Each piece counts against the
geometry-effect budget, so the painted effects of one stack make at most
100,000 pieces.
The pieces are drawn, measured and exported to PDF and SVG from the
appearance stack without changing the document, and Expand Appearance
writes them as ordinary paths in one undo step. The plug-in API level
rises to 2; plugin.list and plugin.info give api and paintOutput. Effects
without the key behave as before.
An importer plug-in (kind "importer", permission import.read) lists 1 to 8
file extensions and turns a file's bytes into objects, which open as a new
document whose artboard is their bounds. File › Open lists its extensions,
File › Open With (file.openWith) opens a file with a chosen importer, and
document.open takes importer and params. A built-in format that reads the
file by its content or extension always opens it; among importers listing
the same extension, the one whose id sorts first opens it. An importer reads
at most 128 MiB, and Place still reads only the built-in formats.
Docs: docs/plugins.md, docs/mcp.md, docs/gaps.md, docs/target-app-parity.md.
wasmi pauses a run before an instruction the store's fuel can't pay for
and charges nothing for it. A memory.fill or memory.copy is charged for
its whole length, so a pause can leave up to that cost in the store.
Plugin::call replaced the store's fuel with the next slice and compared
the instruction's cost with the rest of the budget alone, so a run whose
budget covered its work could end with Limit("instruction budget").
Each pause now tops the store up to the slice size, keeping what it
held, and ends the run only when the store's fuel and the rest of the
budget together can't pay for the next instruction.