diff --git a/addon/loaders/css.js b/addon/loaders/css.js index 2100f17..1519fb5 100644 --- a/addon/loaders/css.js +++ b/addon/loaders/css.js @@ -1,5 +1,6 @@ import RSVP from 'rsvp'; import { createLoadElement, nodeLoader } from './utilities'; +import { scheduleWork } from './scheduler'; /** * Default loader function for CSS assets. Loads them by inserting a link tag @@ -19,39 +20,51 @@ export default nodeLoader(function css(uri) { return resolve(); } - // Try using the default onload/onerror handlers... - const link = createLoadElement('link', resolve, function(error) { - if (this.parentNode) { - this.parentNode.removeChild(this); - } - reject(error); - }); + scheduleWork(() => { + let link; + try { + // Try using the default onload/onerror handlers... + const link = createLoadElement('link', resolve, function(error) { + if (this.parentNode) { + this.parentNode.removeChild(this); + } + reject(error); + }); - link.rel = 'stylesheet'; - link.href = uri; - - document.head.appendChild(link); - - // In case the browser doesn't fire onload/onerror events, we poll the - // the list of stylesheets to see when it loads... - function checkSheetLoad() { - const resolvedHref = link.href; - const stylesheets = document.styleSheets; - let i = stylesheets.length; - - while (i--) { - const sheet = stylesheets[i]; - if (sheet.href === resolvedHref) { - // Unfortunately we have no way of knowing if the load was - // successful or not, so we always resolve. - setTimeout(resolve); - return; - } + link.rel = 'stylesheet'; + link.href = uri; + + document.head.appendChild(link); + + // In case the browser doesn't fire onload/onerror events, we poll the + // the list of stylesheets to see when it loads... + + setTimeout(checkSheetLoad); + } catch(reason) { + reject(reason); } - setTimeout(checkSheetLoad); - } + function checkSheetLoad() { + try { + const resolvedHref = link.href; + const stylesheets = document.styleSheets; + let i = stylesheets.length; + + while (i--) { + const sheet = stylesheets[i]; + if (sheet.href === resolvedHref) { + // Unfortunately we have no way of knowing if the load was + // successful or not, so we always resolve. + setTimeout(resolve); + return; + } + } - setTimeout(checkSheetLoad); + setTimeout(checkSheetLoad); + } catch(reason) { + reject(reason); + } + } + }); }); }); diff --git a/addon/loaders/js.js b/addon/loaders/js.js index 86a6255..1349a59 100644 --- a/addon/loaders/js.js +++ b/addon/loaders/js.js @@ -1,5 +1,6 @@ import RSVP from 'rsvp'; import { createLoadElement, nodeLoader } from './utilities'; +import { scheduleWork } from './scheduler'; /** * Default loader function for JS assets. Loads them by inserting a script tag @@ -15,16 +16,34 @@ export default nodeLoader(function js(uri) { return resolve(); } - const script = createLoadElement('script', resolve, function(error) { - if (this.parentNode) { - this.parentNode.removeChild(this); - } - reject(error); - }); + // DOM mutation should be batched, this indirection enables this batching. + // By default, it will schedule work on the next afterRender queue. But can + // be configured for further control via. + // + // ```js + // import { setScheduler } from 'ember-asset-loader/scheduler'; + // + // setScheduler(function(work /* work is a callback */) { + // someScheduler.scheduleWork(work); + // }); + // ``` + // + scheduleWork(() => { + try { + const script = createLoadElement('script', resolve, function(error) { + if (this.parentNode) { + this.parentNode.removeChild(this); + } + reject(error); + }); - script.src = uri; - script.async = false; + script.src = uri; + script.async = false; - document.head.appendChild(script); + document.head.appendChild(script); + } catch(e) { + reject(e); + } + }); }); }); diff --git a/addon/loaders/scheduler.js b/addon/loaders/scheduler.js new file mode 100644 index 0000000..30e4cf1 --- /dev/null +++ b/addon/loaders/scheduler.js @@ -0,0 +1,12 @@ +import Ember from 'ember'; +export const defaultScheduler = (work) => Ember.run.schedule('afterRender', work); + +let scheduler = defaultScheduler; + +export function setScheduler(_scheduler) { + scheduler = _scheduler; +} + +export function scheduleWork(work) { + Ember.run.join(scheduler, work); +} \ No newline at end of file diff --git a/tests/unit/services/asset-loader-test.js b/tests/unit/services/asset-loader-test.js index 9b78ad4..c0af4b6 100644 --- a/tests/unit/services/asset-loader-test.js +++ b/tests/unit/services/asset-loader-test.js @@ -315,12 +315,12 @@ module('Unit | Service | asset-loader', function(hooks) { }); test('loadAsset() - js - handles successful load', function(assert) { - assert.expect(1); + assert.expect(0); const service = this.owner.lookup('service:asset-loader'); const asset = { type: 'js', uri: '/unit-test.js' }; - return service.loadAsset(asset).then(shouldHappen(assert), shouldNotHappen(assert)); + return service.loadAsset(asset); }); test('loadAsset() - js - handles failed load', function(assert) { @@ -329,7 +329,7 @@ module('Unit | Service | asset-loader', function(hooks) { const service = this.owner.lookup('service:asset-loader'); const asset = { type: 'js', uri: '/unit-test.jsss' }; - return service.loadAsset(asset).then(shouldNotHappen(assert), shouldHappen(assert)); + return service.loadAsset(asset).catch(() => assert.ok(true, 'loadAsset should reject')); }); test('loadAsset() - js - does not insert additional script tag if asset is in DOM already', function(assert) { @@ -364,12 +364,12 @@ module('Unit | Service | asset-loader', function(hooks) { }); test('loadAsset() - css - handles successful load', function(assert) { - assert.expect(1); + assert.expect(0); const service = this.owner.lookup('service:asset-loader'); const asset = { type: 'css', uri: '/unit-test.css' }; - return service.loadAsset(asset).then(shouldHappen(assert), shouldNotHappen(assert)); + return service.loadAsset(asset); }); test('loadAsset() - css - handles failed load', function(assert) { @@ -382,9 +382,11 @@ module('Unit | Service | asset-loader', function(hooks) { // non-Chrome browsers to either resolve or reject (they should do something). var isChrome = !!window.chrome && window.navigator.vendor === 'Google Inc.'; if (isChrome) { - return service.loadAsset(asset).then(shouldNotHappen(assert), shouldHappen(assert)); + return service.loadAsset(asset).catch(() => assert.ok(true, 'loadAsset should reject')); } else { - return service.loadAsset(asset).then(shouldHappen(assert), shouldHappen(assert)); + return service.loadAsset(asset) + .then(() => assert.ok(true, 'loadAsset may resolve')) + .catch(() => assert.ok(true, 'loadAsset may reject')); } });