Merge pull request #16920 from Snuffleupagus/annotationGlobals

Slightly reduce asynchronicity when parsing Annotations
This commit is contained in:
Jonas Jenwald 2023-09-09 09:55:49 +02:00 committed by GitHub
commit 18a661b6a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 420 additions and 322 deletions

View File

@ -67,6 +67,35 @@ import { writeObject } from "./writer.js";
import { XFAFactory } from "./xfa/factory.js"; import { XFAFactory } from "./xfa/factory.js";
class AnnotationFactory { class AnnotationFactory {
static createGlobals(pdfManager) {
return Promise.all([
pdfManager.ensureCatalog("acroForm"),
pdfManager.ensureDoc("xfaDatasets"),
pdfManager.ensureCatalog("structTreeRoot"),
// Only necessary to prevent the `Catalog.baseUrl`-getter, used
// with some Annotations, from throwing and thus breaking parsing:
pdfManager.ensureCatalog("baseUrl"),
// Only necessary to prevent the `Catalog.attachments`-getter, used
// with "GoToE" actions, from throwing and thus breaking parsing:
pdfManager.ensureCatalog("attachments"),
]).then(
([acroForm, xfaDatasets, structTreeRoot, baseUrl, attachments]) => {
return {
pdfManager,
acroForm: acroForm instanceof Dict ? acroForm : Dict.empty,
xfaDatasets,
structTreeRoot,
baseUrl,
attachments,
};
},
reason => {
warn(`createGlobals: "${reason}".`);
return null;
}
);
}
/** /**
* Create an `Annotation` object of the correct type for the given reference * Create an `Annotation` object of the correct type for the given reference
* to an annotation dictionary. This yields a promise that is resolved when * to an annotation dictionary. This yields a promise that is resolved when
@ -74,48 +103,33 @@ class AnnotationFactory {
* *
* @param {XRef} xref * @param {XRef} xref
* @param {Object} ref * @param {Object} ref
* @param {PDFManager} pdfManager * @params {Object} annotationGlobals
* @param {Object} idFactory * @param {Object} idFactory
* @param {boolean} collectFields * @param {boolean} [collectFields]
* @param {Object} [pageRef] * @param {Object} [pageRef]
* @returns {Promise} A promise that is resolved with an {Annotation} * @returns {Promise} A promise that is resolved with an {Annotation}
* instance. * instance.
*/ */
static create(xref, ref, pdfManager, idFactory, collectFields, pageRef) { static async create(
return Promise.all([ xref,
pdfManager.ensureCatalog("acroForm"), ref,
// Only necessary to prevent the `pdfManager.docBaseUrl`-getter, used annotationGlobals,
// with certain Annotations, from throwing and thus breaking parsing: idFactory,
pdfManager.ensureCatalog("baseUrl"), collectFields,
// Only necessary in the `Catalog.parseDestDictionary`-method, pageRef
// when parsing "GoToE" actions: ) {
pdfManager.ensureCatalog("attachments"), const pageIndex = collectFields
pdfManager.ensureDoc("xfaDatasets"), ? await this._getPageIndex(xref, ref, annotationGlobals.pdfManager)
collectFields ? this._getPageIndex(xref, ref, pdfManager) : -1, : null;
pageRef ? pdfManager.ensureCatalog("structTreeRoot") : null,
]).then( return annotationGlobals.pdfManager.ensure(this, "_create", [
([ xref,
acroForm, ref,
baseUrl, annotationGlobals,
attachments, idFactory,
xfaDatasets, pageIndex,
pageIndex, pageRef,
structTreeRoot, ]);
]) =>
pdfManager.ensure(this, "_create", [
xref,
ref,
pdfManager,
idFactory,
acroForm,
attachments,
xfaDatasets,
collectFields,
pageIndex,
structTreeRoot,
pageRef,
])
);
} }
/** /**
@ -124,14 +138,9 @@ class AnnotationFactory {
static _create( static _create(
xref, xref,
ref, ref,
pdfManager, annotationGlobals,
idFactory, idFactory,
acroForm, pageIndex = null,
attachments = null,
xfaDatasets,
collectFields,
pageIndex = -1,
structTreeRoot = null,
pageRef = null pageRef = null
) { ) {
const dict = xref.fetchIfRef(ref); const dict = xref.fetchIfRef(ref);
@ -139,6 +148,7 @@ class AnnotationFactory {
return undefined; return undefined;
} }
const { acroForm, pdfManager } = annotationGlobals;
const id = const id =
ref instanceof Ref ? ref.toString() : `annot_${idFactory.createObjId()}`; ref instanceof Ref ? ref.toString() : `annot_${idFactory.createObjId()}`;
@ -146,8 +156,6 @@ class AnnotationFactory {
let subtype = dict.get("Subtype"); let subtype = dict.get("Subtype");
subtype = subtype instanceof Name ? subtype.name : null; subtype = subtype instanceof Name ? subtype.name : null;
const acroFormDict = acroForm instanceof Dict ? acroForm : Dict.empty;
// Return the right annotation object based on the subtype and field type. // Return the right annotation object based on the subtype and field type.
const parameters = { const parameters = {
xref, xref,
@ -155,16 +163,11 @@ class AnnotationFactory {
dict, dict,
subtype, subtype,
id, id,
pdfManager, annotationGlobals,
acroForm: acroFormDict,
attachments,
xfaDatasets,
collectFields,
needAppearances: needAppearances:
!collectFields && acroFormDict.get("NeedAppearances") === true, pageIndex === null && acroForm.get("NeedAppearances") === true,
pageIndex, pageIndex,
evaluatorOptions: pdfManager.evaluatorOptions, evaluatorOptions: pdfManager.evaluatorOptions,
structTreeRoot,
pageRef, pageRef,
}; };
@ -241,7 +244,7 @@ class AnnotationFactory {
return new FileAttachmentAnnotation(parameters); return new FileAttachmentAnnotation(parameters);
default: default:
if (!collectFields) { if (pageIndex === null) {
if (!subtype) { if (!subtype) {
warn("Annotation is missing the required /Subtype."); warn("Annotation is missing the required /Subtype.");
} else { } else {
@ -392,6 +395,7 @@ class AnnotationFactory {
} }
static async printNewAnnotations( static async printNewAnnotations(
annotationGlobals,
evaluator, evaluator,
task, task,
annotations, annotations,
@ -410,18 +414,28 @@ class AnnotationFactory {
switch (annotation.annotationType) { switch (annotation.annotationType) {
case AnnotationEditorType.FREETEXT: case AnnotationEditorType.FREETEXT:
promises.push( promises.push(
FreeTextAnnotation.createNewPrintAnnotation(xref, annotation, { FreeTextAnnotation.createNewPrintAnnotation(
evaluator, annotationGlobals,
task, xref,
evaluatorOptions: options, annotation,
}) {
evaluator,
task,
evaluatorOptions: options,
}
)
); );
break; break;
case AnnotationEditorType.INK: case AnnotationEditorType.INK:
promises.push( promises.push(
InkAnnotation.createNewPrintAnnotation(xref, annotation, { InkAnnotation.createNewPrintAnnotation(
evaluatorOptions: options, annotationGlobals,
}) xref,
annotation,
{
evaluatorOptions: options,
}
)
); );
break; break;
case AnnotationEditorType.STAMP: case AnnotationEditorType.STAMP:
@ -438,10 +452,15 @@ class AnnotationFactory {
image.imageStream = image.smaskStream = null; image.imageStream = image.smaskStream = null;
} }
promises.push( promises.push(
StampAnnotation.createNewPrintAnnotation(xref, annotation, { StampAnnotation.createNewPrintAnnotation(
image, annotationGlobals,
evaluatorOptions: options, xref,
}) annotation,
{
image,
evaluatorOptions: options,
}
)
); );
break; break;
} }
@ -570,7 +589,7 @@ function getTransformMatrix(rect, bbox, matrix) {
class Annotation { class Annotation {
constructor(params) { constructor(params) {
const { dict, xref } = params; const { dict, xref, annotationGlobals } = params;
this.setTitle(dict.get("T")); this.setTitle(dict.get("T"));
this.setContents(dict.get("Contents")); this.setContents(dict.get("Contents"));
@ -598,11 +617,15 @@ class Annotation {
const isLocked = !!(this.flags & AnnotationFlag.LOCKED); const isLocked = !!(this.flags & AnnotationFlag.LOCKED);
const isContentLocked = !!(this.flags & AnnotationFlag.LOCKEDCONTENTS); const isContentLocked = !!(this.flags & AnnotationFlag.LOCKEDCONTENTS);
if (params.structTreeRoot) { if (annotationGlobals.structTreeRoot) {
let structParent = dict.get("StructParent"); let structParent = dict.get("StructParent");
structParent = structParent =
Number.isInteger(structParent) && structParent >= 0 ? structParent : -1; Number.isInteger(structParent) && structParent >= 0 ? structParent : -1;
params.structTreeRoot.addAnnotationIdToPage(params.pageRef, structParent);
annotationGlobals.structTreeRoot.addAnnotationIdToPage(
params.pageRef,
structParent
);
} }
// Expose public properties using a data object. // Expose public properties using a data object.
@ -624,7 +647,7 @@ class Annotation {
noHTML: isLocked && isContentLocked, noHTML: isLocked && isContentLocked,
}; };
if (params.collectFields) { if (params.pageIndex !== null) {
// Fields can act as container for other fields and have // Fields can act as container for other fields and have
// some actions even if no Annotation inherit from them. // some actions even if no Annotation inherit from them.
// Those fields can be referenced by CO (calculation order). // Those fields can be referenced by CO (calculation order).
@ -755,9 +778,11 @@ class Annotation {
} }
setDefaultAppearance(params) { setDefaultAppearance(params) {
const { dict, annotationGlobals } = params;
const defaultAppearance = const defaultAppearance =
getInheritableProperty({ dict: params.dict, key: "DA" }) || getInheritableProperty({ dict, key: "DA" }) ||
params.acroForm.get("DA"); annotationGlobals.acroForm.get("DA");
this._defaultAppearance = this._defaultAppearance =
typeof defaultAppearance === "string" ? defaultAppearance : ""; typeof defaultAppearance === "string" ? defaultAppearance : "";
this.data.defaultAppearanceData = parseDefaultAppearance( this.data.defaultAppearanceData = parseDefaultAppearance(
@ -1634,13 +1659,19 @@ class MarkupAnnotation extends Annotation {
return { ref: annotationRef, data: buffer.join("") }; return { ref: annotationRef, data: buffer.join("") };
} }
static async createNewPrintAnnotation(xref, annotation, params) { static async createNewPrintAnnotation(
annotationGlobals,
xref,
annotation,
params
) {
const ap = await this.createNewAppearanceStream(annotation, xref, params); const ap = await this.createNewAppearanceStream(annotation, xref, params);
const annotationDict = this.createNewDict(annotation, xref, { ap }); const annotationDict = this.createNewDict(annotation, xref, { ap });
const newAnnotation = new this.prototype.constructor({ const newAnnotation = new this.prototype.constructor({
dict: annotationDict, dict: annotationDict,
xref, xref,
annotationGlobals,
evaluatorOptions: params.evaluatorOptions, evaluatorOptions: params.evaluatorOptions,
}); });
@ -1656,7 +1687,7 @@ class WidgetAnnotation extends Annotation {
constructor(params) { constructor(params) {
super(params); super(params);
const { dict, xref } = params; const { dict, xref, annotationGlobals } = params;
const data = this.data; const data = this.data;
this._needAppearances = params.needAppearances; this._needAppearances = params.needAppearances;
@ -1683,12 +1714,13 @@ class WidgetAnnotation extends Annotation {
}); });
data.defaultFieldValue = this._decodeFormValue(defaultFieldValue); data.defaultFieldValue = this._decodeFormValue(defaultFieldValue);
if (fieldValue === undefined && params.xfaDatasets) { if (fieldValue === undefined && annotationGlobals.xfaDatasets) {
// Try to figure out if we have something in the xfa dataset. // Try to figure out if we have something in the xfa dataset.
const path = this._title.str; const path = this._title.str;
if (path) { if (path) {
this._hasValueFromXFA = true; this._hasValueFromXFA = true;
data.fieldValue = fieldValue = params.xfaDatasets.getValue(path); data.fieldValue = fieldValue =
annotationGlobals.xfaDatasets.getValue(path);
} }
} }
@ -1711,7 +1743,7 @@ class WidgetAnnotation extends Annotation {
data.fieldType = fieldType instanceof Name ? fieldType.name : null; data.fieldType = fieldType instanceof Name ? fieldType.name : null;
const localResources = getInheritableProperty({ dict, key: "DR" }); const localResources = getInheritableProperty({ dict, key: "DR" });
const acroFormResources = params.acroForm.get("DR"); const acroFormResources = annotationGlobals.acroForm.get("DR");
const appearanceResources = this.appearance?.dict.get("Resources"); const appearanceResources = this.appearance?.dict.get("Resources");
this._fieldResources = { this._fieldResources = {
@ -3208,22 +3240,20 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
} }
_processPushButton(params) { _processPushButton(params) {
if ( const { dict, annotationGlobals } = params;
!params.dict.has("A") &&
!params.dict.has("AA") && if (!dict.has("A") && !dict.has("AA") && !this.data.alternativeText) {
!this.data.alternativeText
) {
warn("Push buttons without action dictionaries are not supported"); warn("Push buttons without action dictionaries are not supported");
return; return;
} }
this.data.isTooltipOnly = !params.dict.has("A") && !params.dict.has("AA"); this.data.isTooltipOnly = !dict.has("A") && !dict.has("AA");
Catalog.parseDestDictionary({ Catalog.parseDestDictionary({
destDict: params.dict, destDict: dict,
resultObj: this.data, resultObj: this.data,
docBaseUrl: params.pdfManager.docBaseUrl, docBaseUrl: annotationGlobals.baseUrl,
docAttachments: params.attachments, docAttachments: annotationGlobals.attachments,
}); });
} }
@ -3581,9 +3611,10 @@ class LinkAnnotation extends Annotation {
constructor(params) { constructor(params) {
super(params); super(params);
const { dict, annotationGlobals } = params;
this.data.annotationType = AnnotationType.LINK; this.data.annotationType = AnnotationType.LINK;
const quadPoints = getQuadPoints(params.dict, this.rectangle); const quadPoints = getQuadPoints(dict, this.rectangle);
if (quadPoints) { if (quadPoints) {
this.data.quadPoints = quadPoints; this.data.quadPoints = quadPoints;
} }
@ -3592,10 +3623,10 @@ class LinkAnnotation extends Annotation {
this.data.borderColor ||= this.data.color; this.data.borderColor ||= this.data.color;
Catalog.parseDestDictionary({ Catalog.parseDestDictionary({
destDict: params.dict, destDict: dict,
resultObj: this.data, resultObj: this.data,
docBaseUrl: params.pdfManager.docBaseUrl, docBaseUrl: annotationGlobals.baseUrl,
docAttachments: params.attachments, docAttachments: annotationGlobals.attachments,
}); });
} }
} }

View File

@ -310,7 +310,7 @@ class Catalog {
Catalog.parseDestDictionary({ Catalog.parseDestDictionary({
destDict: outlineDict, destDict: outlineDict,
resultObj: data, resultObj: data,
docBaseUrl: this.pdfManager.docBaseUrl, docBaseUrl: this.baseUrl,
docAttachments: this.attachments, docAttachments: this.attachments,
}); });
const title = outlineDict.get("Title"); const title = outlineDict.get("Title");
@ -1405,7 +1405,7 @@ class Catalog {
} }
} }
} }
return shadow(this, "baseUrl", null); return shadow(this, "baseUrl", this.pdfManager.docBaseUrl);
} }
/** /**
@ -1423,19 +1423,16 @@ class Catalog {
* Helper function used to parse the contents of destination dictionaries. * Helper function used to parse the contents of destination dictionaries.
* @param {ParseDestDictionaryParameters} params * @param {ParseDestDictionaryParameters} params
*/ */
static parseDestDictionary(params) { static parseDestDictionary({
const destDict = params.destDict; destDict,
resultObj,
docBaseUrl = null,
docAttachments = null,
}) {
if (!(destDict instanceof Dict)) { if (!(destDict instanceof Dict)) {
warn("parseDestDictionary: `destDict` must be a dictionary."); warn("parseDestDictionary: `destDict` must be a dictionary.");
return; return;
} }
const resultObj = params.resultObj;
if (typeof resultObj !== "object") {
warn("parseDestDictionary: `resultObj` must be an object.");
return;
}
const docBaseUrl = params.docBaseUrl || null;
const docAttachments = params.docAttachments || null;
let action = destDict.get("A"), let action = destDict.get("A"),
url, url,

View File

@ -426,9 +426,12 @@ class Page {
let newAnnotationsPromise = Promise.resolve(null); let newAnnotationsPromise = Promise.resolve(null);
if (newAnnotationsByPage) { if (newAnnotationsByPage) {
let imagePromises;
const newAnnotations = newAnnotationsByPage.get(this.pageIndex); const newAnnotations = newAnnotationsByPage.get(this.pageIndex);
if (newAnnotations) { if (newAnnotations) {
const annotationGlobalsPromise =
this.pdfManager.ensureDoc("annotationGlobals");
let imagePromises;
// An annotation can contain a reference to a bitmap, but this bitmap // An annotation can contain a reference to a bitmap, but this bitmap
// is defined in another annotation. So we need to find this annotation // is defined in another annotation. So we need to find this annotation
// and generate the bitmap. // and generate the bitmap.
@ -467,11 +470,21 @@ class Page {
deletedAnnotations = new RefSet(); deletedAnnotations = new RefSet();
this.#replaceIdByRef(newAnnotations, deletedAnnotations, null); this.#replaceIdByRef(newAnnotations, deletedAnnotations, null);
newAnnotationsPromise = AnnotationFactory.printNewAnnotations(
partialEvaluator, newAnnotationsPromise = annotationGlobalsPromise.then(
task, annotationGlobals => {
newAnnotations, if (!annotationGlobals) {
imagePromises return null;
}
return AnnotationFactory.printNewAnnotations(
annotationGlobals,
partialEvaluator,
task,
newAnnotations,
imagePromises
);
}
); );
} }
} }
@ -663,7 +676,7 @@ class Page {
async getAnnotationsData(handler, task, intent) { async getAnnotationsData(handler, task, intent) {
const annotations = await this._parsedAnnotations; const annotations = await this._parsedAnnotations;
if (annotations.length === 0) { if (annotations.length === 0) {
return []; return annotations;
} }
const annotationsData = [], const annotationsData = [],
@ -723,16 +736,25 @@ class Page {
} }
get _parsedAnnotations() { get _parsedAnnotations() {
const parsedAnnotations = this.pdfManager const promise = this.pdfManager
.ensure(this, "annotations") .ensure(this, "annotations")
.then(() => { .then(async annots => {
if (annots.length === 0) {
return annots;
}
const annotationGlobals =
await this.pdfManager.ensureDoc("annotationGlobals");
if (!annotationGlobals) {
return [];
}
const annotationPromises = []; const annotationPromises = [];
for (const annotationRef of this.annotations) { for (const annotationRef of annots) {
annotationPromises.push( annotationPromises.push(
AnnotationFactory.create( AnnotationFactory.create(
this.xref, this.xref,
annotationRef, annotationRef,
this.pdfManager, annotationGlobals,
this._localIdFactory, this._localIdFactory,
/* collectFields */ false, /* collectFields */ false,
this.ref this.ref
@ -743,34 +765,28 @@ class Page {
); );
} }
return Promise.all(annotationPromises).then(function (annotations) { const sortedAnnotations = [];
if (annotations.length === 0) { let popupAnnotations;
return annotations; // Ensure that PopupAnnotations are handled last, since they depend on
// their parent Annotation in the display layer; fixes issue 11362.
for (const annotation of await Promise.all(annotationPromises)) {
if (!annotation) {
continue;
} }
if (annotation instanceof PopupAnnotation) {
(popupAnnotations ||= []).push(annotation);
continue;
}
sortedAnnotations.push(annotation);
}
if (popupAnnotations) {
sortedAnnotations.push(...popupAnnotations);
}
const sortedAnnotations = []; return sortedAnnotations;
let popupAnnotations;
// Ensure that PopupAnnotations are handled last, since they depend on
// their parent Annotation in the display layer; fixes issue 11362.
for (const annotation of annotations) {
if (!annotation) {
continue;
}
if (annotation instanceof PopupAnnotation) {
(popupAnnotations ||= []).push(annotation);
continue;
}
sortedAnnotations.push(annotation);
}
if (popupAnnotations) {
sortedAnnotations.push(...popupAnnotations);
}
return sortedAnnotations;
});
}); });
return shadow(this, "_parsedAnnotations", parsedAnnotations); return shadow(this, "_parsedAnnotations", promise);
} }
get jsActions() { get jsActions() {
@ -1695,10 +1711,7 @@ class PDFDocument {
: clearGlobalCaches(); : clearGlobalCaches();
} }
/** #collectFieldObjects(name, fieldRef, promises, annotationGlobals) {
* @private
*/
_collectFieldObjects(name, fieldRef, promises) {
const field = this.xref.fetchIfRef(fieldRef); const field = this.xref.fetchIfRef(fieldRef);
if (field.has("T")) { if (field.has("T")) {
const partName = stringToPDFString(field.get("T")); const partName = stringToPDFString(field.get("T"));
@ -1712,22 +1725,21 @@ class PDFDocument {
AnnotationFactory.create( AnnotationFactory.create(
this.xref, this.xref,
fieldRef, fieldRef,
this.pdfManager, annotationGlobals,
this._localIdFactory, this._localIdFactory,
/* collectFields */ true, /* collectFields */ true,
/* pageRef */ null /* pageRef */ null
) )
.then(annotation => annotation?.getFieldObject()) .then(annotation => annotation?.getFieldObject())
.catch(function (reason) { .catch(function (reason) {
warn(`_collectFieldObjects: "${reason}".`); warn(`#collectFieldObjects: "${reason}".`);
return null; return null;
}) })
); );
if (field.has("Kids")) { if (field.has("Kids")) {
const kids = field.get("Kids"); for (const kid of field.get("Kids")) {
for (const kid of kids) { this.#collectFieldObjects(name, kid, promises, annotationGlobals);
this._collectFieldObjects(name, kid, promises);
} }
} }
} }
@ -1737,29 +1749,41 @@ class PDFDocument {
return shadow(this, "fieldObjects", Promise.resolve(null)); return shadow(this, "fieldObjects", Promise.resolve(null));
} }
const allFields = Object.create(null); const promise = this.pdfManager
const fieldPromises = new Map(); .ensureDoc("annotationGlobals")
for (const fieldRef of this.catalog.acroForm.get("Fields")) { .then(async annotationGlobals => {
this._collectFieldObjects("", fieldRef, fieldPromises); if (!annotationGlobals) {
} return null;
}
const allPromises = []; const allFields = Object.create(null);
for (const [name, promises] of fieldPromises) { const fieldPromises = new Map();
allPromises.push( for (const fieldRef of this.catalog.acroForm.get("Fields")) {
Promise.all(promises).then(fields => { this.#collectFieldObjects(
fields = fields.filter(field => !!field); "",
if (fields.length > 0) { fieldRef,
allFields[name] = fields; fieldPromises,
} annotationGlobals
}) );
); }
}
return shadow( const allPromises = [];
this, for (const [name, promises] of fieldPromises) {
"fieldObjects", allPromises.push(
Promise.all(allPromises).then(() => allFields) Promise.all(promises).then(fields => {
); fields = fields.filter(field => !!field);
if (fields.length > 0) {
allFields[name] = fields;
}
})
);
}
await Promise.all(allPromises);
return allFields;
});
return shadow(this, "fieldObjects", promise);
} }
get hasJSActions() { get hasJSActions() {
@ -1809,6 +1833,14 @@ class PDFDocument {
} }
return shadow(this, "calculationOrderIds", ids); return shadow(this, "calculationOrderIds", ids);
} }
get annotationGlobals() {
return shadow(
this,
"annotationGlobals",
AnnotationFactory.createGlobals(this.pdfManager)
);
}
} }
export { Page, PDFDocument }; export { Page, PDFDocument };

View File

@ -16,7 +16,6 @@
import { import {
createValidAbsoluteUrl, createValidAbsoluteUrl,
FeatureTest, FeatureTest,
shadow,
unreachable, unreachable,
warn, warn,
} from "../shared/util.js"; } from "../shared/util.js";
@ -62,8 +61,7 @@ class BasePdfManager {
} }
get docBaseUrl() { get docBaseUrl() {
const catalog = this.pdfDocument.catalog; return this._docBaseUrl;
return shadow(this, "docBaseUrl", catalog.baseUrl || this._docBaseUrl);
} }
ensureDoc(prop, args) { ensureDoc(prop, args) {

File diff suppressed because it is too large Load Diff