Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ckeditor/ckeditor5-watchdog

Package Overview
Dependencies
Maintainers
1
Versions
677
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ckeditor/ckeditor5-watchdog - npm Package Compare versions

Comparing version 36.0.1 to 37.0.0-alpha.0

src/contextwatchdog.d.ts

2

build/watchdog.js
/*!
* @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` &ndash; Before the first initialization, and after crashes, before the item is ready.
* * `ready` &ndash; A state when the user can interact with the item.
* * `crashed` &ndash; 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` &ndash; A state when the watchdog stops reacting to errors and keeps the item it is watching crashed,
* * `destroyed` &ndash; 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` &ndash; Before the first initialization, and after crashes, before the item is ready.
* * `ready` &ndash; A state when the user can interact with the item.
* * `crashed` &ndash; 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` &ndash; A state when the watchdog stops reacting to errors and keeps the item it is watching crashed,
* * `destroyed` &ndash; 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.
*/
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc