Raw template plugin-outlet use can lead to assertion failure

I have a plugin I am working on that extends the topic-list-item with the topic-list-tags plugin-outlet with a file in …/connectors/topic-list-tags/file-name.raw.hbs. When my page loads (I am running in development mode with Vagrant) I am greeted with Uncaught Error: Assertion Failed: undefined must be a subclass or an instance of Ember.View, not in the console, as a result of plugin-outlet.js.es6:164 using undefined as an Ember View object.

This happens because when the connectors are loaded, _connectorCache[‘topic-list-tags’] is set to an empty array, and the raw template is put into _rawCache[‘topic-list-tags’]. Later, because the empty array in _connectorCache[‘topic-list-tags’] is a truthy value, it attempts to load a view from this empty array, which of course is undefined.

The relevant code sections from plugin-outlet.js.es6 include…

 findOutlets(Ember.TEMPLATES, function(outletName, resource, uniqueName) {
    _connectorCache[outletName] = _connectorCache[outletName] || [];

    const mixin = {templateName: resource.replace('javascripts/', '')};
    let viewClass = uniqueViews[uniqueName];

    if (viewClass) {
      // We are going to add it back with the proper template
      _connectorCache[outletName].removeObject(viewClass);
    } else {
      if (!/\.raw$/.test(uniqueName)) {
        viewClass = Ember.View.extend({ classNames: [outletName + '-outlet', uniqueName] });
      }
    }

    if (viewClass) {
      _connectorCache[outletName].pushObject(viewClass.extend(mixin));
    } else {
      // we have a raw template
      if (!_rawCache[outletName]) {
        _rawCache[outletName] = [];
      }

      _rawCache[outletName].push(Ember.TEMPLATES[resource]);
    }
  });

}

and

registerHelper('plugin-outlet', function(params, hash, options, env) {
  const connectionName = params[0];

  if (!_connectorCache) { buildConnectorCache(); }

  if (_connectorCache[connectionName]) {
    const childViews = _connectorCache[connectionName];

    // If there is more than one view, create a container. Otherwise
    // just shove it in.
    const viewClass = (childViews.length > 1) ? Ember.ContainerView : childViews[0];

    const newHash = $.extend({}, viewInjections(env.data.view.container));
    if (hash.tagName) { newHash.tagName = hash.tagName; }

    delete options.fn;  // we don't need the default template since we have a connector
    // ---The error occurs on the line below, where viewClass is undefined---
    env.helpers.view.helperFunction.call(this, [viewClass], newHash, options, env);
[...]
  }
});

A very easy workaround is to simply add an additional .hbs (not .raw.hbs) file to the …/connectors/topic-list-tags/ directory. This makes _connectorCache[‘topic-list-tags’] into an array with one element, preventing the error.

Alternatively, it would be very simple to either not create the _connectorCache entry in the first place for a raw template, or to ensure the array is not empty before indexing it.

What I don’t understand in all of this is why this does not seem to be a problem for the majority of Discourse users, since popular plugins like the Topic List Preview and other customizations also depend on the topic-list-tags outlet.

Is this is not a general bug, then I would want to know what keeps this from impacting other systems. Is the error harmlessly ignored when Discourse is run in production mode?

I have made a pull request to fix this: https://github.com/discourse/discourse/pull/4296

2 Likes

@eviltrout can you review this? :slight_smile: