DEV: attempts to fix various leaks (#9428)

* DEV: attempts to fix various leaks

* scheduleOnce doesnt work with anon function

* removes the I18n change
This commit is contained in:
Joffrey JAFFEUX 2020-04-16 07:58:04 +02:00 committed by GitHub
parent ca06991ee5
commit 5e24436454
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 103 additions and 73 deletions

View File

@ -1,7 +1,7 @@
import { import {
run, run,
cancel, cancel,
scheduleOnce, schedule,
later, later,
debounce, debounce,
throttle throttle
@ -65,7 +65,7 @@ export default Component.extend(KeyEnterEscape, {
"composer.canEditTopicFeaturedLink" "composer.canEditTopicFeaturedLink"
) )
resize() { resize() {
scheduleOnce("afterRender", () => { schedule("afterRender", () => {
if (!this.element || this.isDestroying || this.isDestroyed) { if (!this.element || this.isDestroying || this.isDestroyed) {
return; return;
} }

View File

@ -1,4 +1,4 @@
import { debounce, later, next, scheduleOnce, throttle } from "@ember/runloop"; import { debounce, later, next, schedule, throttle } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import userSearch from "discourse/lib/user-search"; import userSearch from "discourse/lib/user-search";
import discourseComputed, { import discourseComputed, {
@ -194,7 +194,7 @@ export default Component.extend({
transformComplete: v => v.username || v.name, transformComplete: v => v.username || v.name,
afterComplete() { afterComplete() {
// ensures textarea scroll position is correct // ensures textarea scroll position is correct
scheduleOnce("afterRender", () => $input.blur().focus()); schedule("afterRender", () => $input.blur().focus());
}, },
triggerRule: textarea => triggerRule: textarea =>
!inCodeBlock(textarea.value, caretPosition(textarea)) !inCodeBlock(textarea.value, caretPosition(textarea))
@ -323,7 +323,7 @@ export default Component.extend({
this.appEvents.on(event, this, this._resetShouldBuildScrollMap); this.appEvents.on(event, this, this._resetShouldBuildScrollMap);
}); });
scheduleOnce("afterRender", () => { schedule("afterRender", () => {
$input.on("touchstart mouseenter", () => { $input.on("touchstart mouseenter", () => {
if (!$preview.is(":visible")) return; if (!$preview.is(":visible")) return;
$preview.off("scroll"); $preview.off("scroll");
@ -569,7 +569,7 @@ export default Component.extend({
}, },
_warnMentionedGroups($preview) { _warnMentionedGroups($preview) {
scheduleOnce("afterRender", () => { schedule("afterRender", () => {
var found = this.warnedGroupMentions || []; var found = this.warnedGroupMentions || [];
$preview.find(".mention-group.notify").each((idx, e) => { $preview.find(".mention-group.notify").each((idx, e) => {
const $e = $(e); const $e = $(e);
@ -597,7 +597,7 @@ export default Component.extend({
return; return;
} }
scheduleOnce("afterRender", () => { schedule("afterRender", () => {
let found = this.warnedCannotSeeMentions || []; let found = this.warnedCannotSeeMentions || [];
$preview.find(".mention.cannot-see").each((idx, e) => { $preview.find(".mention.cannot-see").each((idx, e) => {

View File

@ -356,7 +356,7 @@ export default Component.extend({
return; return;
} }
this.set("preview", cooked); this.set("preview", cooked);
scheduleOnce("afterRender", () => { schedule("afterRender", () => {
if (this._state !== "inDOM") { if (this._state !== "inDOM") {
return; return;
} }
@ -559,7 +559,7 @@ export default Component.extend({
}, },
_selectText(from, length) { _selectText(from, length) {
scheduleOnce("afterRender", () => { schedule("afterRender", () => {
const textarea = this.element.querySelector("textarea.d-editor-input"); const textarea = this.element.querySelector("textarea.d-editor-input");
const $textarea = $(textarea); const $textarea = $(textarea);
const oldScrollPos = $textarea.scrollTop(); const oldScrollPos = $textarea.scrollTop();
@ -898,7 +898,7 @@ export default Component.extend({
// ensures textarea scroll position is correct // ensures textarea scroll position is correct
_focusTextArea() { _focusTextArea() {
const textarea = this.element.querySelector("textarea.d-editor-input"); const textarea = this.element.querySelector("textarea.d-editor-input");
scheduleOnce("afterRender", () => { schedule("afterRender", () => {
textarea.blur(); textarea.blur();
textarea.focus(); textarea.focus();
}); });

View File

@ -1,4 +1,4 @@
import { scheduleOnce } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import { buildCategoryPanel } from "discourse/components/edit-category-panel"; import { buildCategoryPanel } from "discourse/components/edit-category-panel";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
@ -6,7 +6,7 @@ export default buildCategoryPanel("topic-template", {
@observes("activeTab") @observes("activeTab")
_activeTabChanged: function() { _activeTabChanged: function() {
if (this.activeTab) { if (this.activeTab) {
scheduleOnce("afterRender", () => schedule("afterRender", () =>
this.element.querySelector(".d-editor-input").focus() this.element.querySelector(".d-editor-input").focus()
); );
} }

View File

@ -1,5 +1,5 @@
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { schedule } from "@ember/runloop"; import { throttle, debounce, schedule } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import { on, observes } from "discourse-common/utils/decorators"; import { on, observes } from "discourse-common/utils/decorators";
import { findRawTemplate } from "discourse/lib/raw-templates"; import { findRawTemplate } from "discourse/lib/raw-templates";
@ -12,8 +12,6 @@ import {
import { safariHacksDisabled } from "discourse/lib/utilities"; import { safariHacksDisabled } from "discourse/lib/utilities";
import ENV, { INPUT_DELAY } from "discourse-common/config/environment"; import ENV, { INPUT_DELAY } from "discourse-common/config/environment";
const { run } = Ember;
const PER_ROW = 11; const PER_ROW = 11;
function customEmojis() { function customEmojis() {
const list = extendedEmojiList(); const list = extendedEmojiList();
@ -54,7 +52,7 @@ export default Component.extend({
recentEmojis: this.emojiStore.favorites recentEmojis: this.emojiStore.favorites
}); });
run.scheduleOnce("afterRender", this, function() { schedule("afterRender", this, function() {
this._bindEvents(); this._bindEvents();
this._loadCategoriesEmojis(); this._loadCategoriesEmojis();
this._positionPicker(); this._positionPicker();
@ -110,7 +108,7 @@ export default Component.extend({
filterChanged() { filterChanged() {
this.$filter.find(".clear-filter").toggle(!_.isEmpty(this.filter)); this.$filter.find(".clear-filter").toggle(!_.isEmpty(this.filter));
const filterDelay = this.site.isMobileDevice ? 400 : INPUT_DELAY; const filterDelay = this.site.isMobileDevice ? 400 : INPUT_DELAY;
run.debounce(this, this._filterEmojisList, filterDelay); debounce(this, this._filterEmojisList, filterDelay);
}, },
@observes("selectedDiversity") @observes("selectedDiversity")
@ -309,11 +307,11 @@ export default Component.extend({
_bindResizing() { _bindResizing() {
$(window).on("resize", () => { $(window).on("resize", () => {
run.throttle(this, this._positionPicker, 16); throttle(this, this._positionPicker, 16);
}); });
$("#reply-control").on("div-resizing", () => { $("#reply-control").on("div-resizing", () => {
run.throttle(this, this._positionPicker, 16); throttle(this, this._positionPicker, 16);
}); });
}, },
@ -376,7 +374,7 @@ export default Component.extend({
_bindSectionsScroll() { _bindSectionsScroll() {
let onScroll = () => { let onScroll = () => {
run.debounce(this, this._checkVisibleSection, 50); debounce(this, this._checkVisibleSection, 50);
}; };
this.$list.on("scroll", onScroll); this.$list.on("scroll", onScroll);

View File

@ -1,12 +1,12 @@
import { TextArea } from "@ember/component"; import { TextArea } from "@ember/component";
import { scheduleOnce } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import { on, observes } from "discourse-common/utils/decorators"; import { on, observes } from "discourse-common/utils/decorators";
import autosize from "discourse/lib/autosize"; import autosize from "discourse/lib/autosize";
export default TextArea.extend({ export default TextArea.extend({
@on("didInsertElement") @on("didInsertElement")
_startWatching() { _startWatching() {
scheduleOnce("afterRender", () => { schedule("afterRender", () => {
$(this.element).focus(); $(this.element).focus();
autosize(this.element); autosize(this.element);
}); });

View File

@ -1,4 +1,4 @@
import { scheduleOnce } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse/lib/debounce";
import toMarkdown from "discourse/lib/to-markdown"; import toMarkdown from "discourse/lib/to-markdown";
@ -138,7 +138,11 @@ export default Component.extend({
} }
// change the position of the button // change the position of the button
scheduleOnce("afterRender", () => { schedule("afterRender", () => {
if (!this.element || this.isDestroying || this.isDestroyed) {
return;
}
let top = markerOffset.top; let top = markerOffset.top;
let left = markerOffset.left + Math.max(0, parentScrollLeft); let left = markerOffset.left + Math.max(0, parentScrollLeft);
@ -160,6 +164,8 @@ export default Component.extend({
}, },
didInsertElement() { didInsertElement() {
this._super(...arguments);
const { isWinphone, isAndroid } = this.capabilities; const { isWinphone, isAndroid } = this.capabilities;
const wait = isWinphone || isAndroid ? INPUT_DELAY : 25; const wait = isWinphone || isAndroid ? INPUT_DELAY : 25;
const onSelectionChanged = discourseDebounce( const onSelectionChanged = discourseDebounce(

View File

@ -77,7 +77,7 @@ export default Component.extend({
this._init(); this._init();
scheduleOnce("afterRender", () => this._update()); scheduleOnce("afterRender", this, this._update);
}, },
@observes("searchTerm") @observes("searchTerm")

View File

@ -1,5 +1,5 @@
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { cancel, debounce, scheduleOnce } from "@ember/runloop"; import { cancel, debounce, schedule } from "@ember/runloop";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import { searchForTerm } from "discourse/lib/search"; import { searchForTerm } from "discourse/lib/search";
@ -17,7 +17,7 @@ export default Controller.extend(ModalFunctionality, {
selectedRow: -1 selectedRow: -1
}); });
scheduleOnce("afterRender", () => { schedule("afterRender", () => {
const element = document.querySelector(".insert-link"); const element = document.querySelector(".insert-link");
element.addEventListener("keydown", e => this.keyDown(e)); element.addEventListener("keydown", e => this.keyDown(e));

View File

@ -2,8 +2,7 @@ import discourseComputed from "discourse-common/utils/decorators";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { alias, or, readOnly } from "@ember/object/computed"; import { alias, or, readOnly } from "@ember/object/computed";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import { next } from "@ember/runloop"; import { next, schedule } from "@ember/runloop";
import { scheduleOnce } from "@ember/runloop";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
@ -152,7 +151,7 @@ export default Controller.extend(ModalFunctionality, {
// only need to focus the 2FA input for TOTP // only need to focus the 2FA input for TOTP
if (!this.showSecurityKey) { if (!this.showSecurityKey) {
scheduleOnce("afterRender", () => schedule("afterRender", () =>
document document
.getElementById("second-factor") .getElementById("second-factor")
.querySelector("input") .querySelector("input")

View File

@ -1,8 +1,7 @@
import { isPresent, isEmpty } from "@ember/utils"; import { isPresent, isEmpty } from "@ember/utils";
import { or, and, not, alias } from "@ember/object/computed"; import { or, and, not, alias } from "@ember/object/computed";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import { next } from "@ember/runloop"; import { next, schedule } from "@ember/runloop";
import { scheduleOnce } from "@ember/runloop";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import { bufferedProperty } from "discourse/mixins/buffered-content"; import { bufferedProperty } from "discourse/mixins/buffered-content";
import Composer from "discourse/models/composer"; import Composer from "discourse/models/composer";
@ -137,9 +136,7 @@ export default Controller.extend(bufferedProperty("model"), {
return; return;
} }
scheduleOnce("afterRender", () => { schedule("afterRender", () => this.send("showHistory", post, revision));
this.send("showHistory", post, revision);
});
}, },
showCategoryChooser: not("model.isPrivateMessage"), showCategoryChooser: not("model.isPrivateMessage"),

View File

@ -7,5 +7,9 @@ export default {
initialize(container) { initialize(container) {
KeyboardShortcuts.init(Mousetrap, container); KeyboardShortcuts.init(Mousetrap, container);
KeyboardShortcuts.bindEvents(); KeyboardShortcuts.bindEvents();
},
teardown() {
KeyboardShortcuts.teardown();
} }
}; };

View File

@ -13,6 +13,14 @@ export default {
.on("notifications:changed", this, "_updateTitle"); .on("notifications:changed", this, "_updateTitle");
}, },
teardown(container) {
container
.lookup("service:app-events")
.off("notifications:changed", this, "_updateTitle");
this.container = null;
},
_updateTitle() { _updateTitle() {
const user = this.container.lookup("current-user:main"); const user = this.container.lookup("current-user:main");
if (!user) return; // must be logged in if (!user) return; // must be logged in

View File

@ -106,6 +106,10 @@ export default {
}); });
}, },
teardown() {
this.container = null;
},
bindKey(key, binding = null) { bindKey(key, binding = null) {
if (!binding) { if (!binding) {
binding = DEFAULT_BINDINGS[key]; binding = DEFAULT_BINDINGS[key];

View File

@ -1,4 +1,4 @@
import { scheduleOnce } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { userPath } from "discourse/lib/url"; import { userPath } from "discourse/lib/url";
import { formatUsername } from "discourse/lib/utilities"; import { formatUsername } from "discourse/lib/utilities";
@ -40,7 +40,7 @@ const checked = {};
const cannotSee = []; const cannotSee = [];
function updateFound($mentions, usernames) { function updateFound($mentions, usernames) {
scheduleOnce("afterRender", function() { schedule("afterRender", function() {
$mentions.each((i, e) => { $mentions.each((i, e) => {
const $e = $(e); const $e = $(e);
const username = usernames[i]; const username = usernames[i];

View File

@ -1,5 +1,5 @@
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { scheduleOnce } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import Draft from "discourse/models/draft"; import Draft from "discourse/models/draft";
@ -60,7 +60,7 @@ export default DiscourseRoute.extend({
topicController.subscribe(); topicController.subscribe();
// Highlight our post after the next render // Highlight our post after the next render
scheduleOnce("afterRender", () => schedule("afterRender", () =>
this.appEvents.trigger("post:highlight", closest) this.appEvents.trigger("post:highlight", closest)
); );

View File

@ -1,20 +1,26 @@
import { get } from "@ember/object"; import { get } from "@ember/object";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { cancel, later, scheduleOnce } from "@ember/runloop"; import { cancel, later, schedule } from "@ember/runloop";
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import { ID_CONSTRAINT } from "discourse/models/topic"; import { ID_CONSTRAINT } from "discourse/models/topic";
import { EventTarget } from "rsvp"; import { EventTarget } from "rsvp";
let isTransitioning = false,
scheduledReplace = null,
lastScrollPos = null;
const SCROLL_DELAY = 500; const SCROLL_DELAY = 500;
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
const TopicRoute = DiscourseRoute.extend({ const TopicRoute = DiscourseRoute.extend({
init() {
this._super(...arguments);
this.setProperties({
isTransitioning: false,
scheduledReplace: null,
lastScrollPos: null
});
},
redirect() { redirect() {
return this.redirectIfLoginRequired(); return this.redirectIfLoginRequired();
}, },
@ -169,7 +175,7 @@ const TopicRoute = DiscourseRoute.extend({
// Use replaceState to update the URL once it changes // Use replaceState to update the URL once it changes
postChangedRoute(currentPost) { postChangedRoute(currentPost) {
// do nothing if we are transitioning to another route // do nothing if we are transitioning to another route
if (isTransitioning || TopicRoute.disableReplaceState) { if (this.isTransitioning || TopicRoute.disableReplaceState) {
return; return;
} }
@ -180,14 +186,17 @@ const TopicRoute = DiscourseRoute.extend({
postUrl += "/" + currentPost; postUrl += "/" + currentPost;
} }
cancel(scheduledReplace); cancel(this.scheduledReplace);
lastScrollPos = parseInt($(document).scrollTop(), 10);
scheduledReplace = later( this.setProperties({
this, lastScrollPos: parseInt($(document).scrollTop(), 10),
"_replaceUnlessScrolling", scheduledReplace: later(
postUrl, this,
Ember.Test ? 0 : SCROLL_DELAY "_replaceUnlessScrolling",
); postUrl,
Ember.Test ? 0 : SCROLL_DELAY
)
});
} }
}, },
@ -198,8 +207,8 @@ const TopicRoute = DiscourseRoute.extend({
willTransition() { willTransition() {
this._super(...arguments); this._super(...arguments);
cancel(scheduledReplace); cancel(this.scheduledReplace);
isTransitioning = true; this.set("isTransitioning", true);
return true; return true;
} }
}, },
@ -208,17 +217,20 @@ const TopicRoute = DiscourseRoute.extend({
// within a topic until scrolling stops // within a topic until scrolling stops
_replaceUnlessScrolling(url) { _replaceUnlessScrolling(url) {
const currentPos = parseInt($(document).scrollTop(), 10); const currentPos = parseInt($(document).scrollTop(), 10);
if (currentPos === lastScrollPos) { if (currentPos === this.lastScrollPos) {
DiscourseURL.replaceState(url); DiscourseURL.replaceState(url);
return; return;
} }
lastScrollPos = currentPos;
scheduledReplace = later( this.setProperties({
this, lastScrollPos: currentPos,
"_replaceUnlessScrolling", scheduledReplace: later(
url, this,
SCROLL_DELAY "_replaceUnlessScrolling",
); url,
SCROLL_DELAY
)
});
}, },
setupParams(topic, params) { setupParams(topic, params) {
@ -264,7 +276,7 @@ const TopicRoute = DiscourseRoute.extend({
activate() { activate() {
this._super(...arguments); this._super(...arguments);
isTransitioning = false; this.set("isTransitioning", false);
const topic = this.modelFor("topic"); const topic = this.modelFor("topic");
this.session.set("lastTopicIdViewed", parseInt(topic.get("id"), 10)); this.session.set("lastTopicIdViewed", parseInt(topic.get("id"), 10));
@ -292,7 +304,7 @@ const TopicRoute = DiscourseRoute.extend({
setupController(controller, model) { setupController(controller, model) {
// In case we navigate from one topic directly to another // In case we navigate from one topic directly to another
isTransitioning = false; this.set("isTransitioning", false);
controller.setProperties({ controller.setProperties({
model, model,
@ -314,9 +326,9 @@ const TopicRoute = DiscourseRoute.extend({
// We reset screen tracking every time a topic is entered // We reset screen tracking every time a topic is entered
this.screenTrack.start(model.get("id"), controller); this.screenTrack.start(model.get("id"), controller);
scheduleOnce("afterRender", () => { schedule("afterRender", () =>
this.appEvents.trigger("header:update-topic", model); this.appEvents.trigger("header:update-topic", model)
}); );
} }
}); });

View File

@ -1,4 +1,4 @@
import { scheduleOnce } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
export default Component.extend({ export default Component.extend({
@ -63,7 +63,7 @@ export default Component.extend({
this.updateField(); this.updateField();
this.set("inviteEmail", ""); this.set("inviteEmail", "");
scheduleOnce("afterRender", () => schedule("afterRender", () =>
this.element.querySelector(".invite-email").focus() this.element.querySelector(".invite-email").focus()
); );
}, },

View File

@ -1,4 +1,4 @@
import { scheduleOnce } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import getUrl from "discourse-common/lib/get-url"; import getUrl from "discourse-common/lib/get-url";
import discourseComputed, { observes } from "discourse-common/utils/decorators"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
@ -96,7 +96,7 @@ export default Component.extend({
}, },
autoFocus() { autoFocus() {
scheduleOnce("afterRender", () => { schedule("afterRender", () => {
const $invalid = $(".wizard-field.invalid:eq(0) .wizard-focusable"); const $invalid = $(".wizard-field.invalid:eq(0) .wizard-focusable");
if ($invalid.length) { if ($invalid.length) {
@ -108,7 +108,7 @@ export default Component.extend({
}, },
animateInvalidFields() { animateInvalidFields() {
scheduleOnce("afterRender", () => schedule("afterRender", () =>
$(".invalid input[type=text], .invalid textarea").wiggle(2, 100) $(".invalid input[type=text], .invalid textarea").wiggle(2, 100)
); );
}, },

View File

@ -201,6 +201,8 @@ QUnit.testDone(function() {
}); });
window.MessageBus.unsubscribe("*"); window.MessageBus.unsubscribe("*");
delete window.server;
window.Mousetrap.reset();
}); });
// Load ES6 tests // Load ES6 tests