@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
212 lines (209 loc) • 9.78 kB
JavaScript
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([]) : [];
}
}