Skip to content
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

feat(): support aborting promises/requests #7827

Merged
merged 57 commits into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
735e854
fix(loadFromJSON): clear canvas
ShaMan123 Mar 23, 2022
cb13c0c
Update canvas_serialization.mixin.js
ShaMan123 Mar 23, 2022
d927c7e
feat(util): support aborting `loadImage`
ShaMan123 Mar 23, 2022
47e98f4
refactor `renderOnAddRemove`
ShaMan123 Mar 23, 2022
172d550
Update misc.js
ShaMan123 Mar 23, 2022
65e6984
parallel promise
ShaMan123 Mar 24, 2022
b8f0ada
live namespace
ShaMan123 Mar 24, 2022
c02b917
enliven options
ShaMan123 Mar 24, 2022
c1fdfd4
Update misc.js
ShaMan123 Mar 24, 2022
cc0539d
Update image.class.js
ShaMan123 Mar 24, 2022
3099a65
Update pattern.class.js
ShaMan123 Mar 24, 2022
f46d833
aborting
ShaMan123 Mar 24, 2022
54b01e5
aborting svg parsing
ShaMan123 Mar 24, 2022
5857859
bump node to support AbortController
ShaMan123 Mar 24, 2022
4ffefd6
listen to abort once
ShaMan123 Mar 24, 2022
aaf3f5b
bump node tests
ShaMan123 Mar 24, 2022
da720f5
lint + JSDOC
ShaMan123 Mar 24, 2022
82c4e9e
Update parser.js
ShaMan123 Mar 24, 2022
b1470dd
feat(Image): abort `setSrc`
ShaMan123 Mar 24, 2022
20e8e87
Update .eslintrc.json
ShaMan123 Mar 24, 2022
ea049c9
Update canvas_serialization.mixin.js
ShaMan123 Mar 24, 2022
92f5336
remove `resolveNamespace`
ShaMan123 Mar 24, 2022
7510ab1
Update pattern.class.js
ShaMan123 Mar 24, 2022
0565006
Update misc.js
ShaMan123 Mar 24, 2022
88b3f0c
consolidate options object
ShaMan123 Mar 24, 2022
3e0f7b6
abort image filter loading
ShaMan123 Mar 24, 2022
9f82c17
fire `loading:aborted` event
ShaMan123 Mar 24, 2022
bb26215
Update object.class.js
ShaMan123 Mar 24, 2022
b3a0ea7
disposing after aborting
ShaMan123 Mar 24, 2022
fc55713
remove DOMException
ShaMan123 Mar 24, 2022
e4d3882
Update canvas_static.js
ShaMan123 Mar 24, 2022
131513c
Update image.class.js
ShaMan123 Mar 24, 2022
32ec3c5
Update canvas_static.js
ShaMan123 Mar 24, 2022
a21a4a4
fix(): concurrency error
ShaMan123 Mar 24, 2022
c9713b1
Update misc.js
ShaMan123 Mar 24, 2022
53b4c5d
tests + concurrency test
ShaMan123 Mar 24, 2022
2e4b7d4
removeListener on error
ShaMan123 Mar 24, 2022
1081460
Update misc.js
ShaMan123 Mar 24, 2022
8c4cd91
bump node version
ShaMan123 Mar 24, 2022
3d38254
Update dom_request.js
ShaMan123 Mar 24, 2022
a3030dc
bump node version
ShaMan123 Mar 24, 2022
19b5ea5
Update image.class.js
ShaMan123 Mar 25, 2022
13c6940
Update image.js
ShaMan123 Mar 25, 2022
639223e
bump node to v16
ShaMan123 Mar 25, 2022
ea8f234
Merge branch 'master' into fix-json-load
ShaMan123 May 1, 2022
13c0056
Merge branch 'master' into fix-json-load
ShaMan123 Jun 26, 2022
54e1c0e
typo
ShaMan123 Jun 26, 2022
fd6573e
rename
ShaMan123 Jun 26, 2022
1ff31f1
rename
ShaMan123 Jun 26, 2022
4f5b6e2
incoming signal **ONLY** + revert node to v14
ShaMan123 Jun 28, 2022
52ba6fb
missed out
ShaMan123 Jun 28, 2022
ec86828
tests
ShaMan123 Jun 28, 2022
a61e3e6
Update canvas_serialization.mixin.js
ShaMan123 Jun 28, 2022
b15bea1
conform rejection object
ShaMan123 Jun 28, 2022
db16268
Update canvas_serialization.mixin.js
ShaMan123 Jun 28, 2022
efaf0b4
restore config for tests
ShaMan123 Jun 28, 2022
06a1d62
revert node v
ShaMan123 Jun 28, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"Buffer": true,
"process": true,
"QUnit": true,
"assert": true
"assert": true,
"AbortController": true
},
"rules": {
"semi": 2,
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/node-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [14.x, 16.x]
node-version: [16.x]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a bit extreme for my taste, node 14 is supported up to apr 2023 in general, getting rid of it way before seems bad from a support point of view.

Copy link
Contributor Author

@ShaMan123 ShaMan123 Jun 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if AbortController is 15.4 and up, we can remove 14 and keep 16 only. We do odd numbers just when they are the latest

Originally posted by @asturur in #7827 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep i found my comments later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://www.npmjs.com/package/abortcontroller-polyfill

Look fine.
And polyfills fetch as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaving tests on v16 because they use AbortController

# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/visual-node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [14.x, 16.x]
node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
Expand Down
8 changes: 5 additions & 3 deletions src/filters/blendimage_filter.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,13 @@
/**
* Create filter instance from an object representation
* @static
* @param {Object} object Object to create an instance from
* @param {object} object Object to create an instance from
* @param {object} [options]
* @param {AbortSignal} [options.signal] handle aborting image loading, see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
* @returns {Promise<fabric.Image.filters.BlendImage>}
*/
fabric.Image.filters.BlendImage.fromObject = function(object) {
return fabric.Image.fromObject(object.image).then(function(image) {
fabric.Image.filters.BlendImage.fromObject = function(object, options) {
return fabric.Image.fromObject(object.image, options).then(function(image) {
return new fabric.Image.filters.BlendImage(Object.assign({}, object, { image: image }));
});
};
Expand Down
9 changes: 7 additions & 2 deletions src/filters/composed_filter.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,16 @@

/**
* Deserialize a JSON definition of a ComposedFilter into a concrete instance.
* @static
* @param {oject} object Object to create an instance from
* @param {object} [options]
* @param {AbortSignal} [options.signal] handle aborting `BlendImage` filter loading, see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
* @returns {Promise<fabric.Image.filters.Composed>}
*/
fabric.Image.filters.Composed.fromObject = function(object) {
fabric.Image.filters.Composed.fromObject = function(object, options) {
var filters = object.subFilters || [];
return Promise.all(filters.map(function(filter) {
return fabric.Image.filters[filter.type].fromObject(filter);
return fabric.Image.filters[filter.type].fromObject(filter, options);
})).then(function(enlivedFilters) {
return new fabric.Image.filters.Composed({ subFilters: enlivedFilters });
});
Expand Down
50 changes: 27 additions & 23 deletions src/mixins/canvas_serialization.mixin.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {

/**
* Populates canvas with data from the specified JSON.
* JSON format must conform to the one of {@link fabric.Canvas#toJSON}
*
* **IMPORTANT**: It is recommended to abort loading tasks before calling this method to prevent race conditions and unnecessary networking
*
* @param {String|Object} json JSON string or object
* @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created.
* @param {Object} [options] options
* @param {AbortSignal} [options.signal] see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
* @return {Promise<fabric.Canvas>} instance
* @chainable
* @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization}
* @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo}
* @example <caption>loadFromJSON</caption>
Expand All @@ -18,54 +23,53 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
* }).then((canvas) => {
* ... canvas is restored, add your code.
* });
*
*/
loadFromJSON: function (json, reviver) {
loadFromJSON: function (json, reviver, options) {
if (!json) {
return;
return Promise.reject(new Error('fabric.js: `json` is undefined'));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BREAKING

ShaMan123 marked this conversation as resolved.
Show resolved Hide resolved
}

// serialize if it wasn't already
var serialized = (typeof json === 'string')
? JSON.parse(json)
: Object.assign({}, json);

var _this = this,
renderOnAddRemove = this.renderOnAddRemove;

var _this = this, renderOnAddRemove = this.renderOnAddRemove;
this.renderOnAddRemove = false;

return fabric.util.enlivenObjects(serialized.objects || [], '', reviver)
.then(function(enlived) {
return Promise.all([
fabric.util.enlivenObjects(serialized.objects || [], { reviver: reviver, signal: options && options.signal }),
fabric.util.enlivenObjectEnlivables({
backgroundImage: serialized.backgroundImage,
backgroundColor: serialized.background,
overlayImage: serialized.overlayImage,
overlayColor: serialized.overlay,
clipPath: serialized.clipPath,
}, { signal: options && options.signal })
])
.then(function (res) {
var enlived = res[0], enlivedMap = res[1];
_this.clear();
return fabric.util.enlivenObjectEnlivables({
backgroundImage: serialized.backgroundImage,
backgroundColor: serialized.background,
overlayImage: serialized.overlayImage,
overlayColor: serialized.overlay,
clipPath: serialized.clipPath,
})
.then(function(enlivedMap) {
_this.__setupCanvas(serialized, enlived, renderOnAddRemove);
_this.set(enlivedMap);
return _this;
});
_this.__setupCanvas(serialized, enlived);
_this.renderOnAddRemove = renderOnAddRemove;
_this.set(enlivedMap);
return _this;
});
},

/**
* @private
* @param {Object} serialized Object with background and overlay information
* @param {Array} enlivenedObjects canvas objects
* @param {boolean} renderOnAddRemove renderOnAddRemove setting for the canvas
*/
__setupCanvas: function(serialized, enlivenedObjects, renderOnAddRemove) {
__setupCanvas: function(serialized, enlivenedObjects) {
var _this = this;
enlivenedObjects.forEach(function(obj, index) {
// we splice the array just in case some custom classes restored from JSON
// will add more object to canvas at canvas init.
_this.insertAt(obj, index);
});
this.renderOnAddRemove = renderOnAddRemove;
// remove parts i cannot set as options
delete serialized.objects;
delete serialized.backgroundImage;
Expand Down
11 changes: 9 additions & 2 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -686,19 +686,23 @@
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
* @param {Object} [parsingOptions] options for parsing document
* @param {String} [parsingOptions.crossOrigin] crossOrigin settings
* @param {AbortSignal} [parsingOptions.signal] see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
*/
fabric.parseSVGDocument = function(doc, callback, reviver, parsingOptions) {
if (!doc) {
return;
}

if (parsingOptions && parsingOptions.signal && parsingOptions.signal.aborted) {
throw new Error('`options.signal` is in `aborted` state');
}
parseUseDirectives(doc);

var svgUid = fabric.Object.__uid++, i, len,
options = applyViewboxTransform(doc),
descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
options.crossOrigin = parsingOptions && parsingOptions.crossOrigin;
options.svgUid = svgUid;
options.signal = parsingOptions && parsingOptions.signal;

if (descendants.length === 0 && fabric.isLikelyNode) {
// we're likely in node, where "o3-xml" library fails to gEBTN("*")
Expand Down Expand Up @@ -1044,13 +1048,15 @@
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
* @param {Object} [options] Object containing options for parsing
* @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
* @param {AbortSignal} [options.signal] handle aborting, see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
*/
loadSVGFromURL: function(url, callback, reviver, options) {

url = url.replace(/^\n\s*/, '').trim();
new fabric.util.request(url, {
method: 'get',
onComplete: onComplete
onComplete: onComplete,
signal: options && options.signal
});

function onComplete(r) {
Expand All @@ -1075,6 +1081,7 @@
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
* @param {Object} [options] Object containing options for parsing
* @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
* @param {AbortSignal} [options.signal] handle aborting, see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
*/
loadSVGFromString: function(string, callback, reviver, options) {
var parser = new fabric.window.DOMParser(),
Expand Down
14 changes: 11 additions & 3 deletions src/pattern.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,17 @@
}
});

fabric.Pattern.fromObject = function(object) {
var patternOptions = Object.assign({}, object);
return fabric.util.loadImage(object.source, { crossOrigin: object.crossOrigin })
/**
*
* @param {object} object
* @param {object} [options]
* @param {AbortSignal} [options.signal] handle aborting, see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
* @returns
*/
fabric.Pattern.fromObject = function(object, options) {
var patternOptions = Object.assign({}, object),
imageOptions = Object.assign({}, options, { crossOrigin: object.crossOrigin });
return fabric.util.loadImage(object.source, imageOptions)
.then(function(img) {
patternOptions.source = img;
return new fabric.Pattern(patternOptions);
Expand Down
47 changes: 28 additions & 19 deletions src/shapes/image.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,16 +364,18 @@
},

/**
* Sets source of an image
* Loads and sets source of an image\
* **IMPORTANT**: It is recommended to abort loading tasks before calling this method to prevent race conditions and unnecessary networking
* @param {String} src Source string (URL)
* @param {Object} [options] Options object
* @param {AbortSignal} [options.signal] see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
* @param {String} [options.crossOrigin] crossOrigin value (one of "", "anonymous", "use-credentials")
* @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes
* @return {Promise<fabric.Image>} thisArg
*/
setSrc: function(src, options) {
var _this = this;
return fabric.util.loadImage(src, options).then(function(img) {
return fabric.util.loadImage(src, options).then(function (img) {
_this.setElement(img, options);
_this._setWidthHeight();
return _this;
Expand Down Expand Up @@ -678,38 +680,44 @@
* Creates an instance of fabric.Image from its object representation
* @static
* @param {Object} object Object to create an instance from
* @param {object} [options] Options object
* @param {AbortSignal} [options.signal] handle aborting, see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
* @returns {Promise<fabric.Image>}
*/
fabric.Image.fromObject = function(_object) {
var object = Object.assign({}, _object),
filters = object.filters,
resizeFilter = object.resizeFilter;
fabric.Image.fromObject = function (object, options) {
var _object = Object.assign({}, object),
filters = _object.filters,
resizeFilter = _object.resizeFilter;
// the generic enliving will fail on filters for now
delete object.resizeFilter;
delete object.filters;
delete _object.resizeFilter;
delete _object.filters;
var imageOptions = Object.assign({}, options, { crossOrigin: _object.crossOrigin }),
filterOptions = Object.assign({}, options, { namespace: fabric.Image.filters });
return Promise.all([
fabric.util.loadImage(object.src, { crossOrigin: _object.crossOrigin }),
filters && fabric.util.enlivenObjects(filters, 'fabric.Image.filters'),
resizeFilter && fabric.util.enlivenObjects([resizeFilter], 'fabric.Image.filters'),
fabric.util.enlivenObjectEnlivables(object),
fabric.util.loadImage(_object.src, imageOptions),
filters && fabric.util.enlivenObjects(filters, filterOptions),
resizeFilter && fabric.util.enlivenObjects([resizeFilter], filterOptions),
fabric.util.enlivenObjectEnlivables(_object, options),
])
.then(function(imgAndFilters) {
object.filters = imgAndFilters[1] || [];
object.resizeFilter = imgAndFilters[2] && imgAndFilters[2][0];
return new fabric.Image(imgAndFilters[0], Object.assign(object, imgAndFilters[3]));
_object.filters = imgAndFilters[1] || [];
_object.resizeFilter = imgAndFilters[2] && imgAndFilters[2][0];
return new fabric.Image(imgAndFilters[0], Object.assign(_object, imgAndFilters[3]));
});
};

/**
* Creates an instance of fabric.Image from an URL string
* @static
* @param {String} url URL to create an image from
* @param {Object} [imgOptions] Options object
* @param {object} [options] Options object
* @param {string} [options.crossOrigin] cors value for the image loading, default to anonymous
* @param {AbortSignal} [options.signal] handle aborting, see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
* @returns {Promise<fabric.Image>}
*/
fabric.Image.fromURL = function(url, imgOptions) {
return fabric.util.loadImage(url, imgOptions || {}).then(function(img) {
return new fabric.Image(img, imgOptions);
fabric.Image.fromURL = function(url, options) {
return fabric.util.loadImage(url, options || {}).then(function(img) {
return new fabric.Image(img, options);
});
};

Expand All @@ -729,6 +737,7 @@
* @static
* @param {SVGElement} element Element to parse
* @param {Object} [options] Options object
* @param {AbortSignal} [options.signal] handle aborting, see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
* @param {Function} callback Callback to execute when fabric.Image object is created
* @return {fabric.Image} Instance of fabric.Image
*/
Expand Down
2 changes: 1 addition & 1 deletion src/shapes/itext.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,6 @@
* @returns {Promise<fabric.IText>}
*/
fabric.IText.fromObject = function(object) {
return fabric.Object._fromObject(fabric.IText, object, 'text');
return fabric.Object._fromObject(fabric.IText, object, { extraParam: 'text' });
};
})();
2 changes: 1 addition & 1 deletion src/shapes/line.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@
fabric.Line.fromObject = function(object) {
var options = clone(object, true);
options.points = [object.x1, object.y1, object.x2, object.y2];
return fabric.Object._fromObject(fabric.Line, options, 'points').then(function(fabricLine) {
return fabric.Object._fromObject(fabric.Line, options, { extraParam: 'points' }).then(function(fabricLine) {
delete fabricLine.points;
return fabricLine;
});
Expand Down
32 changes: 21 additions & 11 deletions src/shapes/object.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -1928,23 +1928,33 @@
fabric.Object.NUM_FRACTION_DIGITS = 2;

/**
* Defines which properties should be enlivened from the object passed to {@link fabric.Object._fromObject}
* @static
* @memberOf fabric.Object
* @constant
* @type string[]
*
* @param {Function} klass
* @param {object} object
* @param {object} [options]
* @param {string} [options.extraParam] property to pass as first argument to the constructor
* @param {AbortSignal} [options.signal] handle aborting, see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
* @returns {Promise<fabric.Object>}
*/

fabric.Object._fromObject = function(klass, object, extraParam) {
fabric.Object._fromObject = function(klass, object, options) {
var serializedObject = clone(object, true);
return fabric.util.enlivenObjectEnlivables(serializedObject).then(function(enlivedMap) {
return fabric.util.enlivenObjectEnlivables(serializedObject, options).then(function(enlivedMap) {
var newObject = Object.assign(object, enlivedMap);
return extraParam ? new klass(object[extraParam], newObject) : new klass(newObject);
return options && options.extraParam ? new klass(object[options.extraParam], newObject) : new klass(newObject);
});
};

fabric.Object.fromObject = function(object) {
return fabric.Object._fromObject(fabric.Object, object);
/**
*
* @static
* @memberOf fabric.Object
* @param {object} object
* @param {object} [options]
* @param {AbortSignal} [options.signal] handle aborting, see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
* @returns {Promise<fabric.Object>}
*/
fabric.Object.fromObject = function(object, options) {
return fabric.Object._fromObject(fabric.Object, object, options);
};

/**
Expand Down
2 changes: 1 addition & 1 deletion src/shapes/path.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@
* @returns {Promise<fabric.Path>}
*/
fabric.Path.fromObject = function(object) {
return fabric.Object._fromObject(fabric.Path, object, 'path');
return fabric.Object._fromObject(fabric.Path, object, { extraParam: 'path' });
};

/* _FROM_SVG_START_ */
Expand Down
2 changes: 1 addition & 1 deletion src/shapes/polygon.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
* @returns {Promise<fabric.Polygon>}
*/
fabric.Polygon.fromObject = function(object) {
return fabric.Object._fromObject(fabric.Polygon, object, 'points');
return fabric.Object._fromObject(fabric.Polygon, object, { extraParam: 'points' });
};

})(typeof exports !== 'undefined' ? exports : this);
Loading