@ckeditor/ckeditor5-watchdog
Advanced tools
Comparing version 36.0.1 to 37.0.0-alpha.0
/*! | ||
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
*/(()=>{"use strict";var t={d:(e,r)=>{for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{ContextWatchdog:()=>Fn,EditorWatchdog:()=>Dn,Watchdog:()=>r});class r{constructor(t){if(this.crashes=[],this.state="initializing",this._crashNumberLimit="number"==typeof t.crashNumberLimit?t.crashNumberLimit:3,this._now=Date.now,this._minimumNonErrorTimePeriod="number"==typeof t.minimumNonErrorTimePeriod?t.minimumNonErrorTimePeriod:5e3,this._boundErrorHandler=t=>{const e=t.error||t.reason;e instanceof Error&&this._handleError(e,t)},this._listeners={},!this._restart)throw new Error("The Watchdog class was split into the abstract `Watchdog` class and the `EditorWatchdog` class. Please, use `EditorWatchdog` if you have used the `Watchdog` class previously.")}setCreator(t){this._creator=t}setDestructor(t){this._destructor=t}destroy(){this._stopErrorHandling(),this._listeners={}}on(t,e){this._listeners[t]||(this._listeners[t]=[]),this._listeners[t].push(e)}off(t,e){this._listeners[t]=this._listeners[t].filter((t=>t!==e))}_fire(t,...e){const r=this._listeners[t]||[];for(const t of r)t.apply(this,[null,...e])}_startErrorHandling(){window.addEventListener("error",this._boundErrorHandler),window.addEventListener("unhandledrejection",this._boundErrorHandler)}_stopErrorHandling(){window.removeEventListener("error",this._boundErrorHandler),window.removeEventListener("unhandledrejection",this._boundErrorHandler)}_handleError(t,e){if(this._shouldReactToError(t)){this.crashes.push({message:t.message,stack:t.stack,filename:e.filename,lineno:e.lineno,colno:e.colno,date:this._now()});const r=this._shouldRestart();this.state="crashed",this._fire("stateChange"),this._fire("error",{error:t,causesRestart:r}),r?this._restart():(this.state="crashedPermanently",this._fire("stateChange"))}}_shouldReactToError(t){return t.is&&t.is("CKEditorError")&&void 0!==t.context&&null!==t.context&&"ready"===this.state&&this._isErrorComingFromThisItem(t)}_shouldRestart(){if(this.crashes.length<=this._crashNumberLimit)return!0;return(this.crashes[this.crashes.length-1].date-this.crashes[this.crashes.length-1-this._crashNumberLimit].date)/this._crashNumberLimit>this._minimumNonErrorTimePeriod}}const n=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)};const o="object"==typeof global&&global&&global.Object===Object&&global;var s="object"==typeof self&&self&&self.Object===Object&&self;const i=o||s||Function("return this")();const c=function(){return i.Date.now()};var a=/\s/;const u=function(t){for(var e=t.length;e--&&a.test(t.charAt(e)););return e};var h=/^\s+/;const l=function(t){return t?t.slice(0,u(t)+1).replace(h,""):t};const f=i.Symbol;var d=Object.prototype,_=d.hasOwnProperty,p=d.toString,y=f?f.toStringTag:void 0;const v=function(t){var e=_.call(t,y),r=t[y];try{t[y]=void 0;var n=!0}catch(t){}var o=p.call(t);return n&&(e?t[y]=r:delete t[y]),o};var b=Object.prototype.toString;const g=function(t){return b.call(t)};var m="[object Null]",j="[object Undefined]",w=f?f.toStringTag:void 0;const E=function(t){return null==t?void 0===t?j:m:w&&w in Object(t)?v(t):g(t)};const x=function(t){return null!=t&&"object"==typeof t};var O="[object Symbol]";const A=function(t){return"symbol"==typeof t||x(t)&&E(t)==O};var P=NaN,S=/^[-+]0x[0-9a-f]+$/i,C=/^0b[01]+$/i,T=/^0o[0-7]+$/i,I=parseInt;const z=function(t){if("number"==typeof t)return t;if(A(t))return P;if(n(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=n(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=l(t);var r=C.test(t);return r||T.test(t)?I(t.slice(2),r?2:8):S.test(t)?P:+t};var D="Expected a function",W=Math.max,F=Math.min;const M=function(t,e,r){var o,s,i,a,u,h,l=0,f=!1,d=!1,_=!0;if("function"!=typeof t)throw new TypeError(D);function p(e){var r=o,n=s;return o=s=void 0,l=e,a=t.apply(n,r)}function y(t){var r=t-h;return void 0===h||r>=e||r<0||d&&t-l>=i}function v(){var t=c();if(y(t))return b(t);u=setTimeout(v,function(t){var r=e-(t-h);return d?F(r,i-(t-l)):r}(t))}function b(t){return u=void 0,_&&o?p(t):(o=s=void 0,a)}function g(){var t=c(),r=y(t);if(o=arguments,s=this,h=t,r){if(void 0===u)return function(t){return l=t,u=setTimeout(v,e),f?p(t):a}(h);if(d)return clearTimeout(u),u=setTimeout(v,e),p(h)}return void 0===u&&(u=setTimeout(v,e)),a}return e=z(e)||0,n(r)&&(f=!!r.leading,i=(d="maxWait"in r)?W(z(r.maxWait)||0,e):i,_="trailing"in r?!!r.trailing:_),g.cancel=function(){void 0!==u&&clearTimeout(u),l=0,o=h=s=u=void 0},g.flush=function(){return void 0===u?a:b(c())},g};var N="Expected a function";const q=function(t,e,r){var o=!0,s=!0;if("function"!=typeof t)throw new TypeError(N);return n(r)&&(o="leading"in r?!!r.leading:o,s="trailing"in r?!!r.trailing:s),M(t,e,{leading:o,maxWait:e,trailing:s})};const U=function(){this.__data__=[],this.size=0};const R=function(t,e){return t===e||t!=t&&e!=e};const $=function(t,e){for(var r=t.length;r--;)if(R(t[r][0],e))return r;return-1};var k=Array.prototype.splice;const L=function(t){var e=this.__data__,r=$(e,t);return!(r<0)&&(r==e.length-1?e.pop():k.call(e,r,1),--this.size,!0)};const H=function(t){var e=this.__data__,r=$(e,t);return r<0?void 0:e[r][1]};const B=function(t){return $(this.__data__,t)>-1};const Q=function(t,e){var r=this.__data__,n=$(r,t);return n<0?(++this.size,r.push([t,e])):r[n][1]=e,this};function V(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}V.prototype.clear=U,V.prototype.delete=L,V.prototype.get=H,V.prototype.has=B,V.prototype.set=Q;const K=V;const G=function(){this.__data__=new K,this.size=0};const Y=function(t){var e=this.__data__,r=e.delete(t);return this.size=e.size,r};const J=function(t){return this.__data__.get(t)};const X=function(t){return this.__data__.has(t)};var Z="[object AsyncFunction]",tt="[object Function]",et="[object GeneratorFunction]",rt="[object Proxy]";const nt=function(t){if(!n(t))return!1;var e=E(t);return e==tt||e==et||e==Z||e==rt};const ot=i["__core-js_shared__"];var st,it=(st=/[^.]+$/.exec(ot&&ot.keys&&ot.keys.IE_PROTO||""))?"Symbol(src)_1."+st:"";const ct=function(t){return!!it&&it in t};var at=Function.prototype.toString;const ut=function(t){if(null!=t){try{return at.call(t)}catch(t){}try{return t+""}catch(t){}}return""};var ht=/^\[object .+?Constructor\]$/,lt=Function.prototype,ft=Object.prototype,dt=lt.toString,_t=ft.hasOwnProperty,pt=RegExp("^"+dt.call(_t).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");const yt=function(t){return!(!n(t)||ct(t))&&(nt(t)?pt:ht).test(ut(t))};const vt=function(t,e){return null==t?void 0:t[e]};const bt=function(t,e){var r=vt(t,e);return yt(r)?r:void 0};const gt=bt(i,"Map");const mt=bt(Object,"create");const jt=function(){this.__data__=mt?mt(null):{},this.size=0};const wt=function(t){var e=this.has(t)&&delete this.__data__[t];return this.size-=e?1:0,e};var Et="__lodash_hash_undefined__",xt=Object.prototype.hasOwnProperty;const Ot=function(t){var e=this.__data__;if(mt){var r=e[t];return r===Et?void 0:r}return xt.call(e,t)?e[t]:void 0};var At=Object.prototype.hasOwnProperty;const Pt=function(t){var e=this.__data__;return mt?void 0!==e[t]:At.call(e,t)};var St="__lodash_hash_undefined__";const Ct=function(t,e){var r=this.__data__;return this.size+=this.has(t)?0:1,r[t]=mt&&void 0===e?St:e,this};function Tt(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}Tt.prototype.clear=jt,Tt.prototype.delete=wt,Tt.prototype.get=Ot,Tt.prototype.has=Pt,Tt.prototype.set=Ct;const It=Tt;const zt=function(){this.size=0,this.__data__={hash:new It,map:new(gt||K),string:new It}};const Dt=function(t){var e=typeof t;return"string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==t:null===t};const Wt=function(t,e){var r=t.__data__;return Dt(e)?r["string"==typeof e?"string":"hash"]:r.map};const Ft=function(t){var e=Wt(this,t).delete(t);return this.size-=e?1:0,e};const Mt=function(t){return Wt(this,t).get(t)};const Nt=function(t){return Wt(this,t).has(t)};const qt=function(t,e){var r=Wt(this,t),n=r.size;return r.set(t,e),this.size+=r.size==n?0:1,this};function Ut(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}Ut.prototype.clear=zt,Ut.prototype.delete=Ft,Ut.prototype.get=Mt,Ut.prototype.has=Nt,Ut.prototype.set=qt;const Rt=Ut;var $t=200;const kt=function(t,e){var r=this.__data__;if(r instanceof K){var n=r.__data__;if(!gt||n.length<$t-1)return n.push([t,e]),this.size=++r.size,this;r=this.__data__=new Rt(n)}return r.set(t,e),this.size=r.size,this};function Lt(t){var e=this.__data__=new K(t);this.size=e.size}Lt.prototype.clear=G,Lt.prototype.delete=Y,Lt.prototype.get=J,Lt.prototype.has=X,Lt.prototype.set=kt;const Ht=Lt;const Bt=function(t,e){for(var r=-1,n=null==t?0:t.length;++r<n&&!1!==e(t[r],r,t););return t};const Qt=function(){try{var t=bt(Object,"defineProperty");return t({},"",{}),t}catch(t){}}();const Vt=function(t,e,r){"__proto__"==e&&Qt?Qt(t,e,{configurable:!0,enumerable:!0,value:r,writable:!0}):t[e]=r};var Kt=Object.prototype.hasOwnProperty;const Gt=function(t,e,r){var n=t[e];Kt.call(t,e)&&R(n,r)&&(void 0!==r||e in t)||Vt(t,e,r)};const Yt=function(t,e,r,n){var o=!r;r||(r={});for(var s=-1,i=e.length;++s<i;){var c=e[s],a=n?n(r[c],t[c],c,r,t):void 0;void 0===a&&(a=t[c]),o?Vt(r,c,a):Gt(r,c,a)}return r};const Jt=function(t,e){for(var r=-1,n=Array(t);++r<t;)n[r]=e(r);return n};var Xt="[object Arguments]";const Zt=function(t){return x(t)&&E(t)==Xt};var te=Object.prototype,ee=te.hasOwnProperty,re=te.propertyIsEnumerable;const ne=Zt(function(){return arguments}())?Zt:function(t){return x(t)&&ee.call(t,"callee")&&!re.call(t,"callee")};const oe=Array.isArray;const se=function(){return!1};var ie="object"==typeof exports&&exports&&!exports.nodeType&&exports,ce=ie&&"object"==typeof module&&module&&!module.nodeType&&module,ae=ce&&ce.exports===ie?i.Buffer:void 0;const ue=(ae?ae.isBuffer:void 0)||se;var he=9007199254740991,le=/^(?:0|[1-9]\d*)$/;const fe=function(t,e){var r=typeof t;return!!(e=null==e?he:e)&&("number"==r||"symbol"!=r&&le.test(t))&&t>-1&&t%1==0&&t<e};var de=9007199254740991;const _e=function(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=de};var pe={};pe["[object Float32Array]"]=pe["[object Float64Array]"]=pe["[object Int8Array]"]=pe["[object Int16Array]"]=pe["[object Int32Array]"]=pe["[object Uint8Array]"]=pe["[object Uint8ClampedArray]"]=pe["[object Uint16Array]"]=pe["[object Uint32Array]"]=!0,pe["[object Arguments]"]=pe["[object Array]"]=pe["[object ArrayBuffer]"]=pe["[object Boolean]"]=pe["[object DataView]"]=pe["[object Date]"]=pe["[object Error]"]=pe["[object Function]"]=pe["[object Map]"]=pe["[object Number]"]=pe["[object Object]"]=pe["[object RegExp]"]=pe["[object Set]"]=pe["[object String]"]=pe["[object WeakMap]"]=!1;const ye=function(t){return x(t)&&_e(t.length)&&!!pe[E(t)]};const ve=function(t){return function(e){return t(e)}};var be="object"==typeof exports&&exports&&!exports.nodeType&&exports,ge=be&&"object"==typeof module&&module&&!module.nodeType&&module,me=ge&&ge.exports===be&&o.process;const je=function(){try{var t=ge&&ge.require&&ge.require("util").types;return t||me&&me.binding&&me.binding("util")}catch(t){}}();var we=je&&je.isTypedArray;const Ee=we?ve(we):ye;var xe=Object.prototype.hasOwnProperty;const Oe=function(t,e){var r=oe(t),n=!r&&ne(t),o=!r&&!n&&ue(t),s=!r&&!n&&!o&&Ee(t),i=r||n||o||s,c=i?Jt(t.length,String):[],a=c.length;for(var u in t)!e&&!xe.call(t,u)||i&&("length"==u||o&&("offset"==u||"parent"==u)||s&&("buffer"==u||"byteLength"==u||"byteOffset"==u)||fe(u,a))||c.push(u);return c};var Ae=Object.prototype;const Pe=function(t){var e=t&&t.constructor;return t===("function"==typeof e&&e.prototype||Ae)};const Se=function(t,e){return function(r){return t(e(r))}};const Ce=Se(Object.keys,Object);var Te=Object.prototype.hasOwnProperty;const Ie=function(t){if(!Pe(t))return Ce(t);var e=[];for(var r in Object(t))Te.call(t,r)&&"constructor"!=r&&e.push(r);return e};const ze=function(t){return null!=t&&_e(t.length)&&!nt(t)};const De=function(t){return ze(t)?Oe(t):Ie(t)};const We=function(t,e){return t&&Yt(e,De(e),t)};const Fe=function(t){var e=[];if(null!=t)for(var r in Object(t))e.push(r);return e};var Me=Object.prototype.hasOwnProperty;const Ne=function(t){if(!n(t))return Fe(t);var e=Pe(t),r=[];for(var o in t)("constructor"!=o||!e&&Me.call(t,o))&&r.push(o);return r};const qe=function(t){return ze(t)?Oe(t,!0):Ne(t)};const Ue=function(t,e){return t&&Yt(e,qe(e),t)};var Re="object"==typeof exports&&exports&&!exports.nodeType&&exports,$e=Re&&"object"==typeof module&&module&&!module.nodeType&&module,ke=$e&&$e.exports===Re?i.Buffer:void 0,Le=ke?ke.allocUnsafe:void 0;const He=function(t,e){if(e)return t.slice();var r=t.length,n=Le?Le(r):new t.constructor(r);return t.copy(n),n};const Be=function(t,e){var r=-1,n=t.length;for(e||(e=Array(n));++r<n;)e[r]=t[r];return e};const Qe=function(t,e){for(var r=-1,n=null==t?0:t.length,o=0,s=[];++r<n;){var i=t[r];e(i,r,t)&&(s[o++]=i)}return s};const Ve=function(){return[]};var Ke=Object.prototype.propertyIsEnumerable,Ge=Object.getOwnPropertySymbols;const Ye=Ge?function(t){return null==t?[]:(t=Object(t),Qe(Ge(t),(function(e){return Ke.call(t,e)})))}:Ve;const Je=function(t,e){return Yt(t,Ye(t),e)};const Xe=function(t,e){for(var r=-1,n=e.length,o=t.length;++r<n;)t[o+r]=e[r];return t};const Ze=Se(Object.getPrototypeOf,Object);const tr=Object.getOwnPropertySymbols?function(t){for(var e=[];t;)Xe(e,Ye(t)),t=Ze(t);return e}:Ve;const er=function(t,e){return Yt(t,tr(t),e)};const rr=function(t,e,r){var n=e(t);return oe(t)?n:Xe(n,r(t))};const nr=function(t){return rr(t,De,Ye)};const or=function(t){return rr(t,qe,tr)};const sr=bt(i,"DataView");const ir=bt(i,"Promise");const cr=bt(i,"Set");const ar=bt(i,"WeakMap");var ur="[object Map]",hr="[object Promise]",lr="[object Set]",fr="[object WeakMap]",dr="[object DataView]",_r=ut(sr),pr=ut(gt),yr=ut(ir),vr=ut(cr),br=ut(ar),gr=E;(sr&&gr(new sr(new ArrayBuffer(1)))!=dr||gt&&gr(new gt)!=ur||ir&&gr(ir.resolve())!=hr||cr&&gr(new cr)!=lr||ar&&gr(new ar)!=fr)&&(gr=function(t){var e=E(t),r="[object Object]"==e?t.constructor:void 0,n=r?ut(r):"";if(n)switch(n){case _r:return dr;case pr:return ur;case yr:return hr;case vr:return lr;case br:return fr}return e});const mr=gr;var jr=Object.prototype.hasOwnProperty;const wr=function(t){var e=t.length,r=new t.constructor(e);return e&&"string"==typeof t[0]&&jr.call(t,"index")&&(r.index=t.index,r.input=t.input),r};const Er=i.Uint8Array;const xr=function(t){var e=new t.constructor(t.byteLength);return new Er(e).set(new Er(t)),e};const Or=function(t,e){var r=e?xr(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.byteLength)};var Ar=/\w*$/;const Pr=function(t){var e=new t.constructor(t.source,Ar.exec(t));return e.lastIndex=t.lastIndex,e};var Sr=f?f.prototype:void 0,Cr=Sr?Sr.valueOf:void 0;const Tr=function(t){return Cr?Object(Cr.call(t)):{}};const Ir=function(t,e){var r=e?xr(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.length)};var zr="[object Boolean]",Dr="[object Date]",Wr="[object Map]",Fr="[object Number]",Mr="[object RegExp]",Nr="[object Set]",qr="[object String]",Ur="[object Symbol]",Rr="[object ArrayBuffer]",$r="[object DataView]",kr="[object Float32Array]",Lr="[object Float64Array]",Hr="[object Int8Array]",Br="[object Int16Array]",Qr="[object Int32Array]",Vr="[object Uint8Array]",Kr="[object Uint8ClampedArray]",Gr="[object Uint16Array]",Yr="[object Uint32Array]";const Jr=function(t,e,r){var n=t.constructor;switch(e){case Rr:return xr(t);case zr:case Dr:return new n(+t);case $r:return Or(t,r);case kr:case Lr:case Hr:case Br:case Qr:case Vr:case Kr:case Gr:case Yr:return Ir(t,r);case Wr:return new n;case Fr:case qr:return new n(t);case Mr:return Pr(t);case Nr:return new n;case Ur:return Tr(t)}};var Xr=Object.create;const Zr=function(){function t(){}return function(e){if(!n(e))return{};if(Xr)return Xr(e);t.prototype=e;var r=new t;return t.prototype=void 0,r}}();const tn=function(t){return"function"!=typeof t.constructor||Pe(t)?{}:Zr(Ze(t))};var en="[object Map]";const rn=function(t){return x(t)&&mr(t)==en};var nn=je&&je.isMap;const on=nn?ve(nn):rn;var sn="[object Set]";const cn=function(t){return x(t)&&mr(t)==sn};var an=je&&je.isSet;const un=an?ve(an):cn;var hn=1,ln=2,fn=4,dn="[object Arguments]",_n="[object Function]",pn="[object GeneratorFunction]",yn="[object Object]",vn={};vn[dn]=vn["[object Array]"]=vn["[object ArrayBuffer]"]=vn["[object DataView]"]=vn["[object Boolean]"]=vn["[object Date]"]=vn["[object Float32Array]"]=vn["[object Float64Array]"]=vn["[object Int8Array]"]=vn["[object Int16Array]"]=vn["[object Int32Array]"]=vn["[object Map]"]=vn["[object Number]"]=vn[yn]=vn["[object RegExp]"]=vn["[object Set]"]=vn["[object String]"]=vn["[object Symbol]"]=vn["[object Uint8Array]"]=vn["[object Uint8ClampedArray]"]=vn["[object Uint16Array]"]=vn["[object Uint32Array]"]=!0,vn["[object Error]"]=vn[_n]=vn["[object WeakMap]"]=!1;const bn=function t(e,r,o,s,i,c){var a,u=r&hn,h=r&ln,l=r&fn;if(o&&(a=i?o(e,s,i,c):o(e)),void 0!==a)return a;if(!n(e))return e;var f=oe(e);if(f){if(a=wr(e),!u)return Be(e,a)}else{var d=mr(e),_=d==_n||d==pn;if(ue(e))return He(e,u);if(d==yn||d==dn||_&&!i){if(a=h||_?{}:tn(e),!u)return h?er(e,Ue(a,e)):Je(e,We(a,e))}else{if(!vn[d])return i?e:{};a=Jr(e,d,u)}}c||(c=new Ht);var p=c.get(e);if(p)return p;c.set(e,a),un(e)?e.forEach((function(n){a.add(t(n,r,o,n,e,c))})):on(e)&&e.forEach((function(n,s){a.set(s,t(n,r,o,s,e,c))}));var y=f?void 0:(l?h?or:nr:h?qe:De)(e);return Bt(y||e,(function(n,s){y&&(n=e[s=n]),Gt(a,s,t(n,r,o,s,e,c))})),a};var gn=1,mn=4;const jn=function(t,e){return bn(t,gn|mn,e="function"==typeof e?e:void 0)};var wn="[object Object]",En=Function.prototype,xn=Object.prototype,On=En.toString,An=xn.hasOwnProperty,Pn=On.call(Object);const Sn=function(t){if(!x(t)||E(t)!=wn)return!1;var e=Ze(t);if(null===e)return!0;var r=An.call(e,"constructor")&&e.constructor;return"function"==typeof r&&r instanceof r&&On.call(r)==Pn};const Cn=function(t){return x(t)&&1===t.nodeType&&!Sn(t)};function Tn(t,e=new Set){const r=[t],n=new Set;let o=0;for(;r.length>o;){const t=r[o++];if(!(n.has(t)||In(t)||e.has(t)))if(n.add(t),t[Symbol.iterator])try{for(const e of t)r.push(e)}catch(t){}else for(const e in t)"defaultValue"!==e&&r.push(t[e])}return n}function In(t){const e=Object.prototype.toString.call(t),r=typeof t;return"number"===r||"boolean"===r||"string"===r||"symbol"===r||"function"===r||"[object Date]"===e||"[object RegExp]"===e||"[object Module]"===e||null==t||!0===t._watchdogExcluded||t instanceof EventTarget||t instanceof Event}function zn(t,e,r=new Set){if(t===e&&("object"==typeof(n=t)&&null!==n))return!0;var n;const o=Tn(t,r),s=Tn(e,r);for(const t of o)if(s.has(t))return!0;return!1}class Dn extends r{constructor(t,e={}){super(e),this._editor=null,this._throttledSave=q(this._save.bind(this),"number"==typeof e.saveInterval?e.saveInterval:5e3),this._creator=(e,r)=>t.create(e,r),this._destructor=t=>t.destroy()}get editor(){return this._editor}get _item(){return this._editor}_restart(){return Promise.resolve().then((()=>(this.state="initializing",this._fire("stateChange"),this._destroy()))).catch((t=>{console.error("An error happened during the editor destroying.",t)})).then((()=>{if("string"==typeof this._elementOrData)return this.create(this._data,this._config,this._config.context);{const t=Object.assign({},this._config,{initialData:this._data});return this.create(this._elementOrData,t,t.context)}})).then((()=>{this._fire("restart")}))}create(t=this._elementOrData,e=this._config,r){return Promise.resolve().then((()=>(super._startErrorHandling(),this._elementOrData=t,this._config=this._cloneEditorConfiguration(e)||{},this._config.context=r,this._creator(t,this._config)))).then((t=>{this._editor=t,t.model.document.on("change:data",this._throttledSave),this._lastDocumentVersion=t.model.document.version,this._data=this._getData(),this.state="ready",this._fire("stateChange")}))}destroy(){return Promise.resolve().then((()=>(this.state="destroyed",this._fire("stateChange"),super.destroy(),this._destroy())))}_destroy(){return Promise.resolve().then((()=>{this._stopErrorHandling(),this._throttledSave.flush();const t=this._editor;return this._editor=null,t.model.document.off("change:data",this._throttledSave),this._destructor(t)}))}_save(){const t=this._editor.model.document.version;try{this._data=this._getData(),this._lastDocumentVersion=t}catch(t){console.error(t,"An error happened during restoring editor data. Editor will be restored from the previously saved data.")}}_setExcludedProperties(t){this._excludedProps=t}_getData(){const t={};for(const e of this._editor.model.document.getRootNames())t[e]=this._editor.data.get({rootName:e});return t}_isErrorComingFromThisItem(t){return zn(this._editor,t.context,this._excludedProps)}_cloneEditorConfiguration(t){return jn(t,((t,e)=>Cn(t)||"context"===e?t:void 0))}}const Wn=Symbol("MainQueueId");class Fn extends r{constructor(t,e={}){super(e),this._watchdogs=new Map,this._watchdogConfig=e,this._context=null,this._contextProps=new Set,this._actionQueues=new Mn,this._creator=e=>t.create(e),this._destructor=t=>t.destroy(),this._actionQueues.onEmpty((()=>{"initializing"===this.state&&(this.state="ready",this._fire("stateChange"))}))}get context(){return this._context}create(t={}){return this._actionQueues.enqueue(Wn,(()=>(this._contextConfig=t,this._create())))}getItem(t){return this._getWatchdog(t)._item}getItemState(t){return this._getWatchdog(t).state}add(t){const e=Nn(t);return Promise.all(e.map((t=>this._actionQueues.enqueue(t.id,(()=>{if("destroyed"===this.state)throw new Error("Cannot add items to destroyed watchdog.");if(!this._context)throw new Error("Context was not created yet. You should call the `ContextWatchdog#create()` method first.");let e;if(this._watchdogs.has(t.id))throw new Error(`Item with the given id is already added: '${t.id}'.`);if("editor"===t.type)return e=new Dn(this._watchdogConfig),e.setCreator(t.creator),e._setExcludedProperties(this._contextProps),t.destructor&&e.setDestructor(t.destructor),this._watchdogs.set(t.id,e),e.on("error",((r,{error:n,causesRestart:o})=>{this._fire("itemError",{itemId:t.id,error:n}),o&&this._actionQueues.enqueue(t.id,(()=>new Promise((r=>{e.on("restart",function n(){e.off("restart",n),this._fire("itemRestart",{itemId:t.id}),r()}.bind(this))}))))})),e.create(t.sourceElementOrData,t.config,this._context);throw new Error(`Not supported item type: '${t.type}'.`)})))))}remove(t){const e=Nn(t);return Promise.all(e.map((t=>this._actionQueues.enqueue(t,(()=>{const e=this._getWatchdog(t);return this._watchdogs.delete(t),e.destroy()})))))}destroy(){return this._actionQueues.enqueue(Wn,(()=>(this.state="destroyed",this._fire("stateChange"),super.destroy(),this._destroy())))}_restart(){return this._actionQueues.enqueue(Wn,(()=>(this.state="initializing",this._fire("stateChange"),this._destroy().catch((t=>{console.error("An error happened during destroying the context or items.",t)})).then((()=>this._create())).then((()=>this._fire("restart"))))))}_create(){return Promise.resolve().then((()=>(this._startErrorHandling(),this._creator(this._contextConfig)))).then((t=>(this._context=t,this._contextProps=Tn(this._context),Promise.all(Array.from(this._watchdogs.values()).map((t=>(t._setExcludedProperties(this._contextProps),t.create(void 0,void 0,this._context))))))))}_destroy(){return Promise.resolve().then((()=>{this._stopErrorHandling();const t=this._context;return this._context=null,this._contextProps=new Set,Promise.all(Array.from(this._watchdogs.values()).map((t=>t.destroy()))).then((()=>this._destructor(t)))}))}_getWatchdog(t){const e=this._watchdogs.get(t);if(!e)throw new Error(`Item with the given id was not registered: ${t}.`);return e}_isErrorComingFromThisItem(t){for(const e of this._watchdogs.values())if(e._isErrorComingFromThisItem(t))return!1;return zn(this._context,t.context)}}class Mn{constructor(){this._onEmptyCallbacks=[],this._queues=new Map,this._actions=new WeakMap,this._lastActionId=0,this._activeActions=0}onEmpty(t){this._onEmptyCallbacks.push(t)}enqueue(t,e){const r=t===Wn;this._activeActions++,this._queues.get(t)||this._queues.set(t,Promise.resolve());const n=(r?Promise.all(this._queues.values()):Promise.all([this._queues.get(Wn),this._queues.get(t)])).then(e),o=n.catch((()=>{}));return this._queues.set(t,o),n.finally((()=>{this._activeActions--,this._queues.get(t)===o&&0===this._activeActions&&this._onEmptyCallbacks.forEach((t=>t()))}))}}function Nn(t){return Array.isArray(t)?t:[t]}(window.CKEditor5=window.CKEditor5||{}).watchdog=e})(); | ||
*/(()=>{"use strict";var t={d:(e,r)=>{for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{ContextWatchdog:()=>Wn,EditorWatchdog:()=>zn,Watchdog:()=>r});class r{constructor(t){if(this.crashes=[],this.state="initializing",this._now=Date.now,this.crashes=[],this._crashNumberLimit="number"==typeof t.crashNumberLimit?t.crashNumberLimit:3,this._minimumNonErrorTimePeriod="number"==typeof t.minimumNonErrorTimePeriod?t.minimumNonErrorTimePeriod:5e3,this._boundErrorHandler=t=>{const e="error"in t?t.error:t.reason;e instanceof Error&&this._handleError(e,t)},this._listeners={},!this._restart)throw new Error("The Watchdog class was split into the abstract `Watchdog` class and the `EditorWatchdog` class. Please, use `EditorWatchdog` if you have used the `Watchdog` class previously.")}destroy(){this._stopErrorHandling(),this._listeners={}}on(t,e){this._listeners[t]||(this._listeners[t]=[]),this._listeners[t].push(e)}off(t,e){this._listeners[t]=this._listeners[t].filter((t=>t!==e))}_fire(t,...e){const r=this._listeners[t]||[];for(const t of r)t.apply(this,[null,...e])}_startErrorHandling(){window.addEventListener("error",this._boundErrorHandler),window.addEventListener("unhandledrejection",this._boundErrorHandler)}_stopErrorHandling(){window.removeEventListener("error",this._boundErrorHandler),window.removeEventListener("unhandledrejection",this._boundErrorHandler)}_handleError(t,e){if(this._shouldReactToError(t)){this.crashes.push({message:t.message,stack:t.stack,filename:e instanceof ErrorEvent?e.filename:void 0,lineno:e instanceof ErrorEvent?e.lineno:void 0,colno:e instanceof ErrorEvent?e.colno:void 0,date:this._now()});const r=this._shouldRestart();this.state="crashed",this._fire("stateChange"),this._fire("error",{error:t,causesRestart:r}),r?this._restart():(this.state="crashedPermanently",this._fire("stateChange"))}}_shouldReactToError(t){return t.is&&t.is("CKEditorError")&&void 0!==t.context&&null!==t.context&&"ready"===this.state&&this._isErrorComingFromThisItem(t)}_shouldRestart(){if(this.crashes.length<=this._crashNumberLimit)return!0;return(this.crashes[this.crashes.length-1].date-this.crashes[this.crashes.length-1-this._crashNumberLimit].date)/this._crashNumberLimit>this._minimumNonErrorTimePeriod}}function n(t,e=new Set){const r=[t],n=new Set;let s=0;for(;r.length>s;){const t=r[s++];if(!n.has(t)&&o(t)&&!e.has(t))if(n.add(t),Symbol.iterator in t)try{for(const e of t)r.push(e)}catch(t){}else for(const e in t)"defaultValue"!==e&&r.push(t[e])}return n}function o(t){const e=Object.prototype.toString.call(t),r=typeof t;return!("number"===r||"boolean"===r||"string"===r||"symbol"===r||"function"===r||"[object Date]"===e||"[object RegExp]"===e||"[object Module]"===e||null==t||t._watchdogExcluded||t instanceof EventTarget||t instanceof Event)}function s(t,e,r=new Set){if(t===e&&("object"==typeof(o=t)&&null!==o))return!0;var o;const s=n(t,r),i=n(e,r);for(const t of s)if(i.has(t))return!0;return!1}const i=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)};const c="object"==typeof global&&global&&global.Object===Object&&global;var a="object"==typeof self&&self&&self.Object===Object&&self;const u=c||a||Function("return this")();const h=function(){return u.Date.now()};var l=/\s/;const f=function(t){for(var e=t.length;e--&&l.test(t.charAt(e)););return e};var d=/^\s+/;const _=function(t){return t?t.slice(0,f(t)+1).replace(d,""):t};const p=u.Symbol;var y=Object.prototype,v=y.hasOwnProperty,b=y.toString,g=p?p.toStringTag:void 0;const m=function(t){var e=v.call(t,g),r=t[g];try{t[g]=void 0;var n=!0}catch(t){}var o=b.call(t);return n&&(e?t[g]=r:delete t[g]),o};var j=Object.prototype.toString;const w=function(t){return j.call(t)};var E="[object Null]",x="[object Undefined]",O=p?p.toStringTag:void 0;const A=function(t){return null==t?void 0===t?x:E:O&&O in Object(t)?m(t):w(t)};const P=function(t){return null!=t&&"object"==typeof t};var S="[object Symbol]";const C=function(t){return"symbol"==typeof t||P(t)&&A(t)==S};var T=NaN,I=/^[-+]0x[0-9a-f]+$/i,D=/^0b[01]+$/i,z=/^0o[0-7]+$/i,F=parseInt;const W=function(t){if("number"==typeof t)return t;if(C(t))return T;if(i(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=i(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=_(t);var r=D.test(t);return r||z.test(t)?F(t.slice(2),r?2:8):I.test(t)?T:+t};var M="Expected a function",N=Math.max,q=Math.min;const U=function(t,e,r){var n,o,s,c,a,u,l=0,f=!1,d=!1,_=!0;if("function"!=typeof t)throw new TypeError(M);function p(e){var r=n,s=o;return n=o=void 0,l=e,c=t.apply(s,r)}function y(t){var r=t-u;return void 0===u||r>=e||r<0||d&&t-l>=s}function v(){var t=h();if(y(t))return b(t);a=setTimeout(v,function(t){var r=e-(t-u);return d?q(r,s-(t-l)):r}(t))}function b(t){return a=void 0,_&&n?p(t):(n=o=void 0,c)}function g(){var t=h(),r=y(t);if(n=arguments,o=this,u=t,r){if(void 0===a)return function(t){return l=t,a=setTimeout(v,e),f?p(t):c}(u);if(d)return clearTimeout(a),a=setTimeout(v,e),p(u)}return void 0===a&&(a=setTimeout(v,e)),c}return e=W(e)||0,i(r)&&(f=!!r.leading,s=(d="maxWait"in r)?N(W(r.maxWait)||0,e):s,_="trailing"in r?!!r.trailing:_),g.cancel=function(){void 0!==a&&clearTimeout(a),l=0,n=u=o=a=void 0},g.flush=function(){return void 0===a?c:b(h())},g};var R="Expected a function";const $=function(t,e,r){var n=!0,o=!0;if("function"!=typeof t)throw new TypeError(R);return i(r)&&(n="leading"in r?!!r.leading:n,o="trailing"in r?!!r.trailing:o),U(t,e,{leading:n,maxWait:e,trailing:o})};const L=function(){this.__data__=[],this.size=0};const k=function(t,e){return t===e||t!=t&&e!=e};const H=function(t,e){for(var r=t.length;r--;)if(k(t[r][0],e))return r;return-1};var B=Array.prototype.splice;const Q=function(t){var e=this.__data__,r=H(e,t);return!(r<0)&&(r==e.length-1?e.pop():B.call(e,r,1),--this.size,!0)};const V=function(t){var e=this.__data__,r=H(e,t);return r<0?void 0:e[r][1]};const K=function(t){return H(this.__data__,t)>-1};const G=function(t,e){var r=this.__data__,n=H(r,t);return n<0?(++this.size,r.push([t,e])):r[n][1]=e,this};function Y(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}Y.prototype.clear=L,Y.prototype.delete=Q,Y.prototype.get=V,Y.prototype.has=K,Y.prototype.set=G;const J=Y;const X=function(){this.__data__=new J,this.size=0};const Z=function(t){var e=this.__data__,r=e.delete(t);return this.size=e.size,r};const tt=function(t){return this.__data__.get(t)};const et=function(t){return this.__data__.has(t)};var rt="[object AsyncFunction]",nt="[object Function]",ot="[object GeneratorFunction]",st="[object Proxy]";const it=function(t){if(!i(t))return!1;var e=A(t);return e==nt||e==ot||e==rt||e==st};const ct=u["__core-js_shared__"];var at,ut=(at=/[^.]+$/.exec(ct&&ct.keys&&ct.keys.IE_PROTO||""))?"Symbol(src)_1."+at:"";const ht=function(t){return!!ut&&ut in t};var lt=Function.prototype.toString;const ft=function(t){if(null!=t){try{return lt.call(t)}catch(t){}try{return t+""}catch(t){}}return""};var dt=/^\[object .+?Constructor\]$/,_t=Function.prototype,pt=Object.prototype,yt=_t.toString,vt=pt.hasOwnProperty,bt=RegExp("^"+yt.call(vt).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");const gt=function(t){return!(!i(t)||ht(t))&&(it(t)?bt:dt).test(ft(t))};const mt=function(t,e){return null==t?void 0:t[e]};const jt=function(t,e){var r=mt(t,e);return gt(r)?r:void 0};const wt=jt(u,"Map");const Et=jt(Object,"create");const xt=function(){this.__data__=Et?Et(null):{},this.size=0};const Ot=function(t){var e=this.has(t)&&delete this.__data__[t];return this.size-=e?1:0,e};var At="__lodash_hash_undefined__",Pt=Object.prototype.hasOwnProperty;const St=function(t){var e=this.__data__;if(Et){var r=e[t];return r===At?void 0:r}return Pt.call(e,t)?e[t]:void 0};var Ct=Object.prototype.hasOwnProperty;const Tt=function(t){var e=this.__data__;return Et?void 0!==e[t]:Ct.call(e,t)};var It="__lodash_hash_undefined__";const Dt=function(t,e){var r=this.__data__;return this.size+=this.has(t)?0:1,r[t]=Et&&void 0===e?It:e,this};function zt(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}zt.prototype.clear=xt,zt.prototype.delete=Ot,zt.prototype.get=St,zt.prototype.has=Tt,zt.prototype.set=Dt;const Ft=zt;const Wt=function(){this.size=0,this.__data__={hash:new Ft,map:new(wt||J),string:new Ft}};const Mt=function(t){var e=typeof t;return"string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==t:null===t};const Nt=function(t,e){var r=t.__data__;return Mt(e)?r["string"==typeof e?"string":"hash"]:r.map};const qt=function(t){var e=Nt(this,t).delete(t);return this.size-=e?1:0,e};const Ut=function(t){return Nt(this,t).get(t)};const Rt=function(t){return Nt(this,t).has(t)};const $t=function(t,e){var r=Nt(this,t),n=r.size;return r.set(t,e),this.size+=r.size==n?0:1,this};function Lt(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}Lt.prototype.clear=Wt,Lt.prototype.delete=qt,Lt.prototype.get=Ut,Lt.prototype.has=Rt,Lt.prototype.set=$t;const kt=Lt;var Ht=200;const Bt=function(t,e){var r=this.__data__;if(r instanceof J){var n=r.__data__;if(!wt||n.length<Ht-1)return n.push([t,e]),this.size=++r.size,this;r=this.__data__=new kt(n)}return r.set(t,e),this.size=r.size,this};function Qt(t){var e=this.__data__=new J(t);this.size=e.size}Qt.prototype.clear=X,Qt.prototype.delete=Z,Qt.prototype.get=tt,Qt.prototype.has=et,Qt.prototype.set=Bt;const Vt=Qt;const Kt=function(t,e){for(var r=-1,n=null==t?0:t.length;++r<n&&!1!==e(t[r],r,t););return t};const Gt=function(){try{var t=jt(Object,"defineProperty");return t({},"",{}),t}catch(t){}}();const Yt=function(t,e,r){"__proto__"==e&&Gt?Gt(t,e,{configurable:!0,enumerable:!0,value:r,writable:!0}):t[e]=r};var Jt=Object.prototype.hasOwnProperty;const Xt=function(t,e,r){var n=t[e];Jt.call(t,e)&&k(n,r)&&(void 0!==r||e in t)||Yt(t,e,r)};const Zt=function(t,e,r,n){var o=!r;r||(r={});for(var s=-1,i=e.length;++s<i;){var c=e[s],a=n?n(r[c],t[c],c,r,t):void 0;void 0===a&&(a=t[c]),o?Yt(r,c,a):Xt(r,c,a)}return r};const te=function(t,e){for(var r=-1,n=Array(t);++r<t;)n[r]=e(r);return n};var ee="[object Arguments]";const re=function(t){return P(t)&&A(t)==ee};var ne=Object.prototype,oe=ne.hasOwnProperty,se=ne.propertyIsEnumerable;const ie=re(function(){return arguments}())?re:function(t){return P(t)&&oe.call(t,"callee")&&!se.call(t,"callee")};const ce=Array.isArray;const ae=function(){return!1};var ue="object"==typeof exports&&exports&&!exports.nodeType&&exports,he=ue&&"object"==typeof module&&module&&!module.nodeType&&module,le=he&&he.exports===ue?u.Buffer:void 0;const fe=(le?le.isBuffer:void 0)||ae;var de=9007199254740991,_e=/^(?:0|[1-9]\d*)$/;const pe=function(t,e){var r=typeof t;return!!(e=null==e?de:e)&&("number"==r||"symbol"!=r&&_e.test(t))&&t>-1&&t%1==0&&t<e};var ye=9007199254740991;const ve=function(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=ye};var be={};be["[object Float32Array]"]=be["[object Float64Array]"]=be["[object Int8Array]"]=be["[object Int16Array]"]=be["[object Int32Array]"]=be["[object Uint8Array]"]=be["[object Uint8ClampedArray]"]=be["[object Uint16Array]"]=be["[object Uint32Array]"]=!0,be["[object Arguments]"]=be["[object Array]"]=be["[object ArrayBuffer]"]=be["[object Boolean]"]=be["[object DataView]"]=be["[object Date]"]=be["[object Error]"]=be["[object Function]"]=be["[object Map]"]=be["[object Number]"]=be["[object Object]"]=be["[object RegExp]"]=be["[object Set]"]=be["[object String]"]=be["[object WeakMap]"]=!1;const ge=function(t){return P(t)&&ve(t.length)&&!!be[A(t)]};const me=function(t){return function(e){return t(e)}};var je="object"==typeof exports&&exports&&!exports.nodeType&&exports,we=je&&"object"==typeof module&&module&&!module.nodeType&&module,Ee=we&&we.exports===je&&c.process;const xe=function(){try{var t=we&&we.require&&we.require("util").types;return t||Ee&&Ee.binding&&Ee.binding("util")}catch(t){}}();var Oe=xe&&xe.isTypedArray;const Ae=Oe?me(Oe):ge;var Pe=Object.prototype.hasOwnProperty;const Se=function(t,e){var r=ce(t),n=!r&&ie(t),o=!r&&!n&&fe(t),s=!r&&!n&&!o&&Ae(t),i=r||n||o||s,c=i?te(t.length,String):[],a=c.length;for(var u in t)!e&&!Pe.call(t,u)||i&&("length"==u||o&&("offset"==u||"parent"==u)||s&&("buffer"==u||"byteLength"==u||"byteOffset"==u)||pe(u,a))||c.push(u);return c};var Ce=Object.prototype;const Te=function(t){var e=t&&t.constructor;return t===("function"==typeof e&&e.prototype||Ce)};const Ie=function(t,e){return function(r){return t(e(r))}};const De=Ie(Object.keys,Object);var ze=Object.prototype.hasOwnProperty;const Fe=function(t){if(!Te(t))return De(t);var e=[];for(var r in Object(t))ze.call(t,r)&&"constructor"!=r&&e.push(r);return e};const We=function(t){return null!=t&&ve(t.length)&&!it(t)};const Me=function(t){return We(t)?Se(t):Fe(t)};const Ne=function(t,e){return t&&Zt(e,Me(e),t)};const qe=function(t){var e=[];if(null!=t)for(var r in Object(t))e.push(r);return e};var Ue=Object.prototype.hasOwnProperty;const Re=function(t){if(!i(t))return qe(t);var e=Te(t),r=[];for(var n in t)("constructor"!=n||!e&&Ue.call(t,n))&&r.push(n);return r};const $e=function(t){return We(t)?Se(t,!0):Re(t)};const Le=function(t,e){return t&&Zt(e,$e(e),t)};var ke="object"==typeof exports&&exports&&!exports.nodeType&&exports,He=ke&&"object"==typeof module&&module&&!module.nodeType&&module,Be=He&&He.exports===ke?u.Buffer:void 0,Qe=Be?Be.allocUnsafe:void 0;const Ve=function(t,e){if(e)return t.slice();var r=t.length,n=Qe?Qe(r):new t.constructor(r);return t.copy(n),n};const Ke=function(t,e){var r=-1,n=t.length;for(e||(e=Array(n));++r<n;)e[r]=t[r];return e};const Ge=function(t,e){for(var r=-1,n=null==t?0:t.length,o=0,s=[];++r<n;){var i=t[r];e(i,r,t)&&(s[o++]=i)}return s};const Ye=function(){return[]};var Je=Object.prototype.propertyIsEnumerable,Xe=Object.getOwnPropertySymbols;const Ze=Xe?function(t){return null==t?[]:(t=Object(t),Ge(Xe(t),(function(e){return Je.call(t,e)})))}:Ye;const tr=function(t,e){return Zt(t,Ze(t),e)};const er=function(t,e){for(var r=-1,n=e.length,o=t.length;++r<n;)t[o+r]=e[r];return t};const rr=Ie(Object.getPrototypeOf,Object);const nr=Object.getOwnPropertySymbols?function(t){for(var e=[];t;)er(e,Ze(t)),t=rr(t);return e}:Ye;const or=function(t,e){return Zt(t,nr(t),e)};const sr=function(t,e,r){var n=e(t);return ce(t)?n:er(n,r(t))};const ir=function(t){return sr(t,Me,Ze)};const cr=function(t){return sr(t,$e,nr)};const ar=jt(u,"DataView");const ur=jt(u,"Promise");const hr=jt(u,"Set");const lr=jt(u,"WeakMap");var fr="[object Map]",dr="[object Promise]",_r="[object Set]",pr="[object WeakMap]",yr="[object DataView]",vr=ft(ar),br=ft(wt),gr=ft(ur),mr=ft(hr),jr=ft(lr),wr=A;(ar&&wr(new ar(new ArrayBuffer(1)))!=yr||wt&&wr(new wt)!=fr||ur&&wr(ur.resolve())!=dr||hr&&wr(new hr)!=_r||lr&&wr(new lr)!=pr)&&(wr=function(t){var e=A(t),r="[object Object]"==e?t.constructor:void 0,n=r?ft(r):"";if(n)switch(n){case vr:return yr;case br:return fr;case gr:return dr;case mr:return _r;case jr:return pr}return e});const Er=wr;var xr=Object.prototype.hasOwnProperty;const Or=function(t){var e=t.length,r=new t.constructor(e);return e&&"string"==typeof t[0]&&xr.call(t,"index")&&(r.index=t.index,r.input=t.input),r};const Ar=u.Uint8Array;const Pr=function(t){var e=new t.constructor(t.byteLength);return new Ar(e).set(new Ar(t)),e};const Sr=function(t,e){var r=e?Pr(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.byteLength)};var Cr=/\w*$/;const Tr=function(t){var e=new t.constructor(t.source,Cr.exec(t));return e.lastIndex=t.lastIndex,e};var Ir=p?p.prototype:void 0,Dr=Ir?Ir.valueOf:void 0;const zr=function(t){return Dr?Object(Dr.call(t)):{}};const Fr=function(t,e){var r=e?Pr(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.length)};var Wr="[object Boolean]",Mr="[object Date]",Nr="[object Map]",qr="[object Number]",Ur="[object RegExp]",Rr="[object Set]",$r="[object String]",Lr="[object Symbol]",kr="[object ArrayBuffer]",Hr="[object DataView]",Br="[object Float32Array]",Qr="[object Float64Array]",Vr="[object Int8Array]",Kr="[object Int16Array]",Gr="[object Int32Array]",Yr="[object Uint8Array]",Jr="[object Uint8ClampedArray]",Xr="[object Uint16Array]",Zr="[object Uint32Array]";const tn=function(t,e,r){var n=t.constructor;switch(e){case kr:return Pr(t);case Wr:case Mr:return new n(+t);case Hr:return Sr(t,r);case Br:case Qr:case Vr:case Kr:case Gr:case Yr:case Jr:case Xr:case Zr:return Fr(t,r);case Nr:return new n;case qr:case $r:return new n(t);case Ur:return Tr(t);case Rr:return new n;case Lr:return zr(t)}};var en=Object.create;const rn=function(){function t(){}return function(e){if(!i(e))return{};if(en)return en(e);t.prototype=e;var r=new t;return t.prototype=void 0,r}}();const nn=function(t){return"function"!=typeof t.constructor||Te(t)?{}:rn(rr(t))};var on="[object Map]";const sn=function(t){return P(t)&&Er(t)==on};var cn=xe&&xe.isMap;const an=cn?me(cn):sn;var un="[object Set]";const hn=function(t){return P(t)&&Er(t)==un};var ln=xe&&xe.isSet;const fn=ln?me(ln):hn;var dn=1,_n=2,pn=4,yn="[object Arguments]",vn="[object Function]",bn="[object GeneratorFunction]",gn="[object Object]",mn={};mn[yn]=mn["[object Array]"]=mn["[object ArrayBuffer]"]=mn["[object DataView]"]=mn["[object Boolean]"]=mn["[object Date]"]=mn["[object Float32Array]"]=mn["[object Float64Array]"]=mn["[object Int8Array]"]=mn["[object Int16Array]"]=mn["[object Int32Array]"]=mn["[object Map]"]=mn["[object Number]"]=mn[gn]=mn["[object RegExp]"]=mn["[object Set]"]=mn["[object String]"]=mn["[object Symbol]"]=mn["[object Uint8Array]"]=mn["[object Uint8ClampedArray]"]=mn["[object Uint16Array]"]=mn["[object Uint32Array]"]=!0,mn["[object Error]"]=mn[vn]=mn["[object WeakMap]"]=!1;const jn=function t(e,r,n,o,s,c){var a,u=r&dn,h=r&_n,l=r&pn;if(n&&(a=s?n(e,o,s,c):n(e)),void 0!==a)return a;if(!i(e))return e;var f=ce(e);if(f){if(a=Or(e),!u)return Ke(e,a)}else{var d=Er(e),_=d==vn||d==bn;if(fe(e))return Ve(e,u);if(d==gn||d==yn||_&&!s){if(a=h||_?{}:nn(e),!u)return h?or(e,Le(a,e)):tr(e,Ne(a,e))}else{if(!mn[d])return s?e:{};a=tn(e,d,u)}}c||(c=new Vt);var p=c.get(e);if(p)return p;c.set(e,a),fn(e)?e.forEach((function(o){a.add(t(o,r,n,o,e,c))})):an(e)&&e.forEach((function(o,s){a.set(s,t(o,r,n,s,e,c))}));var y=f?void 0:(l?h?cr:ir:h?$e:Me)(e);return Kt(y||e,(function(o,s){y&&(o=e[s=o]),Xt(a,s,t(o,r,n,s,e,c))})),a};var wn=1,En=4;const xn=function(t,e){return jn(t,wn|En,e="function"==typeof e?e:void 0)};var On="[object Object]",An=Function.prototype,Pn=Object.prototype,Sn=An.toString,Cn=Pn.hasOwnProperty,Tn=Sn.call(Object);const In=function(t){if(!P(t)||A(t)!=On)return!1;var e=rr(t);if(null===e)return!0;var r=Cn.call(e,"constructor")&&e.constructor;return"function"==typeof r&&r instanceof r&&Sn.call(r)==Tn};const Dn=function(t){return P(t)&&1===t.nodeType&&!In(t)};class zn extends r{constructor(t,e={}){super(e),this._editor=null,this._throttledSave=$(this._save.bind(this),"number"==typeof e.saveInterval?e.saveInterval:5e3),t&&(this._creator=(e,r)=>t.create(e,r)),this._destructor=t=>t.destroy()}get editor(){return this._editor}get _item(){return this._editor}setCreator(t){this._creator=t}setDestructor(t){this._destructor=t}_restart(){return Promise.resolve().then((()=>(this.state="initializing",this._fire("stateChange"),this._destroy()))).catch((t=>{console.error("An error happened during the editor destroying.",t)})).then((()=>{if("string"==typeof this._elementOrData)return this.create(this._data,this._config,this._config.context);{const t=Object.assign({},this._config,{initialData:this._data});return this.create(this._elementOrData,t,t.context)}})).then((()=>{this._fire("restart")}))}create(t=this._elementOrData,e=this._config,r){return Promise.resolve().then((()=>(super._startErrorHandling(),this._elementOrData=t,this._config=this._cloneEditorConfiguration(e)||{},this._config.context=r,this._creator(t,this._config)))).then((t=>{this._editor=t,t.model.document.on("change:data",this._throttledSave),this._lastDocumentVersion=t.model.document.version,this._data=this._getData(),this.state="ready",this._fire("stateChange")}))}destroy(){return Promise.resolve().then((()=>(this.state="destroyed",this._fire("stateChange"),super.destroy(),this._destroy())))}_destroy(){return Promise.resolve().then((()=>{this._stopErrorHandling(),this._throttledSave.flush();const t=this._editor;return this._editor=null,t.model.document.off("change:data",this._throttledSave),this._destructor(t)}))}_save(){const t=this._editor.model.document.version;try{this._data=this._getData(),this._lastDocumentVersion=t}catch(t){console.error(t,"An error happened during restoring editor data. Editor will be restored from the previously saved data.")}}_setExcludedProperties(t){this._excludedProps=t}_getData(){const t={};for(const e of this._editor.model.document.getRootNames())t[e]=this._editor.data.get({rootName:e});return t}_isErrorComingFromThisItem(t){return s(this._editor,t.context,this._excludedProps)}_cloneEditorConfiguration(t){return xn(t,((t,e)=>Dn(t)||"context"===e?t:void 0))}}const Fn=Symbol("MainQueueId");class Wn extends r{constructor(t,e={}){super(e),this._watchdogs=new Map,this._context=null,this._contextProps=new Set,this._actionQueues=new Mn,this._watchdogConfig=e,this._creator=e=>t.create(e),this._destructor=t=>t.destroy(),this._actionQueues.onEmpty((()=>{"initializing"===this.state&&(this.state="ready",this._fire("stateChange"))}))}setCreator(t){this._creator=t}setDestructor(t){this._destructor=t}get context(){return this._context}create(t={}){return this._actionQueues.enqueue(Fn,(()=>(this._contextConfig=t,this._create())))}getItem(t){return this._getWatchdog(t)._item}getItemState(t){return this._getWatchdog(t).state}add(t){const e=Nn(t);return Promise.all(e.map((t=>this._actionQueues.enqueue(t.id,(()=>{if("destroyed"===this.state)throw new Error("Cannot add items to destroyed watchdog.");if(!this._context)throw new Error("Context was not created yet. You should call the `ContextWatchdog#create()` method first.");let e;if(this._watchdogs.has(t.id))throw new Error(`Item with the given id is already added: '${t.id}'.`);if("editor"===t.type)return e=new zn(null,this._watchdogConfig),e.setCreator(t.creator),e._setExcludedProperties(this._contextProps),t.destructor&&e.setDestructor(t.destructor),this._watchdogs.set(t.id,e),e.on("error",((r,{error:n,causesRestart:o})=>{this._fire("itemError",{itemId:t.id,error:n}),o&&this._actionQueues.enqueue(t.id,(()=>new Promise((r=>{const n=()=>{e.off("restart",n),this._fire("itemRestart",{itemId:t.id}),r()};e.on("restart",n)}))))})),e.create(t.sourceElementOrData,t.config,this._context);throw new Error(`Not supported item type: '${t.type}'.`)})))))}remove(t){const e=Nn(t);return Promise.all(e.map((t=>this._actionQueues.enqueue(t,(()=>{const e=this._getWatchdog(t);return this._watchdogs.delete(t),e.destroy()})))))}destroy(){return this._actionQueues.enqueue(Fn,(()=>(this.state="destroyed",this._fire("stateChange"),super.destroy(),this._destroy())))}_restart(){return this._actionQueues.enqueue(Fn,(()=>(this.state="initializing",this._fire("stateChange"),this._destroy().catch((t=>{console.error("An error happened during destroying the context or items.",t)})).then((()=>this._create())).then((()=>this._fire("restart"))))))}_create(){return Promise.resolve().then((()=>(this._startErrorHandling(),this._creator(this._contextConfig)))).then((t=>(this._context=t,this._contextProps=n(this._context),Promise.all(Array.from(this._watchdogs.values()).map((t=>(t._setExcludedProperties(this._contextProps),t.create(void 0,void 0,this._context))))))))}_destroy(){return Promise.resolve().then((()=>{this._stopErrorHandling();const t=this._context;return this._context=null,this._contextProps=new Set,Promise.all(Array.from(this._watchdogs.values()).map((t=>t.destroy()))).then((()=>this._destructor(t)))}))}_getWatchdog(t){const e=this._watchdogs.get(t);if(!e)throw new Error(`Item with the given id was not registered: ${t}.`);return e}_isErrorComingFromThisItem(t){for(const e of this._watchdogs.values())if(e._isErrorComingFromThisItem(t))return!1;return s(this._context,t.context)}}class Mn{constructor(){this._onEmptyCallbacks=[],this._queues=new Map,this._activeActions=0}onEmpty(t){this._onEmptyCallbacks.push(t)}enqueue(t,e){const r=t===Fn;this._activeActions++,this._queues.get(t)||this._queues.set(t,Promise.resolve());const n=(r?Promise.all(this._queues.values()):Promise.all([this._queues.get(Fn),this._queues.get(t)])).then(e),o=n.catch((()=>{}));return this._queues.set(t,o),n.finally((()=>{this._activeActions--,this._queues.get(t)===o&&0===this._activeActions&&this._onEmptyCallbacks.forEach((t=>t()))}))}}function Nn(t){return Array.isArray(t)?t:[t]}(window.CKEditor5=window.CKEditor5||{}).watchdog=e})(); |
{ | ||
"name": "@ckeditor/ckeditor5-watchdog", | ||
"version": "36.0.1", | ||
"version": "37.0.0-alpha.0", | ||
"description": "A watchdog feature for CKEditor 5 editors. It keeps a CKEditor 5 editor instance running.", | ||
@@ -17,9 +17,10 @@ "keywords": [ | ||
"devDependencies": { | ||
"@ckeditor/ckeditor5-core": "^36.0.1", | ||
"@ckeditor/ckeditor5-dev-utils": "^32.0.0", | ||
"@ckeditor/ckeditor5-editor-classic": "^36.0.1", | ||
"@ckeditor/ckeditor5-paragraph": "^36.0.1", | ||
"@ckeditor/ckeditor5-theme-lark": "^36.0.1", | ||
"@ckeditor/ckeditor5-utils": "^36.0.1", | ||
"ckeditor5": "^36.0.1", | ||
"@ckeditor/ckeditor5-core": "^37.0.0-alpha.0", | ||
"@ckeditor/ckeditor5-dev-utils": "^34.0.0", | ||
"@ckeditor/ckeditor5-editor-classic": "^37.0.0-alpha.0", | ||
"@ckeditor/ckeditor5-paragraph": "^37.0.0-alpha.0", | ||
"@ckeditor/ckeditor5-theme-lark": "^37.0.0-alpha.0", | ||
"@ckeditor/ckeditor5-utils": "^37.0.0-alpha.0", | ||
"ckeditor5": "^37.0.0-alpha.0", | ||
"typescript": "^4.8.4", | ||
"webpack": "^5.58.1", | ||
@@ -43,3 +44,4 @@ "webpack-cli": "^4.9.0" | ||
"lang", | ||
"src", | ||
"src/**/*.js", | ||
"src/**/*.d.ts", | ||
"theme", | ||
@@ -51,4 +53,7 @@ "build", | ||
"scripts": { | ||
"dll:build": "webpack" | ||
} | ||
"dll:build": "webpack", | ||
"build": "tsc -p ./tsconfig.release.json", | ||
"postversion": "npm run build" | ||
}, | ||
"types": "src/index.d.ts" | ||
} |
@@ -5,9 +5,2 @@ /** | ||
*/ | ||
/** | ||
* @module watchdog/contextwatchdog | ||
*/ | ||
/* globals console */ | ||
import Watchdog from './watchdog'; | ||
@@ -17,5 +10,3 @@ import EditorWatchdog from './editorwatchdog'; | ||
import getSubNodes from './utils/getsubnodes'; | ||
const mainQueueId = Symbol( 'MainQueueId' ); | ||
const mainQueueId = Symbol('MainQueueId'); | ||
/** | ||
@@ -26,546 +17,397 @@ * A watchdog for the {@link module:core/context~Context} class. | ||
* how to use it. | ||
* | ||
* @extends {module:watchdog/watchdog~Watchdog} | ||
*/ | ||
export default class ContextWatchdog extends Watchdog { | ||
/** | ||
* The context watchdog class constructor. | ||
* | ||
* const watchdog = new ContextWatchdog( Context ); | ||
* | ||
* await watchdog.create( contextConfiguration ); | ||
* | ||
* await watchdog.add( item ); | ||
* | ||
* See the {@glink features/watchdog Watchdog feature guide} to learn more how to use this feature. | ||
* | ||
* @param {Function} Context The {@link module:core/context~Context} class. | ||
* @param {module:watchdog/watchdog~WatchdogConfig} [watchdogConfig] The watchdog configuration. | ||
*/ | ||
constructor( Context, watchdogConfig = {} ) { | ||
super( watchdogConfig ); | ||
/** | ||
* A map of internal watchdogs for added items. | ||
* | ||
* @protected | ||
* @type {Map.<string,module:watchdog/watchdog~EditorWatchdog>} | ||
*/ | ||
this._watchdogs = new Map(); | ||
/** | ||
* The watchdog configuration. | ||
* | ||
* @private | ||
* @type {module:watchdog/watchdog~WatchdogConfig} | ||
*/ | ||
this._watchdogConfig = watchdogConfig; | ||
/** | ||
* The current context instance. | ||
* | ||
* @private | ||
* @type {module:core/context~Context|null} | ||
*/ | ||
this._context = null; | ||
/** | ||
* Context properties (nodes/references) that are gathered during the initial context creation | ||
* and are used to distinguish the origin of an error. | ||
* | ||
* @private | ||
* @type {Set.<*>} | ||
*/ | ||
this._contextProps = new Set(); | ||
/** | ||
* An action queue, which is used to handle async functions queuing. | ||
* | ||
* @private | ||
* @type {ActionQueues} | ||
*/ | ||
this._actionQueues = new ActionQueues(); | ||
/** | ||
* The configuration for the {@link module:core/context~Context}. | ||
* | ||
* @private | ||
* @member {Object} #_contextConfig | ||
*/ | ||
/** | ||
* The context configuration. | ||
* | ||
* @private | ||
* @member {Object|undefined} #_config | ||
*/ | ||
// Default creator and destructor. | ||
this._creator = contextConfig => Context.create( contextConfig ); | ||
this._destructor = context => context.destroy(); | ||
this._actionQueues.onEmpty( () => { | ||
if ( this.state === 'initializing' ) { | ||
this.state = 'ready'; | ||
this._fire( 'stateChange' ); | ||
} | ||
} ); | ||
/** | ||
* Sets the function that is responsible for the context creation. | ||
* It expects a function that should return a promise (or `undefined`). | ||
* | ||
* watchdog.setCreator( config => Context.create( config ) ); | ||
* | ||
* @method #setCreator | ||
* @param {Function} creator | ||
*/ | ||
/** | ||
* Sets the function that is responsible for the context destruction. | ||
* Overrides the default destruction function, which destroys only the context instance. | ||
* It expects a function that should return a promise (or `undefined`). | ||
* | ||
* watchdog.setDestructor( context => { | ||
* // Do something before the context is destroyed. | ||
* | ||
* return context | ||
* .destroy() | ||
* .then( () => { | ||
* // Do something after the context is destroyed. | ||
* } ); | ||
* } ); | ||
* | ||
* @method #setDestructor | ||
* @param {Function} destructor | ||
*/ | ||
} | ||
/** | ||
* The context instance. Keep in mind that this property might be changed when the context watchdog restarts, | ||
* so do not keep this instance internally. Always operate on the `ContextWatchdog#context` property. | ||
* | ||
* @type {module:core/context~Context|null} | ||
*/ | ||
get context() { | ||
return this._context; | ||
} | ||
/** | ||
* Initializes the context watchdog. Once it is created, the watchdog takes care about | ||
* recreating the context and the provided items, and starts the error handling mechanism. | ||
* | ||
* await watchdog.create( { | ||
* plugins: [] | ||
* } ); | ||
* | ||
* @param {Object} [contextConfig] The context configuration. See {@link module:core/context~Context}. | ||
* @returns {Promise} | ||
*/ | ||
create( contextConfig = {} ) { | ||
return this._actionQueues.enqueue( mainQueueId, () => { | ||
this._contextConfig = contextConfig; | ||
return this._create(); | ||
} ); | ||
} | ||
/** | ||
* Returns an item instance with the given `itemId`. | ||
* | ||
* const editor1 = watchdog.getItem( 'editor1' ); | ||
* | ||
* @param {String} itemId The item ID. | ||
* @returns {*} The item instance or `undefined` if an item with a given ID has not been found. | ||
*/ | ||
getItem( itemId ) { | ||
const watchdog = this._getWatchdog( itemId ); | ||
return watchdog._item; | ||
} | ||
/** | ||
* Gets the state of the given item. See {@link #state} for a list of available states. | ||
* | ||
* const editor1State = watchdog.getItemState( 'editor1' ); | ||
* | ||
* @param {String} itemId Item ID. | ||
* @returns {'initializing'|'ready'|'crashed'|'crashedPermanently'|'destroyed'} The state of the item. | ||
*/ | ||
getItemState( itemId ) { | ||
const watchdog = this._getWatchdog( itemId ); | ||
return watchdog.state; | ||
} | ||
/** | ||
* Adds items to the watchdog. Once created, instances of these items will be available using the {@link #getItem} method. | ||
* | ||
* Items can be passed together as an array of objects: | ||
* | ||
* await watchdog.add( [ { | ||
* id: 'editor1', | ||
* type: 'editor', | ||
* sourceElementOrData: document.querySelector( '#editor' ), | ||
* config: { | ||
* plugins: [ Essentials, Paragraph, Bold, Italic ], | ||
* toolbar: [ 'bold', 'italic', 'alignment' ] | ||
* }, | ||
* creator: ( element, config ) => ClassicEditor.create( element, config ) | ||
* } ] ); | ||
* | ||
* Or one by one as objects: | ||
* | ||
* await watchdog.add( { | ||
* id: 'editor1', | ||
* type: 'editor', | ||
* sourceElementOrData: document.querySelector( '#editor' ), | ||
* config: { | ||
* plugins: [ Essentials, Paragraph, Bold, Italic ], | ||
* toolbar: [ 'bold', 'italic', 'alignment' ] | ||
* }, | ||
* creator: ( element, config ) => ClassicEditor.create( element, config ) | ||
* ] ); | ||
* | ||
* Then an instance can be retrieved using the {@link #getItem} method: | ||
* | ||
* const editor1 = watchdog.getItem( 'editor1' ); | ||
* | ||
* Note that this method can be called multiple times, but for performance reasons it is better | ||
* to pass all items together. | ||
* | ||
* @param {module:watchdog/contextwatchdog~WatchdogItemConfiguration|Array.<module:watchdog/contextwatchdog~WatchdogItemConfiguration>} | ||
* itemConfigurationOrItemConfigurations An item configuration object or an array of item configurations. | ||
* @returns {Promise} | ||
*/ | ||
add( itemConfigurationOrItemConfigurations ) { | ||
const itemConfigurations = toArray( itemConfigurationOrItemConfigurations ); | ||
return Promise.all( itemConfigurations.map( item => { | ||
return this._actionQueues.enqueue( item.id, () => { | ||
if ( this.state === 'destroyed' ) { | ||
throw new Error( 'Cannot add items to destroyed watchdog.' ); | ||
} | ||
if ( !this._context ) { | ||
throw new Error( 'Context was not created yet. You should call the `ContextWatchdog#create()` method first.' ); | ||
} | ||
let watchdog; | ||
if ( this._watchdogs.has( item.id ) ) { | ||
throw new Error( `Item with the given id is already added: '${ item.id }'.` ); | ||
} | ||
if ( item.type === 'editor' ) { | ||
watchdog = new EditorWatchdog( this._watchdogConfig ); | ||
watchdog.setCreator( item.creator ); | ||
watchdog._setExcludedProperties( this._contextProps ); | ||
if ( item.destructor ) { | ||
watchdog.setDestructor( item.destructor ); | ||
} | ||
this._watchdogs.set( item.id, watchdog ); | ||
// Enqueue the internal watchdog errors within the main queue. | ||
// And propagate the internal `error` events as `itemError` event. | ||
watchdog.on( 'error', ( evt, { error, causesRestart } ) => { | ||
this._fire( 'itemError', { itemId: item.id, error } ); | ||
// Do not enqueue the item restart action if the item will not restart. | ||
if ( !causesRestart ) { | ||
return; | ||
} | ||
this._actionQueues.enqueue( item.id, () => new Promise( res => { | ||
watchdog.on( 'restart', rethrowRestartEventOnce.bind( this ) ); | ||
function rethrowRestartEventOnce() { | ||
watchdog.off( 'restart', rethrowRestartEventOnce ); | ||
this._fire( 'itemRestart', { itemId: item.id } ); | ||
res(); | ||
} | ||
} ) ); | ||
} ); | ||
return watchdog.create( item.sourceElementOrData, item.config, this._context ); | ||
} else { | ||
throw new Error( `Not supported item type: '${ item.type }'.` ); | ||
} | ||
} ); | ||
} ) ); | ||
} | ||
/** | ||
* Removes and destroys item(s) with given ID(s). | ||
* | ||
* await watchdog.remove( 'editor1' ); | ||
* | ||
* Or | ||
* | ||
* await watchdog.remove( [ 'editor1', 'editor2' ] ); | ||
* | ||
* @param {Array.<String>|String} itemIdOrItemIds Item ID or an array of item IDs. | ||
* @returns {Promise} | ||
*/ | ||
remove( itemIdOrItemIds ) { | ||
const itemIds = toArray( itemIdOrItemIds ); | ||
return Promise.all( itemIds.map( itemId => { | ||
return this._actionQueues.enqueue( itemId, () => { | ||
const watchdog = this._getWatchdog( itemId ); | ||
this._watchdogs.delete( itemId ); | ||
return watchdog.destroy(); | ||
} ); | ||
} ) ); | ||
} | ||
/** | ||
* Destroys the context watchdog and all added items. | ||
* Once the context watchdog is destroyed, new items cannot be added. | ||
* | ||
* await watchdog.destroy(); | ||
* | ||
* @returns {Promise} | ||
*/ | ||
destroy() { | ||
return this._actionQueues.enqueue( mainQueueId, () => { | ||
this.state = 'destroyed'; | ||
this._fire( 'stateChange' ); | ||
super.destroy(); | ||
return this._destroy(); | ||
} ); | ||
} | ||
/** | ||
* Restarts the context watchdog. | ||
* | ||
* @protected | ||
* @returns {Promise} | ||
*/ | ||
_restart() { | ||
return this._actionQueues.enqueue( mainQueueId, () => { | ||
this.state = 'initializing'; | ||
this._fire( 'stateChange' ); | ||
return this._destroy() | ||
.catch( err => { | ||
console.error( 'An error happened during destroying the context or items.', err ); | ||
} ) | ||
.then( () => this._create() ) | ||
.then( () => this._fire( 'restart' ) ); | ||
} ); | ||
} | ||
/** | ||
* @private | ||
* @returns {Promise} | ||
*/ | ||
_create() { | ||
return Promise.resolve() | ||
.then( () => { | ||
this._startErrorHandling(); | ||
return this._creator( this._contextConfig ); | ||
} ) | ||
.then( context => { | ||
this._context = context; | ||
this._contextProps = getSubNodes( this._context ); | ||
return Promise.all( | ||
Array.from( this._watchdogs.values() ) | ||
.map( watchdog => { | ||
watchdog._setExcludedProperties( this._contextProps ); | ||
return watchdog.create( undefined, undefined, this._context ); | ||
} ) | ||
); | ||
} ); | ||
} | ||
/** | ||
* Destroys the context instance and all added items. | ||
* | ||
* @private | ||
* @returns {Promise} | ||
*/ | ||
_destroy() { | ||
return Promise.resolve() | ||
.then( () => { | ||
this._stopErrorHandling(); | ||
const context = this._context; | ||
this._context = null; | ||
this._contextProps = new Set(); | ||
return Promise.all( | ||
Array.from( this._watchdogs.values() ) | ||
.map( watchdog => watchdog.destroy() ) | ||
) | ||
// Context destructor destroys each editor. | ||
.then( () => this._destructor( context ) ); | ||
} ); | ||
} | ||
/** | ||
* Returns the watchdog for a given item ID. | ||
* | ||
* @protected | ||
* @param {String} itemId Item ID. | ||
* @returns {module:watchdog/watchdog~Watchdog} Watchdog | ||
*/ | ||
_getWatchdog( itemId ) { | ||
const watchdog = this._watchdogs.get( itemId ); | ||
if ( !watchdog ) { | ||
throw new Error( `Item with the given id was not registered: ${ itemId }.` ); | ||
} | ||
return watchdog; | ||
} | ||
/** | ||
* Checks whether an error comes from the context instance and not from the item instances. | ||
* | ||
* @protected | ||
* @param {Error} error | ||
* @returns {Boolean} | ||
*/ | ||
_isErrorComingFromThisItem( error ) { | ||
for ( const watchdog of this._watchdogs.values() ) { | ||
if ( watchdog._isErrorComingFromThisItem( error ) ) { | ||
return false; | ||
} | ||
} | ||
return areConnectedThroughProperties( this._context, error.context ); | ||
} | ||
/** | ||
* Fired after the watchdog restarts the context and the added items because of a crash. | ||
* | ||
* watchdog.on( 'restart', () => { | ||
* console.log( 'The context has been restarted.' ); | ||
* } ); | ||
* | ||
* @event restart | ||
*/ | ||
/** | ||
* Fired when a new error occurred in one of the added items. | ||
* | ||
* watchdog.on( 'itemError', ( evt, { error, itemId, causesRestart } ) => { | ||
* console.log( `An error occurred in an item with the '${ itemId }' ID.` ); | ||
* } ); | ||
* | ||
* @event itemError | ||
*/ | ||
/** | ||
* Fired after an item has been restarted. | ||
* | ||
* watchdog.on( 'itemRestart', ( evt, { itemId } ) => { | ||
* console.log( 'An item with with the '${ itemId }' ID has been restarted.' ); | ||
* } ); | ||
* | ||
* @event itemRestart | ||
*/ | ||
/** | ||
* The context watchdog class constructor. | ||
* | ||
* ```ts | ||
* const watchdog = new ContextWatchdog( Context ); | ||
* | ||
* await watchdog.create( contextConfiguration ); | ||
* | ||
* await watchdog.add( item ); | ||
* ``` | ||
* | ||
* See the {@glink features/watchdog Watchdog feature guide} to learn more how to use this feature. | ||
* | ||
* @param Context The {@link module:core/context~Context} class. | ||
* @param watchdogConfig The watchdog configuration. | ||
*/ | ||
constructor(Context, watchdogConfig = {}) { | ||
super(watchdogConfig); | ||
/** | ||
* A map of internal watchdogs for added items. | ||
*/ | ||
this._watchdogs = new Map(); | ||
/** | ||
* The current context instance. | ||
*/ | ||
this._context = null; | ||
/** | ||
* Context properties (nodes/references) that are gathered during the initial context creation | ||
* and are used to distinguish the origin of an error. | ||
*/ | ||
this._contextProps = new Set(); | ||
/** | ||
* An action queue, which is used to handle async functions queuing. | ||
*/ | ||
this._actionQueues = new ActionQueues(); | ||
this._watchdogConfig = watchdogConfig; | ||
// Default creator and destructor. | ||
this._creator = contextConfig => Context.create(contextConfig); | ||
this._destructor = context => context.destroy(); | ||
this._actionQueues.onEmpty(() => { | ||
if (this.state === 'initializing') { | ||
this.state = 'ready'; | ||
this._fire('stateChange'); | ||
} | ||
}); | ||
} | ||
/** | ||
* Sets the function that is responsible for the context creation. | ||
* It expects a function that should return a promise (or `undefined`). | ||
* | ||
* ```ts | ||
* watchdog.setCreator( config => Context.create( config ) ); | ||
* ``` | ||
*/ | ||
setCreator(creator) { | ||
this._creator = creator; | ||
} | ||
/** | ||
* Sets the function that is responsible for the context destruction. | ||
* Overrides the default destruction function, which destroys only the context instance. | ||
* It expects a function that should return a promise (or `undefined`). | ||
* | ||
* ```ts | ||
* watchdog.setDestructor( context => { | ||
* // Do something before the context is destroyed. | ||
* | ||
* return context | ||
* .destroy() | ||
* .then( () => { | ||
* // Do something after the context is destroyed. | ||
* } ); | ||
* } ); | ||
* ``` | ||
*/ | ||
setDestructor(destructor) { | ||
this._destructor = destructor; | ||
} | ||
/** | ||
* The context instance. Keep in mind that this property might be changed when the context watchdog restarts, | ||
* so do not keep this instance internally. Always operate on the `ContextWatchdog#context` property. | ||
*/ | ||
get context() { | ||
return this._context; | ||
} | ||
/** | ||
* Initializes the context watchdog. Once it is created, the watchdog takes care about | ||
* recreating the context and the provided items, and starts the error handling mechanism. | ||
* | ||
* ```ts | ||
* await watchdog.create( { | ||
* plugins: [] | ||
* } ); | ||
* ``` | ||
* | ||
* @param contextConfig The context configuration. See {@link module:core/context~Context}. | ||
*/ | ||
create(contextConfig = {}) { | ||
return this._actionQueues.enqueue(mainQueueId, () => { | ||
this._contextConfig = contextConfig; | ||
return this._create(); | ||
}); | ||
} | ||
/** | ||
* Returns an item instance with the given `itemId`. | ||
* | ||
* ```ts | ||
* const editor1 = watchdog.getItem( 'editor1' ); | ||
* ``` | ||
* | ||
* @param itemId The item ID. | ||
* @returns The item instance or `undefined` if an item with a given ID has not been found. | ||
*/ | ||
getItem(itemId) { | ||
const watchdog = this._getWatchdog(itemId); | ||
return watchdog._item; | ||
} | ||
/** | ||
* Gets the state of the given item. See {@link #state} for a list of available states. | ||
* | ||
* ```ts | ||
* const editor1State = watchdog.getItemState( 'editor1' ); | ||
* ``` | ||
* | ||
* @param itemId Item ID. | ||
* @returns The state of the item. | ||
*/ | ||
getItemState(itemId) { | ||
const watchdog = this._getWatchdog(itemId); | ||
return watchdog.state; | ||
} | ||
/** | ||
* Adds items to the watchdog. Once created, instances of these items will be available using the {@link #getItem} method. | ||
* | ||
* Items can be passed together as an array of objects: | ||
* | ||
* ```ts | ||
* await watchdog.add( [ { | ||
* id: 'editor1', | ||
* type: 'editor', | ||
* sourceElementOrData: document.querySelector( '#editor' ), | ||
* config: { | ||
* plugins: [ Essentials, Paragraph, Bold, Italic ], | ||
* toolbar: [ 'bold', 'italic', 'alignment' ] | ||
* }, | ||
* creator: ( element, config ) => ClassicEditor.create( element, config ) | ||
* } ] ); | ||
* ``` | ||
* | ||
* Or one by one as objects: | ||
* | ||
* ```ts | ||
* await watchdog.add( { | ||
* id: 'editor1', | ||
* type: 'editor', | ||
* sourceElementOrData: document.querySelector( '#editor' ), | ||
* config: { | ||
* plugins: [ Essentials, Paragraph, Bold, Italic ], | ||
* toolbar: [ 'bold', 'italic', 'alignment' ] | ||
* }, | ||
* creator: ( element, config ) => ClassicEditor.create( element, config ) | ||
* ] ); | ||
* ``` | ||
* | ||
* Then an instance can be retrieved using the {@link #getItem} method: | ||
* | ||
* ```ts | ||
* const editor1 = watchdog.getItem( 'editor1' ); | ||
* ``` | ||
* | ||
* Note that this method can be called multiple times, but for performance reasons it is better | ||
* to pass all items together. | ||
* | ||
* @param itemConfigurationOrItemConfigurations An item configuration object or an array of item configurations. | ||
*/ | ||
add(itemConfigurationOrItemConfigurations) { | ||
const itemConfigurations = toArray(itemConfigurationOrItemConfigurations); | ||
return Promise.all(itemConfigurations.map(item => { | ||
return this._actionQueues.enqueue(item.id, () => { | ||
if (this.state === 'destroyed') { | ||
throw new Error('Cannot add items to destroyed watchdog.'); | ||
} | ||
if (!this._context) { | ||
throw new Error('Context was not created yet. You should call the `ContextWatchdog#create()` method first.'); | ||
} | ||
let watchdog; | ||
if (this._watchdogs.has(item.id)) { | ||
throw new Error(`Item with the given id is already added: '${item.id}'.`); | ||
} | ||
if (item.type === 'editor') { | ||
watchdog = new EditorWatchdog(null, this._watchdogConfig); | ||
watchdog.setCreator(item.creator); | ||
watchdog._setExcludedProperties(this._contextProps); | ||
if (item.destructor) { | ||
watchdog.setDestructor(item.destructor); | ||
} | ||
this._watchdogs.set(item.id, watchdog); | ||
// Enqueue the internal watchdog errors within the main queue. | ||
// And propagate the internal `error` events as `itemError` event. | ||
watchdog.on('error', (evt, { error, causesRestart }) => { | ||
this._fire('itemError', { itemId: item.id, error }); | ||
// Do not enqueue the item restart action if the item will not restart. | ||
if (!causesRestart) { | ||
return; | ||
} | ||
this._actionQueues.enqueue(item.id, () => new Promise(res => { | ||
const rethrowRestartEventOnce = () => { | ||
watchdog.off('restart', rethrowRestartEventOnce); | ||
this._fire('itemRestart', { itemId: item.id }); | ||
res(); | ||
}; | ||
watchdog.on('restart', rethrowRestartEventOnce); | ||
})); | ||
}); | ||
return watchdog.create(item.sourceElementOrData, item.config, this._context); | ||
} | ||
else { | ||
throw new Error(`Not supported item type: '${item.type}'.`); | ||
} | ||
}); | ||
})); | ||
} | ||
/** | ||
* Removes and destroys item(s) with given ID(s). | ||
* | ||
* ```ts | ||
* await watchdog.remove( 'editor1' ); | ||
* ``` | ||
* | ||
* Or | ||
* | ||
* ```ts | ||
* await watchdog.remove( [ 'editor1', 'editor2' ] ); | ||
* ``` | ||
* | ||
* @param itemIdOrItemIds Item ID or an array of item IDs. | ||
*/ | ||
remove(itemIdOrItemIds) { | ||
const itemIds = toArray(itemIdOrItemIds); | ||
return Promise.all(itemIds.map(itemId => { | ||
return this._actionQueues.enqueue(itemId, () => { | ||
const watchdog = this._getWatchdog(itemId); | ||
this._watchdogs.delete(itemId); | ||
return watchdog.destroy(); | ||
}); | ||
})); | ||
} | ||
/** | ||
* Destroys the context watchdog and all added items. | ||
* Once the context watchdog is destroyed, new items cannot be added. | ||
* | ||
* ```ts | ||
* await watchdog.destroy(); | ||
* ``` | ||
*/ | ||
destroy() { | ||
return this._actionQueues.enqueue(mainQueueId, () => { | ||
this.state = 'destroyed'; | ||
this._fire('stateChange'); | ||
super.destroy(); | ||
return this._destroy(); | ||
}); | ||
} | ||
/** | ||
* Restarts the context watchdog. | ||
*/ | ||
_restart() { | ||
return this._actionQueues.enqueue(mainQueueId, () => { | ||
this.state = 'initializing'; | ||
this._fire('stateChange'); | ||
return this._destroy() | ||
.catch(err => { | ||
console.error('An error happened during destroying the context or items.', err); | ||
}) | ||
.then(() => this._create()) | ||
.then(() => this._fire('restart')); | ||
}); | ||
} | ||
/** | ||
* Initializes the context watchdog. | ||
*/ | ||
_create() { | ||
return Promise.resolve() | ||
.then(() => { | ||
this._startErrorHandling(); | ||
return this._creator(this._contextConfig); | ||
}) | ||
.then(context => { | ||
this._context = context; | ||
this._contextProps = getSubNodes(this._context); | ||
return Promise.all(Array.from(this._watchdogs.values()) | ||
.map(watchdog => { | ||
watchdog._setExcludedProperties(this._contextProps); | ||
return watchdog.create(undefined, undefined, this._context); | ||
})); | ||
}); | ||
} | ||
/** | ||
* Destroys the context instance and all added items. | ||
*/ | ||
_destroy() { | ||
return Promise.resolve() | ||
.then(() => { | ||
this._stopErrorHandling(); | ||
const context = this._context; | ||
this._context = null; | ||
this._contextProps = new Set(); | ||
return Promise.all(Array.from(this._watchdogs.values()) | ||
.map(watchdog => watchdog.destroy())) | ||
// Context destructor destroys each editor. | ||
.then(() => this._destructor(context)); | ||
}); | ||
} | ||
/** | ||
* Returns the watchdog for a given item ID. | ||
* | ||
* @param itemId Item ID. | ||
*/ | ||
_getWatchdog(itemId) { | ||
const watchdog = this._watchdogs.get(itemId); | ||
if (!watchdog) { | ||
throw new Error(`Item with the given id was not registered: ${itemId}.`); | ||
} | ||
return watchdog; | ||
} | ||
/** | ||
* Checks whether an error comes from the context instance and not from the item instances. | ||
* | ||
* @internal | ||
*/ | ||
_isErrorComingFromThisItem(error) { | ||
for (const watchdog of this._watchdogs.values()) { | ||
if (watchdog._isErrorComingFromThisItem(error)) { | ||
return false; | ||
} | ||
} | ||
return areConnectedThroughProperties(this._context, error.context); | ||
} | ||
} | ||
// Manager of action queues that allows queuing async functions. | ||
/** | ||
* Manager of action queues that allows queuing async functions. | ||
*/ | ||
class ActionQueues { | ||
constructor() { | ||
// @type {Array.<Function>} | ||
this._onEmptyCallbacks = []; | ||
// @type {Map.<Promise>} | ||
this._queues = new Map(); | ||
this._actions = new WeakMap(); | ||
this._lastActionId = 0; | ||
this._activeActions = 0; | ||
} | ||
// Used to register callbacks that will be run when the queue becomes empty. | ||
// | ||
// @param {Function} onEmptyCallback A callback that will be run whenever the queue becomes empty. | ||
onEmpty( onEmptyCallback ) { | ||
this._onEmptyCallbacks.push( onEmptyCallback ); | ||
} | ||
// It adds asynchronous actions (functions) to the proper queue and runs them one by one. | ||
// | ||
// @param {Symbol|String|Number} queueId The action queue ID. | ||
// @param {Function} action A function that should be enqueued. | ||
// @returns {Promise} | ||
enqueue( queueId, action ) { | ||
const isMainAction = queueId === mainQueueId; | ||
this._activeActions++; | ||
if ( !this._queues.get( queueId ) ) { | ||
this._queues.set( queueId, Promise.resolve() ); | ||
} | ||
// List all sources of actions that the current action needs to await for. | ||
// For the main action wait for all other actions. | ||
// For the item action wait only for the item queue and the main queue. | ||
const awaitedActions = isMainAction ? | ||
Promise.all( this._queues.values() ) : | ||
Promise.all( [ this._queues.get( mainQueueId ), this._queues.get( queueId ) ] ); | ||
const queueWithAction = awaitedActions.then( action ); | ||
// Catch all errors in the main queue to stack promises even if an error occurred in the past. | ||
const nonErrorQueue = queueWithAction.catch( () => {} ); | ||
this._queues.set( queueId, nonErrorQueue ); | ||
return queueWithAction.finally( () => { | ||
this._activeActions--; | ||
if ( this._queues.get( queueId ) === nonErrorQueue && this._activeActions === 0 ) { | ||
this._onEmptyCallbacks.forEach( cb => cb() ); | ||
} | ||
} ); | ||
} | ||
constructor() { | ||
this._onEmptyCallbacks = []; | ||
this._queues = new Map(); | ||
this._activeActions = 0; | ||
} | ||
/** | ||
* Used to register callbacks that will be run when the queue becomes empty. | ||
* | ||
* @param onEmptyCallback A callback that will be run whenever the queue becomes empty. | ||
*/ | ||
onEmpty(onEmptyCallback) { | ||
this._onEmptyCallbacks.push(onEmptyCallback); | ||
} | ||
/** | ||
* It adds asynchronous actions (functions) to the proper queue and runs them one by one. | ||
* | ||
* @param queueId The action queue ID. | ||
* @param action A function that should be enqueued. | ||
*/ | ||
enqueue(queueId, action) { | ||
const isMainAction = queueId === mainQueueId; | ||
this._activeActions++; | ||
if (!this._queues.get(queueId)) { | ||
this._queues.set(queueId, Promise.resolve()); | ||
} | ||
// List all sources of actions that the current action needs to await for. | ||
// For the main action wait for all other actions. | ||
// For the item action wait only for the item queue and the main queue. | ||
const awaitedActions = isMainAction ? | ||
Promise.all(this._queues.values()) : | ||
Promise.all([this._queues.get(mainQueueId), this._queues.get(queueId)]); | ||
const queueWithAction = awaitedActions.then(action); | ||
// Catch all errors in the main queue to stack promises even if an error occurred in the past. | ||
const nonErrorQueue = queueWithAction.catch(() => { }); | ||
this._queues.set(queueId, nonErrorQueue); | ||
return queueWithAction.finally(() => { | ||
this._activeActions--; | ||
if (this._queues.get(queueId) === nonErrorQueue && this._activeActions === 0) { | ||
this._onEmptyCallbacks.forEach(cb => cb()); | ||
} | ||
}); | ||
} | ||
} | ||
// Transforms any value to an array. If the provided value is already an array, it is returned unchanged. | ||
// | ||
// @param {*} elementOrArray The value to transform to an array. | ||
// @returns {Array} An array created from data. | ||
function toArray( elementOrArray ) { | ||
return Array.isArray( elementOrArray ) ? elementOrArray : [ elementOrArray ]; | ||
} | ||
/** | ||
* The watchdog item configuration interface. | ||
* Transforms any value to an array. If the provided value is already an array, it is returned unchanged. | ||
* | ||
* @typedef {Object} module:watchdog/contextwatchdog~WatchdogItemConfiguration | ||
* | ||
* @property {String} id A unique item identificator. | ||
* | ||
* @property {'editor'} type The type of the item to create. At the moment, only `'editor'` is supported. | ||
* | ||
* @property {Function} creator A function that initializes the item (the editor). The function takes editor initialization arguments | ||
* and should return a promise. For example: `( el, config ) => ClassicEditor.create( el, config )`. | ||
* | ||
* @property {Function} [destructor] A function that destroys the item instance (the editor). The function | ||
* takes an item and should return a promise. For example: `editor => editor.destroy()` | ||
* | ||
* @property {String|HTMLElement} sourceElementOrData The source element or data that will be passed | ||
* as the first argument to the `Editor.create()` method. | ||
* | ||
* @property {Object} config An editor configuration. | ||
* @param elementOrArray The value to transform to an array. | ||
* @returns An array created from data. | ||
*/ | ||
function toArray(elementOrArray) { | ||
return Array.isArray(elementOrArray) ? elementOrArray : [elementOrArray]; | ||
} |
@@ -5,13 +5,5 @@ /** | ||
*/ | ||
/** | ||
* @module watchdog/editorwatchdog | ||
*/ | ||
/* globals console */ | ||
import { throttle, cloneDeepWith, isElement } from 'lodash-es'; | ||
import areConnectedThroughProperties from './utils/areconnectedthroughproperties'; | ||
import Watchdog from './watchdog'; | ||
import { throttle, cloneDeepWith, isElement } from 'lodash-es'; | ||
/** | ||
@@ -22,308 +14,206 @@ * A watchdog for CKEditor 5 editors. | ||
* how to use it. | ||
* | ||
* @extends {module:watchdog/watchdog~Watchdog} | ||
*/ | ||
export default class EditorWatchdog extends Watchdog { | ||
/** | ||
* @param {*} Editor The editor class. | ||
* @param {module:watchdog/watchdog~WatchdogConfig} [watchdogConfig] The watchdog plugin configuration. | ||
*/ | ||
constructor( Editor, watchdogConfig = {} ) { | ||
super( watchdogConfig ); | ||
/** | ||
* The current editor instance. | ||
* | ||
* @private | ||
* @type {module:core/editor/editor~Editor} | ||
*/ | ||
this._editor = null; | ||
/** | ||
* Throttled save method. The `save()` method is called the specified `saveInterval` after `throttledSave()` is called, | ||
* unless a new action happens in the meantime. | ||
* | ||
* @private | ||
* @type {Function} | ||
*/ | ||
this._throttledSave = throttle( | ||
this._save.bind( this ), | ||
typeof watchdogConfig.saveInterval === 'number' ? watchdogConfig.saveInterval : 5000 | ||
); | ||
/** | ||
* The latest saved editor data represented as a root name -> root data object. | ||
* | ||
* @private | ||
* @member {Object.<String,String>} #_data | ||
*/ | ||
/** | ||
* The last document version. | ||
* | ||
* @private | ||
* @member {Number} #_lastDocumentVersion | ||
*/ | ||
/** | ||
* The editor source element or data. | ||
* | ||
* @private | ||
* @member {HTMLElement|String|Object.<String|String>} #_elementOrData | ||
*/ | ||
/** | ||
* The editor configuration. | ||
* | ||
* @private | ||
* @member {Object|undefined} #_config | ||
*/ | ||
// Set default creator and destructor functions: | ||
this._creator = ( ( elementOrData, config ) => Editor.create( elementOrData, config ) ); | ||
this._destructor = editor => editor.destroy(); | ||
} | ||
/** | ||
* The current editor instance. | ||
* | ||
* @readonly | ||
* @type {module:core/editor/editor~Editor} | ||
*/ | ||
get editor() { | ||
return this._editor; | ||
} | ||
/** | ||
* @inheritDoc | ||
*/ | ||
get _item() { | ||
return this._editor; | ||
} | ||
/** | ||
* Sets the function that is responsible for the editor creation. | ||
* It expects a function that should return a promise. | ||
* | ||
* watchdog.setCreator( ( element, config ) => ClassicEditor.create( element, config ) ); | ||
* | ||
* @method #setCreator | ||
* @param {Function} creator | ||
*/ | ||
/** | ||
* Sets the function that is responsible for the editor destruction. | ||
* Overrides the default destruction function, which destroys only the editor instance. | ||
* It expects a function that should return a promise or `undefined`. | ||
* | ||
* watchdog.setDestructor( editor => { | ||
* // Do something before the editor is destroyed. | ||
* | ||
* return editor | ||
* .destroy() | ||
* .then( () => { | ||
* // Do something after the editor is destroyed. | ||
* } ); | ||
* } ); | ||
* | ||
* @method #setDestructor | ||
* @param {Function} destructor | ||
*/ | ||
/** | ||
* Restarts the editor instance. This method is called whenever an editor error occurs. It fires the `restart` event and changes | ||
* the state to `initializing`. | ||
* | ||
* @protected | ||
* @fires restart | ||
* @returns {Promise} | ||
*/ | ||
_restart() { | ||
return Promise.resolve() | ||
.then( () => { | ||
this.state = 'initializing'; | ||
this._fire( 'stateChange' ); | ||
return this._destroy(); | ||
} ) | ||
.catch( err => { | ||
console.error( 'An error happened during the editor destroying.', err ); | ||
} ) | ||
.then( () => { | ||
if ( typeof this._elementOrData === 'string' ) { | ||
return this.create( this._data, this._config, this._config.context ); | ||
} else { | ||
const updatedConfig = Object.assign( {}, this._config, { | ||
initialData: this._data | ||
} ); | ||
return this.create( this._elementOrData, updatedConfig, updatedConfig.context ); | ||
} | ||
} ) | ||
.then( () => { | ||
this._fire( 'restart' ); | ||
} ); | ||
} | ||
/** | ||
* Creates the editor instance and keeps it running, using the defined creator and destructor. | ||
* | ||
* @param {HTMLElement|String|Object.<String|String>} [elementOrData] The editor source element or the editor data. | ||
* @param {module:core/editor/editorconfig~EditorConfig} [config] The editor configuration. | ||
* @param {Object} [context] A context for the editor. | ||
* | ||
* @returns {Promise} | ||
*/ | ||
create( elementOrData = this._elementOrData, config = this._config, context ) { | ||
return Promise.resolve() | ||
.then( () => { | ||
super._startErrorHandling(); | ||
this._elementOrData = elementOrData; | ||
// Clone configuration because it might be shared within multiple watchdog instances. Otherwise, | ||
// when an error occurs in one of these editors, the watchdog will restart all of them. | ||
this._config = this._cloneEditorConfiguration( config ) || {}; | ||
this._config.context = context; | ||
return this._creator( elementOrData, this._config ); | ||
} ) | ||
.then( editor => { | ||
this._editor = editor; | ||
editor.model.document.on( 'change:data', this._throttledSave ); | ||
this._lastDocumentVersion = editor.model.document.version; | ||
this._data = this._getData(); | ||
this.state = 'ready'; | ||
this._fire( 'stateChange' ); | ||
} ); | ||
} | ||
/** | ||
* Destroys the watchdog and the current editor instance. It fires the callback | ||
* registered in {@link #setDestructor `setDestructor()`} and uses it to destroy the editor instance. | ||
* It also sets the state to `destroyed`. | ||
* | ||
* @returns {Promise} | ||
*/ | ||
destroy() { | ||
return Promise.resolve() | ||
.then( () => { | ||
this.state = 'destroyed'; | ||
this._fire( 'stateChange' ); | ||
super.destroy(); | ||
return this._destroy(); | ||
} ); | ||
} | ||
/** | ||
* @private | ||
* @returns {Promise} | ||
*/ | ||
_destroy() { | ||
return Promise.resolve() | ||
.then( () => { | ||
this._stopErrorHandling(); | ||
// Save data if there is a remaining editor data change. | ||
this._throttledSave.flush(); | ||
const editor = this._editor; | ||
this._editor = null; | ||
// Remove the `change:data` listener before destroying the editor. | ||
// Incorrectly written plugins may trigger firing `change:data` events during the editor destruction phase | ||
// causing the watchdog to call `editor.getData()` when some parts of editor are already destroyed. | ||
editor.model.document.off( 'change:data', this._throttledSave ); | ||
return this._destructor( editor ); | ||
} ); | ||
} | ||
/** | ||
* Saves the editor data, so it can be restored after the crash even if the data cannot be fetched at | ||
* the moment of the crash. | ||
* | ||
* @private | ||
*/ | ||
_save() { | ||
const version = this._editor.model.document.version; | ||
try { | ||
this._data = this._getData(); | ||
this._lastDocumentVersion = version; | ||
} catch ( err ) { | ||
console.error( | ||
err, | ||
'An error happened during restoring editor data. ' + | ||
'Editor will be restored from the previously saved data.' | ||
); | ||
} | ||
} | ||
/** | ||
* @protected | ||
* @param {Set} props | ||
*/ | ||
_setExcludedProperties( props ) { | ||
this._excludedProps = props; | ||
} | ||
/** | ||
* Returns the editor data. | ||
* | ||
* @private | ||
* @returns {Object<String,String>} | ||
*/ | ||
_getData() { | ||
const data = {}; | ||
for ( const rootName of this._editor.model.document.getRootNames() ) { | ||
data[ rootName ] = this._editor.data.get( { rootName } ); | ||
} | ||
return data; | ||
} | ||
/** | ||
* Traverses the error context and the current editor to find out whether these structures are connected | ||
* to each other via properties. | ||
* | ||
* @protected | ||
* @param {module:utils/ckeditorerror~CKEditorError} error | ||
*/ | ||
_isErrorComingFromThisItem( error ) { | ||
return areConnectedThroughProperties( this._editor, error.context, this._excludedProps ); | ||
} | ||
/** | ||
* Clones the editor configuration. | ||
* | ||
* @private | ||
* @param {Object} config | ||
*/ | ||
_cloneEditorConfiguration( config ) { | ||
return cloneDeepWith( config, ( value, key ) => { | ||
// Leave DOM references. | ||
if ( isElement( value ) ) { | ||
return value; | ||
} | ||
if ( key === 'context' ) { | ||
return value; | ||
} | ||
} ); | ||
} | ||
/** | ||
* Fired after the watchdog restarts the error in case of a crash. | ||
* | ||
* @event restart | ||
*/ | ||
/** | ||
* @param Editor The editor class. | ||
* @param watchdogConfig The watchdog plugin configuration. | ||
*/ | ||
constructor(Editor, watchdogConfig = {}) { | ||
super(watchdogConfig); | ||
/** | ||
* The current editor instance. | ||
*/ | ||
this._editor = null; | ||
// this._editorClass = Editor; | ||
this._throttledSave = throttle(this._save.bind(this), typeof watchdogConfig.saveInterval === 'number' ? watchdogConfig.saveInterval : 5000); | ||
// Set default creator and destructor functions: | ||
if (Editor) { | ||
this._creator = ((elementOrData, config) => Editor.create(elementOrData, config)); | ||
} | ||
this._destructor = editor => editor.destroy(); | ||
} | ||
/** | ||
* The current editor instance. | ||
*/ | ||
get editor() { | ||
return this._editor; | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
get _item() { | ||
return this._editor; | ||
} | ||
/** | ||
* Sets the function that is responsible for the editor creation. | ||
* It expects a function that should return a promise. | ||
* | ||
* ```ts | ||
* watchdog.setCreator( ( element, config ) => ClassicEditor.create( element, config ) ); | ||
* ``` | ||
*/ | ||
setCreator(creator) { | ||
this._creator = creator; | ||
} | ||
/** | ||
* Sets the function that is responsible for the editor destruction. | ||
* Overrides the default destruction function, which destroys only the editor instance. | ||
* It expects a function that should return a promise or `undefined`. | ||
* | ||
* ```ts | ||
* watchdog.setDestructor( editor => { | ||
* // Do something before the editor is destroyed. | ||
* | ||
* return editor | ||
* .destroy() | ||
* .then( () => { | ||
* // Do something after the editor is destroyed. | ||
* } ); | ||
* } ); | ||
* ``` | ||
*/ | ||
setDestructor(destructor) { | ||
this._destructor = destructor; | ||
} | ||
/** | ||
* Restarts the editor instance. This method is called whenever an editor error occurs. It fires the `restart` event and changes | ||
* the state to `initializing`. | ||
* | ||
* @fires restart | ||
*/ | ||
_restart() { | ||
return Promise.resolve() | ||
.then(() => { | ||
this.state = 'initializing'; | ||
this._fire('stateChange'); | ||
return this._destroy(); | ||
}) | ||
.catch(err => { | ||
console.error('An error happened during the editor destroying.', err); | ||
}) | ||
.then(() => { | ||
if (typeof this._elementOrData === 'string') { | ||
return this.create(this._data, this._config, this._config.context); | ||
} | ||
else { | ||
const updatedConfig = Object.assign({}, this._config, { | ||
initialData: this._data | ||
}); | ||
return this.create(this._elementOrData, updatedConfig, updatedConfig.context); | ||
} | ||
}) | ||
.then(() => { | ||
this._fire('restart'); | ||
}); | ||
} | ||
/** | ||
* Creates the editor instance and keeps it running, using the defined creator and destructor. | ||
* | ||
* @param elementOrData The editor source element or the editor data. | ||
* @param config The editor configuration. | ||
* @param context A context for the editor. | ||
*/ | ||
create(elementOrData = this._elementOrData, config = this._config, context) { | ||
return Promise.resolve() | ||
.then(() => { | ||
super._startErrorHandling(); | ||
this._elementOrData = elementOrData; | ||
// Clone configuration because it might be shared within multiple watchdog instances. Otherwise, | ||
// when an error occurs in one of these editors, the watchdog will restart all of them. | ||
this._config = this._cloneEditorConfiguration(config) || {}; | ||
this._config.context = context; | ||
return this._creator(elementOrData, this._config); | ||
}) | ||
.then(editor => { | ||
this._editor = editor; | ||
editor.model.document.on('change:data', this._throttledSave); | ||
this._lastDocumentVersion = editor.model.document.version; | ||
this._data = this._getData(); | ||
this.state = 'ready'; | ||
this._fire('stateChange'); | ||
}); | ||
} | ||
/** | ||
* Destroys the watchdog and the current editor instance. It fires the callback | ||
* registered in {@link #setDestructor `setDestructor()`} and uses it to destroy the editor instance. | ||
* It also sets the state to `destroyed`. | ||
*/ | ||
destroy() { | ||
return Promise.resolve() | ||
.then(() => { | ||
this.state = 'destroyed'; | ||
this._fire('stateChange'); | ||
super.destroy(); | ||
return this._destroy(); | ||
}); | ||
} | ||
_destroy() { | ||
return Promise.resolve() | ||
.then(() => { | ||
this._stopErrorHandling(); | ||
// Save data if there is a remaining editor data change. | ||
this._throttledSave.flush(); | ||
const editor = this._editor; | ||
this._editor = null; | ||
// Remove the `change:data` listener before destroying the editor. | ||
// Incorrectly written plugins may trigger firing `change:data` events during the editor destruction phase | ||
// causing the watchdog to call `editor.getData()` when some parts of editor are already destroyed. | ||
editor.model.document.off('change:data', this._throttledSave); | ||
return this._destructor(editor); | ||
}); | ||
} | ||
/** | ||
* Saves the editor data, so it can be restored after the crash even if the data cannot be fetched at | ||
* the moment of the crash. | ||
*/ | ||
_save() { | ||
const version = this._editor.model.document.version; | ||
try { | ||
this._data = this._getData(); | ||
this._lastDocumentVersion = version; | ||
} | ||
catch (err) { | ||
console.error(err, 'An error happened during restoring editor data. ' + | ||
'Editor will be restored from the previously saved data.'); | ||
} | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
_setExcludedProperties(props) { | ||
this._excludedProps = props; | ||
} | ||
/** | ||
* Returns the editor data. | ||
*/ | ||
_getData() { | ||
const data = {}; | ||
for (const rootName of this._editor.model.document.getRootNames()) { | ||
data[rootName] = this._editor.data.get({ rootName }); | ||
} | ||
return data; | ||
} | ||
/** | ||
* Traverses the error context and the current editor to find out whether these structures are connected | ||
* to each other via properties. | ||
* | ||
* @internal | ||
*/ | ||
_isErrorComingFromThisItem(error) { | ||
return areConnectedThroughProperties(this._editor, error.context, this._excludedProps); | ||
} | ||
/** | ||
* Clones the editor configuration. | ||
*/ | ||
_cloneEditorConfiguration(config) { | ||
return cloneDeepWith(config, (value, key) => { | ||
// Leave DOM references. | ||
if (isElement(value)) { | ||
return value; | ||
} | ||
if (key === 'context') { | ||
return value; | ||
} | ||
}); | ||
} | ||
} |
@@ -5,9 +5,7 @@ /** | ||
*/ | ||
/** | ||
* @module watchdog | ||
*/ | ||
export { default as ContextWatchdog } from './contextwatchdog'; | ||
export { default as EditorWatchdog } from './editorwatchdog'; | ||
export { default as Watchdog } from './watchdog'; |
@@ -5,78 +5,55 @@ /** | ||
*/ | ||
/** | ||
* @module watchdog/utils/areconnectedthroughproperties | ||
*/ | ||
/* globals console */ | ||
import getSubNodes from './getsubnodes'; | ||
/** | ||
* Traverses both structures to find out whether there is a reference that is shared between both structures. | ||
* | ||
* @param {Object|Array} target1 | ||
* @param {Object|Array} target2 | ||
* @returns {Boolean} | ||
*/ | ||
export default function areConnectedThroughProperties( target1, target2, excludedNodes = new Set() ) { | ||
if ( target1 === target2 && isObject( target1 ) ) { | ||
return true; | ||
} | ||
// @if CK_DEBUG_WATCHDOG // return checkConnectionBetweenProps( target1, target2, excludedNodes ); | ||
const subNodes1 = getSubNodes( target1, excludedNodes ); | ||
const subNodes2 = getSubNodes( target2, excludedNodes ); | ||
for ( const node of subNodes1 ) { | ||
if ( subNodes2.has( node ) ) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
export default function areConnectedThroughProperties(target1, target2, excludedNodes = new Set()) { | ||
if (target1 === target2 && isObject(target1)) { | ||
return true; | ||
} | ||
// @if CK_DEBUG_WATCHDOG // return checkConnectionBetweenProps( target1, target2, excludedNodes ); | ||
const subNodes1 = getSubNodes(target1, excludedNodes); | ||
const subNodes2 = getSubNodes(target2, excludedNodes); | ||
for (const node of subNodes1) { | ||
if (subNodes2.has(node)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
/* istanbul ignore next */ | ||
// eslint-disable-next-line | ||
function checkConnectionBetweenProps( target1, target2, excludedNodes ) { | ||
const { subNodes: subNodes1, prevNodeMap: prevNodeMap1 } = getSubNodes( target1, excludedNodes.subNodes ); | ||
const { subNodes: subNodes2, prevNodeMap: prevNodeMap2 } = getSubNodes( target2, excludedNodes.subNodes ); | ||
for ( const sharedNode of subNodes1 ) { | ||
if ( subNodes2.has( sharedNode ) ) { | ||
const connection = []; | ||
connection.push( sharedNode ); | ||
let node = prevNodeMap1.get( sharedNode ); | ||
while ( node && node !== target1 ) { | ||
connection.push( node ); | ||
node = prevNodeMap1.get( node ); | ||
} | ||
node = prevNodeMap2.get( sharedNode ); | ||
while ( node && node !== target2 ) { | ||
connection.unshift( node ); | ||
node = prevNodeMap2.get( node ); | ||
} | ||
console.log( '--------' ); | ||
console.log( { target1 } ); | ||
console.log( { sharedNode } ); | ||
console.log( { target2 } ); | ||
console.log( { connection } ); | ||
return true; | ||
} | ||
} | ||
return false; | ||
function checkConnectionBetweenProps(target1, target2, excludedNodes) { | ||
const { subNodes: subNodes1, prevNodeMap: prevNodeMap1 } = getSubNodes(target1, excludedNodes.subNodes); | ||
const { subNodes: subNodes2, prevNodeMap: prevNodeMap2 } = getSubNodes(target2, excludedNodes.subNodes); | ||
for (const sharedNode of subNodes1) { | ||
if (subNodes2.has(sharedNode)) { | ||
const connection = []; | ||
connection.push(sharedNode); | ||
let node = prevNodeMap1.get(sharedNode); | ||
while (node && node !== target1) { | ||
connection.push(node); | ||
node = prevNodeMap1.get(node); | ||
} | ||
node = prevNodeMap2.get(sharedNode); | ||
while (node && node !== target2) { | ||
connection.unshift(node); | ||
node = prevNodeMap2.get(node); | ||
} | ||
console.log('--------'); | ||
console.log({ target1 }); | ||
console.log({ sharedNode }); | ||
console.log({ target2 }); | ||
console.log({ connection }); | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
function isObject( structure ) { | ||
return typeof structure === 'object' && structure !== null; | ||
function isObject(structure) { | ||
return typeof structure === 'object' && structure !== null; | ||
} |
@@ -5,94 +5,75 @@ /** | ||
*/ | ||
/** | ||
* @module watchdog/utils/getsubnodes | ||
*/ | ||
/* globals EventTarget, Event */ | ||
export default function getSubNodes( head, excludedProperties = new Set() ) { | ||
const nodes = [ head ]; | ||
// @if CK_DEBUG_WATCHDOG // const prevNodeMap = new Map(); | ||
// Nodes are stored to prevent infinite looping. | ||
const subNodes = new Set(); | ||
let nodeIndex = 0; | ||
while ( nodes.length > nodeIndex ) { | ||
// Incrementing the iterator is much faster than changing size of the array with Array.prototype.shift(). | ||
const node = nodes[ nodeIndex++ ]; | ||
if ( subNodes.has( node ) || shouldNodeBeSkipped( node ) || excludedProperties.has( node ) ) { | ||
continue; | ||
} | ||
subNodes.add( node ); | ||
// Handle arrays, maps, sets, custom collections that implements `[ Symbol.iterator ]()`, etc. | ||
if ( node[ Symbol.iterator ] ) { | ||
// The custom editor iterators might cause some problems if the editor is crashed. | ||
try { | ||
for ( const n of node ) { | ||
nodes.push( n ); | ||
// @if CK_DEBUG_WATCHDOG // if ( !prevNodeMap.has( n ) ) { | ||
// @if CK_DEBUG_WATCHDOG // prevNodeMap.set( n, node ); | ||
// @if CK_DEBUG_WATCHDOG // } | ||
} | ||
} catch ( err ) { | ||
// Do not log errors for broken structures | ||
// since we are in the error handling process already. | ||
// eslint-disable-line no-empty | ||
} | ||
} else { | ||
for ( const key in node ) { | ||
// We share a reference via the protobuf library within the editors, | ||
// hence the shared value should be skipped. Although, it's not a perfect | ||
// solution since new places like that might occur in the future. | ||
if ( key === 'defaultValue' ) { | ||
continue; | ||
} | ||
nodes.push( node[ key ] ); | ||
// @if CK_DEBUG_WATCHDOG // if ( !prevNodeMap.has( node[ key ] ) ) { | ||
// @if CK_DEBUG_WATCHDOG // prevNodeMap.set( node[ key ], node ); | ||
// @if CK_DEBUG_WATCHDOG // } | ||
} | ||
} | ||
} | ||
// @if CK_DEBUG_WATCHDOG // return { subNodes, prevNodeMap }; | ||
return subNodes; | ||
export default function getSubNodes(head, excludedProperties = new Set()) { | ||
const nodes = [head]; | ||
// @if CK_DEBUG_WATCHDOG // const prevNodeMap = new Map(); | ||
// Nodes are stored to prevent infinite looping. | ||
const subNodes = new Set(); | ||
let nodeIndex = 0; | ||
while (nodes.length > nodeIndex) { | ||
// Incrementing the iterator is much faster than changing size of the array with Array.prototype.shift(). | ||
const node = nodes[nodeIndex++]; | ||
if (subNodes.has(node) || !shouldNodeBeIncluded(node) || excludedProperties.has(node)) { | ||
continue; | ||
} | ||
subNodes.add(node); | ||
// Handle arrays, maps, sets, custom collections that implements `[ Symbol.iterator ]()`, etc. | ||
if (Symbol.iterator in node) { | ||
// The custom editor iterators might cause some problems if the editor is crashed. | ||
try { | ||
for (const n of node) { | ||
nodes.push(n); | ||
// @if CK_DEBUG_WATCHDOG // if ( !prevNodeMap.has( n ) ) { | ||
// @if CK_DEBUG_WATCHDOG // prevNodeMap.set( n, node ); | ||
// @if CK_DEBUG_WATCHDOG // } | ||
} | ||
} | ||
catch (err) { | ||
// Do not log errors for broken structures | ||
// since we are in the error handling process already. | ||
// eslint-disable-line no-empty | ||
} | ||
} | ||
else { | ||
for (const key in node) { | ||
// We share a reference via the protobuf library within the editors, | ||
// hence the shared value should be skipped. Although, it's not a perfect | ||
// solution since new places like that might occur in the future. | ||
if (key === 'defaultValue') { | ||
continue; | ||
} | ||
nodes.push(node[key]); | ||
// @if CK_DEBUG_WATCHDOG // if ( !prevNodeMap.has( node[ key ] ) ) { | ||
// @if CK_DEBUG_WATCHDOG // prevNodeMap.set( node[ key ], node ); | ||
// @if CK_DEBUG_WATCHDOG // } | ||
} | ||
} | ||
} | ||
// @if CK_DEBUG_WATCHDOG // return { subNodes, prevNodeMap } as any; | ||
return subNodes; | ||
} | ||
function shouldNodeBeSkipped( node ) { | ||
const type = Object.prototype.toString.call( node ); | ||
const typeOfNode = typeof node; | ||
return ( | ||
typeOfNode === 'number' || | ||
typeOfNode === 'boolean' || | ||
typeOfNode === 'string' || | ||
typeOfNode === 'symbol' || | ||
typeOfNode === 'function' || | ||
type === '[object Date]' || | ||
type === '[object RegExp]' || | ||
type === '[object Module]' || | ||
node === undefined || | ||
node === null || | ||
// This flag is meant to exclude singletons shared across editor instances. So when an error is thrown in one editor, | ||
// the other editors connected through the reference to the same singleton are not restarted. This is a temporary workaround | ||
// until a better solution is found. | ||
// More in https://github.com/ckeditor/ckeditor5/issues/12292. | ||
node._watchdogExcluded === true || | ||
// Skip native DOM objects, e.g. Window, nodes, events, etc. | ||
node instanceof EventTarget || | ||
node instanceof Event | ||
); | ||
function shouldNodeBeIncluded(node) { | ||
const type = Object.prototype.toString.call(node); | ||
const typeOfNode = typeof node; | ||
return !(typeOfNode === 'number' || | ||
typeOfNode === 'boolean' || | ||
typeOfNode === 'string' || | ||
typeOfNode === 'symbol' || | ||
typeOfNode === 'function' || | ||
type === '[object Date]' || | ||
type === '[object RegExp]' || | ||
type === '[object Module]' || | ||
node === undefined || | ||
node === null || | ||
// This flag is meant to exclude singletons shared across editor instances. So when an error is thrown in one editor, | ||
// the other editors connected through the reference to the same singleton are not restarted. This is a temporary workaround | ||
// until a better solution is found. | ||
// More in https://github.com/ckeditor/ckeditor5/issues/12292. | ||
node._watchdogExcluded || | ||
// Skip native DOM objects, e.g. Window, nodes, events, etc. | ||
node instanceof EventTarget || | ||
node instanceof Event); | ||
} |
@@ -5,10 +5,3 @@ /** | ||
*/ | ||
/** | ||
* @module watchdog/watchdog | ||
*/ | ||
/* globals window */ | ||
/** | ||
* An abstract watchdog class that handles most of the error handling process and the state of the underlying component. | ||
@@ -18,344 +11,177 @@ * | ||
* | ||
* @private | ||
* @abstract | ||
* @internal | ||
*/ | ||
export default class Watchdog { | ||
/** | ||
* @param {module:watchdog/watchdog~WatchdogConfig} config The watchdog plugin configuration. | ||
*/ | ||
constructor( config ) { | ||
/** | ||
* An array of crashes saved as an object with the following properties: | ||
* | ||
* * `message`: `String`, | ||
* * `stack`: `String`, | ||
* * `date`: `Number`, | ||
* * `filename`: `String | undefined`, | ||
* * `lineno`: `Number | undefined`, | ||
* * `colno`: `Number | undefined`, | ||
* | ||
* @public | ||
* @readonly | ||
* @type {Array.<Object>} | ||
*/ | ||
this.crashes = []; | ||
/** | ||
* Specifies the state of the item watched by the watchdog. The state can be one of the following values: | ||
* | ||
* * `initializing` – Before the first initialization, and after crashes, before the item is ready. | ||
* * `ready` – A state when the user can interact with the item. | ||
* * `crashed` – A state when an error occurs. It quickly changes to `initializing` or `crashedPermanently` | ||
* depending on how many and how frequent errors have been caught recently. | ||
* * `crashedPermanently` – A state when the watchdog stops reacting to errors and keeps the item it is watching crashed, | ||
* * `destroyed` – A state when the item is manually destroyed by the user after calling `watchdog.destroy()`. | ||
* | ||
* @public | ||
* @type {'initializing'|'ready'|'crashed'|'crashedPermanently'|'destroyed'} | ||
*/ | ||
this.state = 'initializing'; | ||
/** | ||
* @protected | ||
* @type {Number} | ||
* @see module:watchdog/watchdog~WatchdogConfig | ||
*/ | ||
this._crashNumberLimit = typeof config.crashNumberLimit === 'number' ? config.crashNumberLimit : 3; | ||
/** | ||
* Returns the result of the `Date.now()` call. It can be overridden in tests to mock time as some popular | ||
* approaches like `sinon.useFakeTimers()` do not work well with error handling. | ||
* | ||
* @protected | ||
*/ | ||
this._now = Date.now; | ||
/** | ||
* @protected | ||
* @type {Number} | ||
* @see module:watchdog/watchdog~WatchdogConfig | ||
*/ | ||
this._minimumNonErrorTimePeriod = typeof config.minimumNonErrorTimePeriod === 'number' ? config.minimumNonErrorTimePeriod : 5000; | ||
/** | ||
* Checks if the event error comes from the underlying item and restarts the item. | ||
* | ||
* @private | ||
* @type {Function} | ||
*/ | ||
this._boundErrorHandler = evt => { | ||
// `evt.error` is exposed by EventError while `evt.reason` is available in PromiseRejectionEvent. | ||
const error = evt.error || evt.reason; | ||
// Note that `evt.reason` might be everything that is in the promise rejection. | ||
// Similarly everything that is thrown lands in `evt.error`. | ||
if ( error instanceof Error ) { | ||
this._handleError( error, evt ); | ||
} | ||
}; | ||
/** | ||
* The creation method. | ||
* | ||
* @protected | ||
* @member {Function} #_creator | ||
* @see #setCreator | ||
*/ | ||
/** | ||
* The destruction method. | ||
* | ||
* @protected | ||
* @member {Function} #_destructor | ||
* @see #setDestructor | ||
*/ | ||
/** | ||
* The watched item. | ||
* | ||
* @abstract | ||
* @protected | ||
* @member {Object|undefined} #_item | ||
*/ | ||
/** | ||
* The method responsible for restarting the watched item. | ||
* | ||
* @abstract | ||
* @protected | ||
* @method #_restart | ||
*/ | ||
/** | ||
* Traverses the error context and the watched item to find out whether the error should | ||
* be handled by the given item. | ||
* | ||
* @abstract | ||
* @protected | ||
* @method #_isErrorComingFromThisItem | ||
* @param {module:utils/ckeditorerror~CKEditorError} error | ||
*/ | ||
/** | ||
* A dictionary of event emitter listeners. | ||
* | ||
* @private | ||
* @type {Object.<String,Array.<Function>>} | ||
*/ | ||
this._listeners = {}; | ||
if ( !this._restart ) { | ||
throw new Error( | ||
'The Watchdog class was split into the abstract `Watchdog` class and the `EditorWatchdog` class. ' + | ||
'Please, use `EditorWatchdog` if you have used the `Watchdog` class previously.' | ||
); | ||
} | ||
} | ||
/** | ||
* Sets the function that is responsible for creating watched items. | ||
* | ||
* @param {Function} creator A callback responsible for creating an item. Returns a promise | ||
* that is resolved when the item is created. | ||
*/ | ||
setCreator( creator ) { | ||
this._creator = creator; | ||
} | ||
/** | ||
* Sets the function that is responsible for destroying watched items. | ||
* | ||
* @param {Function} destructor A callback that takes the item and returns the promise | ||
* to the destroying process. | ||
*/ | ||
setDestructor( destructor ) { | ||
this._destructor = destructor; | ||
} | ||
/** | ||
* Destroys the watchdog and releases the resources. | ||
*/ | ||
destroy() { | ||
this._stopErrorHandling(); | ||
this._listeners = {}; | ||
} | ||
/** | ||
* Starts listening to a specific event name by registering a callback that will be executed | ||
* whenever an event with a given name fires. | ||
* | ||
* Note that this method differs from the CKEditor 5's default `EventEmitterMixin` implementation. | ||
* | ||
* @param {String} eventName The event name. | ||
* @param {Function} callback A callback which will be added to event listeners. | ||
*/ | ||
on( eventName, callback ) { | ||
if ( !this._listeners[ eventName ] ) { | ||
this._listeners[ eventName ] = []; | ||
} | ||
this._listeners[ eventName ].push( callback ); | ||
} | ||
/** | ||
* Stops listening to the specified event name by removing the callback from event listeners. | ||
* | ||
* Note that this method differs from the CKEditor 5's default `EventEmitterMixin` implementation. | ||
* | ||
* @param {String} eventName The event name. | ||
* @param {Function} callback A callback which will be removed from event listeners. | ||
*/ | ||
off( eventName, callback ) { | ||
this._listeners[ eventName ] = this._listeners[ eventName ] | ||
.filter( cb => cb !== callback ); | ||
} | ||
/** | ||
* Fires an event with a given event name and arguments. | ||
* | ||
* Note that this method differs from the CKEditor 5's default `EventEmitterMixin` implementation. | ||
* | ||
* @protected | ||
* @param {String} eventName The event name. | ||
* @param {...*} args Event arguments. | ||
*/ | ||
_fire( eventName, ...args ) { | ||
const callbacks = this._listeners[ eventName ] || []; | ||
for ( const callback of callbacks ) { | ||
callback.apply( this, [ null, ...args ] ); | ||
} | ||
} | ||
/** | ||
* Starts error handling by attaching global error handlers. | ||
* | ||
* @protected | ||
*/ | ||
_startErrorHandling() { | ||
window.addEventListener( 'error', this._boundErrorHandler ); | ||
window.addEventListener( 'unhandledrejection', this._boundErrorHandler ); | ||
} | ||
/** | ||
* Stops error handling by detaching global error handlers. | ||
* | ||
* @protected | ||
*/ | ||
_stopErrorHandling() { | ||
window.removeEventListener( 'error', this._boundErrorHandler ); | ||
window.removeEventListener( 'unhandledrejection', this._boundErrorHandler ); | ||
} | ||
/** | ||
* Checks if an error comes from the watched item and restarts it. | ||
* It reacts to {@link module:utils/ckeditorerror~CKEditorError `CKEditorError` errors} only. | ||
* | ||
* @private | ||
* @fires error | ||
* @param {Error} error Error. | ||
* @param {ErrorEvent|PromiseRejectionEvent} evt An error event. | ||
*/ | ||
_handleError( error, evt ) { | ||
// @if CK_DEBUG // if ( error.is && error.is( 'CKEditorError' ) && error.context === undefined ) { | ||
// @if CK_DEBUG // console.warn( 'The error is missing its context and Watchdog cannot restart the proper item.' ); | ||
// @if CK_DEBUG // } | ||
if ( this._shouldReactToError( error ) ) { | ||
this.crashes.push( { | ||
message: error.message, | ||
stack: error.stack, | ||
// `evt.filename`, `evt.lineno` and `evt.colno` are available only in ErrorEvent events | ||
filename: evt.filename, | ||
lineno: evt.lineno, | ||
colno: evt.colno, | ||
date: this._now() | ||
} ); | ||
const causesRestart = this._shouldRestart(); | ||
this.state = 'crashed'; | ||
this._fire( 'stateChange' ); | ||
this._fire( 'error', { error, causesRestart } ); | ||
if ( causesRestart ) { | ||
this._restart(); | ||
} else { | ||
this.state = 'crashedPermanently'; | ||
this._fire( 'stateChange' ); | ||
} | ||
} | ||
} | ||
/** | ||
* Checks whether an error should be handled by the watchdog. | ||
* | ||
* @private | ||
* @param {Error} error An error that was caught by the error handling process. | ||
*/ | ||
_shouldReactToError( error ) { | ||
return ( | ||
error.is && | ||
error.is( 'CKEditorError' ) && | ||
error.context !== undefined && | ||
// In some cases the watched item should not be restarted - e.g. during the item initialization. | ||
// That's why the `null` was introduced as a correct error context which does cause restarting. | ||
error.context !== null && | ||
// Do not react to errors if the watchdog is in states other than `ready`. | ||
this.state === 'ready' && | ||
this._isErrorComingFromThisItem( error ) | ||
); | ||
} | ||
/** | ||
* Checks if the watchdog should restart the underlying item. | ||
* | ||
* @private | ||
*/ | ||
_shouldRestart() { | ||
if ( this.crashes.length <= this._crashNumberLimit ) { | ||
return true; | ||
} | ||
const lastErrorTime = this.crashes[ this.crashes.length - 1 ].date; | ||
const firstMeaningfulErrorTime = this.crashes[ this.crashes.length - 1 - this._crashNumberLimit ].date; | ||
const averageNonErrorTimePeriod = ( lastErrorTime - firstMeaningfulErrorTime ) / this._crashNumberLimit; | ||
return averageNonErrorTimePeriod > this._minimumNonErrorTimePeriod; | ||
} | ||
/** | ||
* Fired when a new {@link module:utils/ckeditorerror~CKEditorError `CKEditorError`} error connected to the watchdog instance occurs | ||
* and the watchdog will react to it. | ||
* | ||
* watchdog.on( 'error', ( evt, { error, causesRestart } ) => { | ||
* console.log( 'An error occurred.' ); | ||
* } ); | ||
* | ||
* @event error | ||
*/ | ||
/** | ||
* @param {module:watchdog/watchdog~WatchdogConfig} config The watchdog plugin configuration. | ||
*/ | ||
constructor(config) { | ||
/** | ||
* An array of crashes saved as an object with the following properties: | ||
* | ||
* * `message`: `String`, | ||
* * `stack`: `String`, | ||
* * `date`: `Number`, | ||
* * `filename`: `String | undefined`, | ||
* * `lineno`: `Number | undefined`, | ||
* * `colno`: `Number | undefined`, | ||
*/ | ||
this.crashes = []; | ||
/** | ||
* Specifies the state of the item watched by the watchdog. The state can be one of the following values: | ||
* | ||
* * `initializing` – Before the first initialization, and after crashes, before the item is ready. | ||
* * `ready` – A state when the user can interact with the item. | ||
* * `crashed` – A state when an error occurs. It quickly changes to `initializing` or `crashedPermanently` | ||
* depending on how many and how frequent errors have been caught recently. | ||
* * `crashedPermanently` – A state when the watchdog stops reacting to errors and keeps the item it is watching crashed, | ||
* * `destroyed` – A state when the item is manually destroyed by the user after calling `watchdog.destroy()`. | ||
*/ | ||
this.state = 'initializing'; | ||
/** | ||
* Returns the result of the `Date.now()` call. It can be overridden in tests to mock time as some popular | ||
* approaches like `sinon.useFakeTimers()` do not work well with error handling. | ||
*/ | ||
this._now = Date.now; | ||
this.crashes = []; | ||
this._crashNumberLimit = typeof config.crashNumberLimit === 'number' ? config.crashNumberLimit : 3; | ||
this._minimumNonErrorTimePeriod = typeof config.minimumNonErrorTimePeriod === 'number' ? config.minimumNonErrorTimePeriod : 5000; | ||
this._boundErrorHandler = evt => { | ||
// `evt.error` is exposed by EventError while `evt.reason` is available in PromiseRejectionEvent. | ||
const error = 'error' in evt ? evt.error : evt.reason; | ||
// Note that `evt.reason` might be everything that is in the promise rejection. | ||
// Similarly everything that is thrown lands in `evt.error`. | ||
if (error instanceof Error) { | ||
this._handleError(error, evt); | ||
} | ||
}; | ||
this._listeners = {}; | ||
if (!this._restart) { | ||
throw new Error('The Watchdog class was split into the abstract `Watchdog` class and the `EditorWatchdog` class. ' + | ||
'Please, use `EditorWatchdog` if you have used the `Watchdog` class previously.'); | ||
} | ||
} | ||
/** | ||
* Destroys the watchdog and releases the resources. | ||
*/ | ||
destroy() { | ||
this._stopErrorHandling(); | ||
this._listeners = {}; | ||
} | ||
/** | ||
* Starts listening to a specific event name by registering a callback that will be executed | ||
* whenever an event with a given name fires. | ||
* | ||
* Note that this method differs from the CKEditor 5's default `EventEmitterMixin` implementation. | ||
* | ||
* @param eventName The event name. | ||
* @param callback A callback which will be added to event listeners. | ||
*/ | ||
on(eventName, callback) { | ||
if (!this._listeners[eventName]) { | ||
this._listeners[eventName] = []; | ||
} | ||
this._listeners[eventName].push(callback); | ||
} | ||
/** | ||
* Stops listening to the specified event name by removing the callback from event listeners. | ||
* | ||
* Note that this method differs from the CKEditor 5's default `EventEmitterMixin` implementation. | ||
* | ||
* @param eventName The event name. | ||
* @param callback A callback which will be removed from event listeners. | ||
*/ | ||
off(eventName, callback) { | ||
this._listeners[eventName] = this._listeners[eventName] | ||
.filter(cb => cb !== callback); | ||
} | ||
/** | ||
* Fires an event with a given event name and arguments. | ||
* | ||
* Note that this method differs from the CKEditor 5's default `EventEmitterMixin` implementation. | ||
*/ | ||
_fire(eventName, ...args) { | ||
const callbacks = this._listeners[eventName] || []; | ||
for (const callback of callbacks) { | ||
callback.apply(this, [null, ...args]); | ||
} | ||
} | ||
/** | ||
* Starts error handling by attaching global error handlers. | ||
*/ | ||
_startErrorHandling() { | ||
window.addEventListener('error', this._boundErrorHandler); | ||
window.addEventListener('unhandledrejection', this._boundErrorHandler); | ||
} | ||
/** | ||
* Stops error handling by detaching global error handlers. | ||
*/ | ||
_stopErrorHandling() { | ||
window.removeEventListener('error', this._boundErrorHandler); | ||
window.removeEventListener('unhandledrejection', this._boundErrorHandler); | ||
} | ||
/** | ||
* Checks if an error comes from the watched item and restarts it. | ||
* It reacts to {@link module:utils/ckeditorerror~CKEditorError `CKEditorError` errors} only. | ||
* | ||
* @fires error | ||
* @param error Error. | ||
* @param evt An error event. | ||
*/ | ||
_handleError(error, evt) { | ||
// @if CK_DEBUG // const err = error as CKEditorError; | ||
// @if CK_DEBUG // if ( err.is && err.is( 'CKEditorError' ) && err.context === undefined ) { | ||
// @if CK_DEBUG // console.warn( 'The error is missing its context and Watchdog cannot restart the proper item.' ); | ||
// @if CK_DEBUG // } | ||
if (this._shouldReactToError(error)) { | ||
this.crashes.push({ | ||
message: error.message, | ||
stack: error.stack, | ||
// `evt.filename`, `evt.lineno` and `evt.colno` are available only in ErrorEvent events | ||
filename: evt instanceof ErrorEvent ? evt.filename : undefined, | ||
lineno: evt instanceof ErrorEvent ? evt.lineno : undefined, | ||
colno: evt instanceof ErrorEvent ? evt.colno : undefined, | ||
date: this._now() | ||
}); | ||
const causesRestart = this._shouldRestart(); | ||
this.state = 'crashed'; | ||
this._fire('stateChange'); | ||
this._fire('error', { error, causesRestart }); | ||
if (causesRestart) { | ||
this._restart(); | ||
} | ||
else { | ||
this.state = 'crashedPermanently'; | ||
this._fire('stateChange'); | ||
} | ||
} | ||
} | ||
/** | ||
* Checks whether an error should be handled by the watchdog. | ||
* | ||
* @param error An error that was caught by the error handling process. | ||
*/ | ||
_shouldReactToError(error) { | ||
return (error.is && | ||
error.is('CKEditorError') && | ||
error.context !== undefined && | ||
// In some cases the watched item should not be restarted - e.g. during the item initialization. | ||
// That's why the `null` was introduced as a correct error context which does cause restarting. | ||
error.context !== null && | ||
// Do not react to errors if the watchdog is in states other than `ready`. | ||
this.state === 'ready' && | ||
this._isErrorComingFromThisItem(error)); | ||
} | ||
/** | ||
* Checks if the watchdog should restart the underlying item. | ||
*/ | ||
_shouldRestart() { | ||
if (this.crashes.length <= this._crashNumberLimit) { | ||
return true; | ||
} | ||
const lastErrorTime = this.crashes[this.crashes.length - 1].date; | ||
const firstMeaningfulErrorTime = this.crashes[this.crashes.length - 1 - this._crashNumberLimit].date; | ||
const averageNonErrorTimePeriod = (lastErrorTime - firstMeaningfulErrorTime) / this._crashNumberLimit; | ||
return averageNonErrorTimePeriod > this._minimumNonErrorTimePeriod; | ||
} | ||
} | ||
/** | ||
* The watchdog plugin configuration. | ||
* | ||
* @typedef {Object} WatchdogConfig | ||
* | ||
* @property {Number} [crashNumberLimit=3] A threshold specifying the number of watched item crashes | ||
* when the watchdog stops restarting the item in case of errors. | ||
* After this limit is reached and the time between the last errors is shorter than `minimumNonErrorTimePeriod`, | ||
* the watchdog changes its state to `crashedPermanently` and it stops restarting the item. This prevents an infinite restart loop. | ||
* | ||
* @property {Number} [minimumNonErrorTimePeriod=5000] An average number of milliseconds between the last watched item errors | ||
* (defaults to 5000). When the period of time between errors is lower than that and the `crashNumberLimit` is also reached, | ||
* the watchdog changes its state to `crashedPermanently` and it stops restarting the item. This prevents an infinite restart loop. | ||
* | ||
* @property {Number} [saveInterval=5000] A minimum number of milliseconds between saving the editor data internally (defaults to 5000). | ||
* Note that for large documents this might impact the editor performance. | ||
*/ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
95681
18
1715
10
1
1