UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

212 lines (209 loc) • 9.78 kB
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } var registerDetectors = function registerDetectors(targetExtensions, supportedDetectors) { var detectors = supportedDetectors.filter(function (registration) { return targetExtensions.includes(registration.name); }); return detectors.map(function (detector) { return { name: detector.name, state: { detected: false }, fns: { async: detector.fns.async, sync: detector.fns.sync } }; }); }; var RACE_COMPLETE = 'race_complete'; var SELECTORS = { GRAMMARLY: 'grammarly-extension, grammarly-popups, [data-grammarly-shadow-root]' }; /** * This is the official list of supported browser extension detectors. To add support * for detecting an additional browser extension, simply add a (DetectorRegistration) * object like below to the list: * * ``` * { * name: 'exampleExtension', * fns: { * // a synchronous check that should return true if the extension is detected * sync: () => !!document.querySelector(".some-example-class"), * // an asynchronous check that should invoke 'detected' if the extension is detected. * // it can also invoke 'cleanup' with a callback to schedule cleanup tasks * // (such as disconnecting observers). * async: (detected, cleanup) => { * if (document.querySelector(".some-example-class")) { * detected(); * } * } * } * } * ``` */ var supportedDetectors = [{ name: 'grammarly', fns: { sync: function sync() { var _document; return Boolean((_document = document) === null || _document === void 0 ? void 0 : _document.querySelector(SELECTORS.GRAMMARLY)); }, async: function async(detected, cleanup) { var _document2; // First check to see if grammarly already exists on page var exists = Boolean((_document2 = document) === null || _document2 === void 0 ? void 0 : _document2.querySelector(SELECTORS.GRAMMARLY)); if (exists) { detected(); } // Otherwise, setup a mutation observer to observe the page and its children // for newly added nodes. Collect observed mutations in a queue and in 1 second // intervals either process the queue or schedule the processing task for when // the user agent's main thread is idle (if possible). var queue = []; var processQueue = function processQueue() { for (var _i = 0, _queue = queue; _i < _queue.length; _i++) { var mutations = _queue[_i]; var _iterator = _createForOfIteratorHelper(mutations), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var _mutation$addedNodes; var mutation = _step.value; if ((mutation === null || mutation === void 0 ? void 0 : mutation.type) === 'childList' && mutation !== null && mutation !== void 0 && (_mutation$addedNodes = mutation.addedNodes) !== null && _mutation$addedNodes !== void 0 && _mutation$addedNodes.length) { var _exists = Array.from(mutation.addedNodes).some(function (node) { var _node$parentElement; return (_node$parentElement = node.parentElement) === null || _node$parentElement === void 0 ? void 0 : _node$parentElement.querySelector(SELECTORS.GRAMMARLY); }); if (_exists) { detected(); } } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } } queue = []; }; var intervalId = setInterval(function () { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any if (typeof window.requestIdleCallback === 'function') { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any window.requestIdleCallback(processQueue); } else { window.requestAnimationFrame(processQueue); } }, 1000); var observer = new MutationObserver(function (mutations) { queue.push(mutations); }); cleanup(function () { queue = []; clearInterval(intervalId); observer === null || observer === void 0 || observer.disconnect(); }); observer.observe(document.documentElement, { childList: true, subtree: true }); } } }]; /** * Call this to return a list (or a Promise of a list) of detected browser extensions. * * This function supports a **synchronous** and **asynchronous** mode through options. You * must pass a list of the browser extension names you want to target for detection. * Only UserBrowserExtension extensions are supported, other target names will be silently * ignored. * * If the async option is enabled, you must also pass a final timeout by when it * should stop all detection attempts and return any partially detected extensions. * * Example usage: * ``` * // synchronously/immediately check for extensions * const extensions = sniffUserBrowserExtensions({ extensions: ['grammarly', 'requestly'] }); * // result will be ['grammarly'] or ['grammarly','requestly'] or ['requestly'] or []; * * // asynchronously check for extensions up to 30s * sniffUserBrowserExtensions({ * extensions: ['grammarly', 'requestly'], * async: true, * asyncTimeoutMs: 30000, * }).then(extensions => { * // result will be ['grammarly'] or ['grammarly','requestly'] or ['requestly'] or []; * }) * ``` */ export function sniffUserBrowserExtensions(options) { try { // First we filter out supported extensions that aren't requested through options. We also // prepare detector objects with some initial internal state (e.g. detector.state.detected = false) var detectors = registerDetectors(options.extensions, supportedDetectors); // If async mode is enabled, we convert the list of detector objects to a list of promises // that resolve when the detector invokes detected() during its asynchronous check. // We also track any scheduled cleanup() tasks. if (options.async === true) { var asyncCleanups = []; var asyncDetections = Promise.all(detectors.map(function (detector) { return new Promise(function (resolve) { var detected = function detected() { detector.state.detected = true; resolve(); }; var cleanup = function cleanup(cb) { asyncCleanups.push(cb); }; if (typeof detector.fns.async === 'function') { detector.fns.async(detected, cleanup); } else { detector.state.detected = false; resolve(); } }); })); // We race all asynchronous checkers against a user-defined timeout (asyncTimeoutMs). // When asynchronous checks are finalised first,or if the timeout elapses, we return // the list of extensions detected up until that point. var globalTimeout = new Promise(function (resolve) { return setTimeout(resolve, options.asyncTimeoutMs, RACE_COMPLETE); }); return Promise.race([asyncDetections, globalTimeout]).then(function () { return detectors.filter(function (detector) { return detector.state.detected; }).map(function (detector) { return detector.name; }); }) // If there are any errors, we fail safely and silently with zero detected extensions. .catch(function () { return []; }).finally(function () { return asyncCleanups.map(function (cleanup) { return cleanup(); }); }); } else { // If sync mode, we immediately execute synchronous checks // and return a list of extensions whose synchronous checks returned true. return detectors.filter(function (detector) { var _detector$fns$sync, _detector$fns; return (_detector$fns$sync = (_detector$fns = detector.fns).sync) === null || _detector$fns$sync === void 0 ? void 0 : _detector$fns$sync.call(_detector$fns); }).map(function (detector) { return detector.name; }); } } catch (err) { // If there are any unhandled errors, we fail safely and silently with zero detected extensions. return options.async ? Promise.resolve([]) : []; } }