Ghost/ghost/admin/app/services/lazy-loader.js
Emilio Cobos Álvarez 28b5f7de52 🐛 Fixed night shift toggle in Firefox Nightly. (#1174)
Over in:

 * https://github.com/whatwg/html/issues/3840
 * https://bugzilla.mozilla.org/show_bug.cgi?id=1281135

I'm trying to come up with a model for `<link rel="stylesheet" disabled>` in
which Blink / WebKit and Firefox can agree on.

See that HTML spec issue for all the inconsistencies of WebKit / Blink, and the
following post for more context:

 * https://groups.google.com/d/msg/mozilla.dev.platform/BdgNaChHnpY/mhXzCBwSCgAJ

---

Unfortunately, my change to Firefox breaks the Ghost Admin panel night-mode
switch (you can see it in Firefox Nightly).

This is because with my change, removing the `disabled` attribute from an
stylesheet behaves the same regardless of whether the `disabled` attribute is
added dynamically or not.

That means that adding the `disabled` attribute dynamically "unloads" the
stylesheet completely (just like when the attribute is there before inserting
the link in the document, or from the parser). Thus removing the attribute will
load the stylesheet again and fire a load event.

This is problematic for the code as-is, because it means that each time that the
load event fires when the disabled attribute is removed on an alternate, then
it's added again. :)

Prevent that from happening by removing the load event listener ASAP. What this
code wants is to only resolve the promise once after all.

Given this is so far the only regression from my change that has been reported
(over at https://bugzilla.mozilla.org/show_bug.cgi?id=1546707), I think fixing
the Ghost-Admin panel is worth it.

If this pattern is somehow common, then we'll probably revert that patch and go
back to the sad current state of affairs regarding interop :(
2019-04-25 09:40:26 +02:00

86 lines
2.5 KiB
JavaScript

import RSVP from 'rsvp';
import Service, {inject as service} from '@ember/service';
import config from 'ghost-admin/config/environment';
export default Service.extend({
ajax: service(),
ghostPaths: service(),
// This is needed so we can disable it in unit tests
testing: undefined,
scriptPromises: null,
init() {
this._super(...arguments);
this.scriptPromises = {};
if (this.testing === undefined) {
this.testing = config.environment === 'test';
}
},
loadScript(key, url) {
if (this.testing) {
return RSVP.resolve();
}
if (this.scriptPromises[key]) {
return this.scriptPromises[key];
}
let scriptPromise = new RSVP.Promise((resolve, reject) => {
let {adminRoot} = this.ghostPaths;
let script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = `${adminRoot}${url}`;
let el = document.getElementsByTagName('script')[0];
el.parentNode.insertBefore(script, el);
script.addEventListener('load', () => {
resolve();
});
script.addEventListener('error', () => {
reject(new Error(`${url} failed to load`));
});
});
this.scriptPromises[key] = scriptPromise;
return scriptPromise;
},
loadStyle(key, url, alternate = false) {
if (this.testing || document.querySelector(`#${key}-styles`)) {
return RSVP.resolve();
}
return new RSVP.Promise((resolve, reject) => {
let link = document.createElement('link');
link.id = `${key}-styles`;
link.rel = alternate ? 'alternate stylesheet' : 'stylesheet';
link.href = `${this.ghostPaths.adminRoot}${url}`;
link.onload = () => {
link.onload = null;
if (alternate) {
// If stylesheet is alternate and we disable the stylesheet before injecting into the DOM,
// the onload handler never gets called. Thus, we should disable the link after it has finished loading
link.disabled = true;
}
resolve();
};
link.onerror = reject;
if (alternate) {
link.title = key;
}
document.querySelector('head').appendChild(link);
});
}
});