FIX: Memory Leaks when decorating posts (#7749)
* Remove long-deprecated method * FIX: Memory Leaks when decorating posts Previously we'd keep creating mixins dynamically when decorating the same class. This code changes the API to recommend an `id` parameter for each decorator which will avoid leaks. All plugins should be updated to include this parameter, although if they don't in the meantime it'll just mean a warning in the console (and a continued leak.)
This commit is contained in:
parent
934adb14d2
commit
c322cccd53
|
@ -9,25 +9,32 @@ export default {
|
|||
initialize(container) {
|
||||
withPluginApi("0.1", api => {
|
||||
const siteSettings = container.lookup("site-settings:main");
|
||||
api.decorateCooked(highlightSyntax);
|
||||
api.decorateCooked(lightbox);
|
||||
api.decorateCooked(highlightSyntax, {
|
||||
id: "discourse-syntax-highlighting"
|
||||
});
|
||||
api.decorateCooked(lightbox, { id: "discourse-lightbox" });
|
||||
if (siteSettings.support_mixed_text_direction) {
|
||||
api.decorateCooked(setTextDirections);
|
||||
api.decorateCooked(setTextDirections, {
|
||||
id: "discourse-text-direction"
|
||||
});
|
||||
}
|
||||
|
||||
setupLazyLoading(api);
|
||||
|
||||
api.decorateCooked($elem => {
|
||||
const players = $("audio", $elem);
|
||||
if (players.length) {
|
||||
players.on("play", () => {
|
||||
const postId = parseInt($elem.closest("article").data("post-id"));
|
||||
if (postId) {
|
||||
api.preventCloak(postId);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
api.decorateCooked(
|
||||
$elem => {
|
||||
const players = $("audio", $elem);
|
||||
if (players.length) {
|
||||
players.on("play", () => {
|
||||
const postId = parseInt($elem.closest("article").data("post-id"));
|
||||
if (postId) {
|
||||
api.preventCloak(postId);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{ id: "discourse-audio" }
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -95,6 +95,6 @@ export function setupLazyLoading(api) {
|
|||
}
|
||||
});
|
||||
},
|
||||
{ onlyStream: true }
|
||||
{ onlyStream: true, id: "discourse-lazy-load" }
|
||||
);
|
||||
}
|
||||
|
|
|
@ -193,8 +193,14 @@ class PluginApi {
|
|||
* For example, to add a yellow background to all posts you could do this:
|
||||
*
|
||||
* ```
|
||||
* api.decorateCooked($elem => $elem.css({ backgroundColor: 'yellow' }));
|
||||
* api.decorateCooked(
|
||||
* $elem => $elem.css({ backgroundColor: 'yellow' }),
|
||||
* { id: 'yellow-decorator' }
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* NOTE: To avoid memory leaks, it is highly recommended to pass a unique `id` parameter.
|
||||
* You will receive a warning if you do not.
|
||||
**/
|
||||
decorateCooked(callback, opts) {
|
||||
opts = opts || {};
|
||||
|
@ -202,12 +208,13 @@ class PluginApi {
|
|||
addDecorator(callback);
|
||||
|
||||
if (!opts.onlyStream) {
|
||||
decorate(ComposerEditor, "previewRefreshed", callback);
|
||||
decorate(DiscourseBanner, "didInsertElement", callback);
|
||||
decorate(ComposerEditor, "previewRefreshed", callback, opts.id);
|
||||
decorate(DiscourseBanner, "didInsertElement", callback, opts.id);
|
||||
decorate(
|
||||
this.container.factoryFor("component:user-stream").class,
|
||||
"didInsertElement",
|
||||
callback
|
||||
callback,
|
||||
opts.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -930,7 +937,26 @@ export function withPluginApi(version, apiCodeCallback, opts) {
|
|||
}
|
||||
|
||||
let _decorateId = 0;
|
||||
function decorate(klass, evt, cb) {
|
||||
let _decorated = new WeakMap();
|
||||
|
||||
function decorate(klass, evt, cb, id) {
|
||||
if (!id) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
"`decorateCooked` should be supplied with an `id` option to avoid memory leaks."
|
||||
);
|
||||
} else {
|
||||
if (!_decorated.has(klass)) {
|
||||
_decorated.set(klass, new Set());
|
||||
}
|
||||
id = `${id}:${evt}`;
|
||||
let set = _decorated.get(klass);
|
||||
if (set.has(id)) {
|
||||
return;
|
||||
}
|
||||
set.add(id);
|
||||
}
|
||||
|
||||
const mixin = {};
|
||||
mixin["_decorate_" + _decorateId++] = function($elem) {
|
||||
$elem = $elem || this.$();
|
||||
|
@ -944,10 +970,3 @@ function decorate(klass, evt, cb) {
|
|||
export function resetPluginApi() {
|
||||
_pluginv01 = null;
|
||||
}
|
||||
|
||||
export function decorateCooked() {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
"`decorateCooked` has been removed. Use `getPluginApi(version).decorateCooked` instead"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
|
||||
function initializeDetails(api) {
|
||||
api.decorateCooked($elem => $("details", $elem).details());
|
||||
api.decorateCooked($elem => $("details", $elem).details(), {
|
||||
id: "discourse-details"
|
||||
});
|
||||
|
||||
api.addToolbarPopupMenuOptionsCallback(() => {
|
||||
return {
|
||||
|
|
|
@ -2,9 +2,12 @@ import { withPluginApi } from "discourse/lib/plugin-api";
|
|||
import showModal from "discourse/lib/show-modal";
|
||||
|
||||
function initializeDiscourseLocalDates(api) {
|
||||
api.decorateCooked($elem => {
|
||||
$(".discourse-local-date", $elem).applyLocalDates();
|
||||
});
|
||||
api.decorateCooked(
|
||||
$elem => {
|
||||
$(".discourse-local-date", $elem).applyLocalDates();
|
||||
},
|
||||
{ id: "discourse-local-date" }
|
||||
);
|
||||
|
||||
api.onToolbarCreate(toolbar => {
|
||||
toolbar.addButton({
|
||||
|
|
|
@ -4,22 +4,25 @@ export default {
|
|||
name: "apply-lazyYT",
|
||||
initialize() {
|
||||
withPluginApi("0.1", api => {
|
||||
api.decorateCooked($elem => {
|
||||
const iframes = $(".lazyYT", $elem);
|
||||
if (iframes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(".lazyYT", $elem).lazyYT({
|
||||
onPlay(e, $el) {
|
||||
// don't cloak posts that have playing videos in them
|
||||
const postId = parseInt($el.closest("article").data("post-id"));
|
||||
if (postId) {
|
||||
api.preventCloak(postId);
|
||||
}
|
||||
api.decorateCooked(
|
||||
$elem => {
|
||||
const iframes = $(".lazyYT", $elem);
|
||||
if (iframes.length === 0) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".lazyYT", $elem).lazyYT({
|
||||
onPlay(e, $el) {
|
||||
// don't cloak posts that have playing videos in them
|
||||
const postId = parseInt($el.closest("article").data("post-id"));
|
||||
if (postId) {
|
||||
api.preventCloak(postId);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
{ id: "discourse-lazyyt" }
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -104,7 +104,7 @@ function initializePolls(api) {
|
|||
}
|
||||
|
||||
api.includePostAttributes("polls", "polls_votes");
|
||||
api.decorateCooked(attachPolls, { onlyStream: true });
|
||||
api.decorateCooked(attachPolls, { onlyStream: true, id: "discourse-poll" });
|
||||
api.cleanupStream(cleanUpPolls);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue