You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by li...@apache.org on 2010/02/02 01:13:50 UTC

svn commit: r905469 - in /incubator/shindig/trunk: config/ php/config/ php/src/gadgets/ php/src/gadgets/render/ php/src/gadgets/servlet/

Author: lindner
Date: Tue Feb  2 00:13:49 2010
New Revision: 905469

URL: http://svn.apache.org/viewvc?rev=905469&view=rev
Log:
SHINDIG-1260 | Patch from Jacky Wang | Improper JavaScript inclusion logic leads to out-of-order and duplicate includes / JavaScript errors

Modified:
    incubator/shindig/trunk/config/container.js
    incubator/shindig/trunk/php/config/container.php
    incubator/shindig/trunk/php/src/gadgets/GadgetContext.php
    incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php
    incubator/shindig/trunk/php/src/gadgets/GadgetFeatureRegistry.php
    incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php
    incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php
    incubator/shindig/trunk/php/src/gadgets/render/GadgetHtmlRenderer.php
    incubator/shindig/trunk/php/src/gadgets/render/GadgetRenderer.php
    incubator/shindig/trunk/php/src/gadgets/render/GadgetUrlRenderer.php
    incubator/shindig/trunk/php/src/gadgets/servlet/JsServlet.php

Modified: incubator/shindig/trunk/config/container.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/config/container.js?rev=905469&r1=905468&r2=905469&view=diff
==============================================================================
--- incubator/shindig/trunk/config/container.js (original)
+++ incubator/shindig/trunk/config/container.js Tue Feb  2 00:13:49 2010
@@ -114,7 +114,7 @@
   },
   "rpc" : {
     // Path to the relay file. Automatically appended to the parent
-    /// parameter if it passes input validation and is not null.
+    // parameter if it passes input validation and is not null.
     // This should never be on the same host in a production environment!
     // Only use this for TESTING!
     "parentRelayUrl" : "/gadgets/files/container/rpc_relay.html",

Modified: incubator/shindig/trunk/php/config/container.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/config/container.php?rev=905469&r1=905468&r2=905469&view=diff
==============================================================================
--- incubator/shindig/trunk/php/config/container.php (original)
+++ incubator/shindig/trunk/php/config/container.php Tue Feb  2 00:13:49 2010
@@ -110,7 +110,7 @@
   'jsondb_path' => realpath(dirname(__FILE__) . '/../../javascript/sampledata') . '/canonicaldb.json',
 
   // Force these libraries to be external (included through <script src="..."> tags), this way they could be cached by the browser
-  'focedJsLibs' => '',
+  'forcedJsLibs' => '',
 
   // After checking the internal __autoload function, shindig can also call the 'extension_autoloader' function to load an
   // unknown custom class, this is particuarly useful for when intergrating shindig into an existing framework that also depends on autoloading

Modified: incubator/shindig/trunk/php/src/gadgets/GadgetContext.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetContext.php?rev=905469&r1=905468&r2=905469&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetContext.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetContext.php Tue Feb  2 00:13:49 2010
@@ -46,7 +46,7 @@
 
     // Request variables
     $this->setIgnoreCache($this->getIgnoreCacheParam());
-    $this->setForcedJsLibs($this->getFocedJsLibsParam());
+    $this->setForcedJsLibs($this->getForcedJsLibsParam());
     $this->setUrl($this->getUrlParam());
     $this->setModuleId($this->getModuleIdParam());
     $this->setView($this->getViewParam());
@@ -79,7 +79,7 @@
     return (isset($_GET['nocache']) && intval($_GET['nocache']) == 1) || (isset($_GET['bpc']) && intval($_GET['bpc']) == 1);
   }
 
-  private function getFocedJsLibsParam() {
+  private function getForcedJsLibsParam() {
     return isset($_GET['libs']) ? trim($_GET['libs']) : null;
   }
 

Modified: incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php?rev=905469&r1=905468&r2=905469&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetFactory.php Tue Feb  2 00:13:49 2010
@@ -70,7 +70,9 @@
    */
   private function parseFeatures(Gadget &$gadget) {
     $found = $missing = array();
-    if (! $this->context->getRegistry()->resolveFeatures(array_merge($gadget->gadgetSpec->requiredFeatures, $gadget->gadgetSpec->optionalFeatures), $found, $missing)) {
+    if (! $this->context->getRegistry()->resolveFeatures(
+        array_merge($gadget->gadgetSpec->requiredFeatures, $gadget->gadgetSpec->optionalFeatures),
+        $found, $missing, true)) {
       $requiredMissing = false;
       foreach ($missing as $featureName) {
         if (in_array($featureName, $gadget->gadgetSpec->requiredFeatures)) {

Modified: incubator/shindig/trunk/php/src/gadgets/GadgetFeatureRegistry.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetFeatureRegistry.php?rev=905469&r1=905468&r2=905469&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetFeatureRegistry.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetFeatureRegistry.php Tue Feb  2 00:13:49 2010
@@ -33,6 +33,14 @@
     $this->registerFeatures($featurePath);
   }
 
+  public function getFeaturesContent($features, GadgetContext $context, $isGadgetContext) {
+    $ret = '';
+    foreach ($features as $feature) {
+      $ret .= $this->getFeatureContent($feature, $context, $isGadgetContext);
+    }
+    return $ret;
+  }
+  
   public function getFeatureContent($feature, GadgetContext $context, $isGadgetContext) {
     if (empty($feature)) return '';
     if (!isset($this->features[$feature])) {
@@ -78,7 +86,7 @@
     return $ret;
   }
 
-  public function resolveFeatures($needed, &$resultsFound, &$resultsMissing) {
+  public function resolveFeatures($needed, &$resultsFound, &$resultsMissing, $isExpand) {
     $resultsFound = array();
     $resultsMissing = array();
     if (! count($needed)) {
@@ -91,18 +99,77 @@
       if ($feature == null) {
         $resultsMissing[] = $featureName;
       } else {
-        $this->addFeatureToResults($resultsFound, $feature);
+        $this->addFeatureToResults($resultsFound, $feature, $isExpand);
       }
     }
     return count($resultsMissing) == 0;
   }
+  
+  public function sortFeatures($features, $forcedJsLibs, &$sortedFeatureGroups) {
+    $sortedFeatureGroups = array();
+    if (empty($features)) return;
 
-  private function addFeatureToResults(&$results, $feature) {
+    // Topological sorting with constrain
+    // Build the topology matrix
+    $sortedFeatures = array();
+    $reverseDeps = array();
+    foreach ($features as $feature) {
+      $reverseDeps[$feature] = array();
+    }
+    $depCount = array();
+    foreach ($features as $feature) {
+      $deps = $this->features[$feature]['deps'];
+      $deps = array_uintersect($deps, $features, "strcasecmp");
+      $depCount[$feature] = count($deps);
+      foreach ($deps as $dep) {
+        $reverseDeps[$dep][] = $feature;
+      }
+    }
+    
+    // iterate to do the sorting
+    $prevIsForcedJsLibs = true;
+    $featureQueue = array();
+    while (! empty($depCount)) {
+      $fail = true;
+      while (! empty($depCount)) {  // get grouped features greedily
+        $foundAll = true;
+        foreach ($depCount as $feature => $count) {
+          if ($count != 0) continue;
+          $fail = false; // found at least one root node
+          if ((! $prevIsForcedJsLibs) ^ in_array($feature, $forcedJsLibs)) {
+            $foundAll = false;
+            $featureQueue[] = $feature;
+            foreach ($reverseDeps[$feature] as $reverseDep) {
+              $depCount[$reverseDep] -= 1;
+            }
+            unset($depCount[$feature]);
+          }
+        }
+        if ($foundAll) break;
+      }
+      if ($fail) {
+        throw new GadgetException("Sorting feature dependence failed: it contains ring!");
+      }
+      if (! empty($featureQueue)) {
+        if ($prevIsForcedJsLibs) {
+          $sortedFeatureGroups[] = array('type' => 'external', 'features' => $featureQueue);
+        } else {
+          $sortedFeatureGroups[] = array('type' => 'inline', 'features' => $featureQueue);
+        }
+      }
+      $prevIsForcedJsLibs = ! $prevIsForcedJsLibs;
+      $featureQueue = array();
+    }
+  }
+  
+  private function addFeatureToResults(&$results, $feature, $isExpand) {
     if (in_array($feature['name'], $results)) {
       return;
     }
-    foreach ($feature['deps'] as $dep) {
-      $this->addFeatureToResults($results, $this->features[$dep]);
+    if ($isExpand) {
+      foreach ($feature['deps'] as $dep) {
+        $this->addFeatureToResults($results, $this->features[$dep], true);
+      }
     }
     if (!in_array($feature['name'], $results)) {
       $results[] = $feature['name'];

Modified: incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php?rev=905469&r1=905468&r2=905469&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php Tue Feb  2 00:13:49 2010
@@ -215,7 +215,8 @@
    */
   private function parseFeatures(DOMElement &$modulePrefs, GadgetSpec &$gadget) {
     $gadget->requiredFeatures = $gadget->optionalFeatures = array();
-    if (($requiredNodes = $modulePrefs->getElementsByTagName('Require')) != null) {
+    $requiredNodes = $modulePrefs->getElementsByTagName('Require');
+    if ($requiredNodes->length != 0) {
       foreach ($requiredNodes as $requiredFeature) {
         $gadget->requiredFeatures[] = $requiredFeature->getAttribute('feature');
         if ($requiredFeature->getAttribute('feature') == 'content-rewrite') {
@@ -225,7 +226,8 @@
         }
       }
     }
-    if (($optionalNodes = $modulePrefs->getElementsByTagName('Optional')) != null) {
+    $optionalNodes = $modulePrefs->getElementsByTagName('Optional');
+    if ($optionalNodes->length != 0) {
       foreach ($optionalNodes as $optionalFeature) {
         $gadget->optionalFeatures[] = $optionalFeature->getAttribute('feature');
         // Content-rewrite is a special case since it has Params as child nodes

Modified: incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php?rev=905469&r1=905468&r2=905469&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/render/GadgetBaseRenderer.php Tue Feb  2 00:13:49 2010
@@ -298,26 +298,27 @@
   }
 
   public function getJavaScripts() {
+    $registry = $this->context->getRegistry();
     $forcedJsLibs = $this->getForcedJsLibs();
-    $externalScript = false;
-    if (! empty($forcedJsLibs)) {
-      // if some of the feature libraries are externalized (through a browser cachable <script src="/gadgets/js/opensocial-0.9:settitle.js"> type url)
-      // we inject the tag and don't inline those libs (and their dependencies)
-      $forcedJsLibs = explode(':', $forcedJsLibs);
-      $externalScript = Config::get('default_js_prefix') . $this->getJsUrl($forcedJsLibs, $this->gadget) . "&container=" . $this->context->getContainer();
-      $registry = $this->context->getRegistry();
-      $missing = array();
-      $registry->resolveFeatures($forcedJsLibs, $forcedJsLibs, $missing);
-    }
-    $script = '';
-    foreach ($this->gadget->features as $feature) {
-      if (! is_array($forcedJsLibs) || (is_array($forcedJsLibs) && ! in_array($feature, $forcedJsLibs))) {
-        $script .= $this->context->getRegistry()->getFeatureContent($feature, $this->context, true);
+    $sortedFeatureGroups = array();
+    $registry->sortFeatures($this->gadget->features, $forcedJsLibs, $sortedFeatureGroups);
+    $scripts = array();
+    
+    // if some of the feature libraries are externalized (through a browser cachable <script src="/gadgets/js/opensocial-0.9:settitle.js"> type url)
+    // we inject the tag and don't inline those libs (and their dependencies)
+    foreach ($sortedFeatureGroups as $featureGroup) {
+      if ($featureGroup['type'] == 'inline') {
+        $featureGroup['content'] = $registry->getFeaturesContent($featureGroup['features'], $this->context, true);
+      } else {
+        $featureGroup['content'] = Config::get('default_js_prefix') . $this->getJsUrl($featureGroup['features']) . "&container=" . $this->context->getContainer();
       }
+      $scripts[] = $featureGroup;
     }
+    
+    $script = '';
     // Add the JavaScript initialization strings for the configuration, localization and preloads
     $script .= "\n";
-    $script .= $this->appendJsConfig($this->gadget, count($forcedJsLibs));
+    $script .= $this->appendJsConfig($this->gadget, $sortedFeatureGroups);
     $script .= $this->appendMessages($this->gadget);
     $script .= $this->appendPreloads($this->gadget);
     if (count($this->dataInserts)) {
@@ -330,7 +331,11 @@
     if ($this->gadget->gadgetSpec->templatesDisableAutoProcessing) {
       $script .= "opensocial.template.Container.disableAutoProcessing();\n";
     }
-    return array('inline' => $script, 'external' => $externalScript);
+    $scripts[] = array(
+        'type' => 'inline',
+        'content' => $script
+    );
+    return $scripts;
   }
 
   /**
@@ -348,62 +353,73 @@
 
     // Inject the OpenSocial feature javascripts
     $scripts = $this->getJavaScripts();
-    if ($scripts['external']) {
+    foreach ($scripts as $script) {
       $scriptNode = $doc->createElement('script');
-      $scriptNode->setAttribute('src', $scripts['external']);
+      if ($script['type'] == 'inline') {
+        $scriptNode->setAttribute('type', 'text/javascript');
+        $scriptNode->appendChild($doc->createTextNode($script['content']));
+      } else {
+        $scriptNode->setAttribute('src', $script['content']);
+      }
       $node->appendChild($scriptNode);
     }
-    $scriptNode = $doc->createElement('script');
-    $scriptNode->setAttribute('type', 'text/javascript');
-    $scriptNode->appendChild($doc->createTextNode($scripts['inline']));
-    $node->appendChild($scriptNode);
   }
 
   /**
    * Retrieve the forced javascript libraries (if any), using either the &libs= from the query
    * or if that's empty, from the config
    *
-   * @return unknown
+   * @return array contains the names of forced external javascript libs. 
    */
   private function getForcedJsLibs() {
     $forcedJsLibs = $this->context->getForcedJsLibs();
     // allow the &libs=.. param to override our forced js libs configuration value
     if (empty($forcedJsLibs)) {
-      $forcedJsLibs = Config::get('focedJsLibs');
+      $forcedJsLibs = Config::get('forcedJsLibs');
+    }
+    if (empty($forcedJsLibs)) {
+      return array();
+    } else {
+      return explode(':', $forcedJsLibs);
     }
-    return $forcedJsLibs;
   }
 
   /**
    * Appends the javascript features configuration string
    *
    * @param Gadget $gadget
-   * @param unknown_type $hasForcedLibs
+   * @param $featureGroups
    * @return string
    */
-  private function appendJsConfig(Gadget $gadget, $hasForcedLibs) {
+  private function appendJsConfig(Gadget $gadget, $featureGroups) {
     $container = $this->context->getContainer();
     $containerConfig = $this->context->getContainerConfig();
-    //TODO some day we should parse the forcedLibs too, and include their config selectivly as well for now we just include everything if forced libs is set.
-    if ($hasForcedLibs) {
-      $gadgetConfig = $containerConfig->getConfig($container, 'gadgets.features');
-    } else {
-      $gadgetConfig = array();
-      $featureConfig = $containerConfig->getConfig($container, 'gadgets.features');
-      foreach ($gadget->getJsLibraries() as $library) {
-        $feature = $library->getFeatureName();
-        if (! isset($gadgetConfig[$feature]) && ! empty($featureConfig[$feature])) {
-          $gadgetConfig[$feature] = $featureConfig[$feature];
-        }
+    $forcedJsLibs = $this->getForcedJsLibs();
+    $gadgetConfig = array();
+    $featureConfig = $containerConfig->getConfig($container, 'gadgets.features');
+    
+    // TODO some day we should parse the forcedLibs too, and include their config selectivly as well.
+    // For now we just include everything.
+    $features = array();
+    foreach ($featureGroups as $featureGroup) {
+      $features = array_merge($features, $featureGroup['features']);
+    }
+    
+    foreach ($features as $feature) {
+      if (! isset($gadgetConfig[$feature]) && ! empty($featureConfig[$feature])) {
+        $gadgetConfig[$feature] = $featureConfig[$feature];
       }
     }
+    
     // Add gadgets.util support. This is calculated dynamically based on request inputs.
     // See java/org/apache/shindig/gadgets/render/RenderingContentRewriter.java for reference.
     $requires = array();
-    foreach ($gadget->features as $feature) {
+    foreach ($features as $feature) {
       $requires[$feature] = new EmptyClass();
     }
     $gadgetConfig['core.util'] = $requires;
+    
+    // following are some quick-fixes for osml and osapi.
     if (isset($gadgetConfig['osml'])) {
       unset($gadgetConfig['osml']);
     }

Modified: incubator/shindig/trunk/php/src/gadgets/render/GadgetHtmlRenderer.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/render/GadgetHtmlRenderer.php?rev=905469&r1=905468&r2=905469&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/render/GadgetHtmlRenderer.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/render/GadgetHtmlRenderer.php Tue Feb  2 00:13:49 2010
@@ -50,13 +50,16 @@
       // Manually generate the html document using basic string concatinations instead of using our DOM based functions
       $content .= "<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n";
       $content .= '<style>'.Config::get('gadget_css')."</style>\n";
+
       $scripts = $this->getJavaScripts();
-      if ($scripts['external']) {
-        $content .= "<script type=\"text/javascript\" src=\"{$scripts['external']}\"></script>\n";
-      }
-      if (!empty($scripts['inline'])) {
-        $content .= "<script type=\"text/javascript\">{$scripts['inline']}</script>\n";
+      foreach ($scripts as $script) {
+        if ($script['type'] == 'inline') {
+          $content .= "<script type=\"text/javascript\">{$script['content']}</script>\n";
+        } else {
+          $content .= "<script type=\"text/javascript\" src=\"{$script['content']}\"></script>\n";
+        }
       }
+
       $content .= "</head>\n<body>\n";
       $content .= $gadget->substitutions->substitute($view['content']);
       $content .= '<script type="text/javascript">'.$this->getBodyScript()."</script>\n";

Modified: incubator/shindig/trunk/php/src/gadgets/render/GadgetRenderer.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/render/GadgetRenderer.php?rev=905469&r1=905468&r2=905469&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/render/GadgetRenderer.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/render/GadgetRenderer.php Tue Feb  2 00:13:49 2010
@@ -38,12 +38,10 @@
    * @return string the list of libraries in core:caja:etc.js?v=checksum> format
    */
   protected function getJsUrl($features) {
-    $ret = '';
     if (! is_array($features) || ! count($features)) {
-      $ret = 'core';
-    } else {
-      $ret = implode(':', $features);
+      return 'null';
     }
+    $ret = implode(':', $features);
     $cache = Cache::createCache(Config::get('feature_cache'), 'FeatureCache');
     if (($md5 = $cache->get(md5('getJsUrlMD5'))) === false) {
       $registry = $this->context->getRegistry();

Modified: incubator/shindig/trunk/php/src/gadgets/render/GadgetUrlRenderer.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/render/GadgetUrlRenderer.php?rev=905469&r1=905468&r2=905469&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/render/GadgetUrlRenderer.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/render/GadgetUrlRenderer.php Tue Feb  2 00:13:49 2010
@@ -22,7 +22,7 @@
 
   /**
    * Renders an 'URL' type view (where the iframe is redirected to the specified url)
-   * This is more a legacy iGoogle support feature then something that should be actually
+   * This is more a legacy iGoogle support feature than something that should be actually
    * used. Proxied content is the socially aware (and higher performance) version of this
    * See GadgetHrefRenderer for it's implementation.
    *
@@ -35,13 +35,21 @@
     $queryStr = strpos($redirURI, '?') !== false ? substr($redirURI, strpos($redirURI, '?')) : '';
     $query = $queryStr;
     $query .= $this->getPrefsQueryString($gadget->gadgetSpec->userPrefs);
+    
+    // deal with features
+    $registry = $this->context->getRegistry();
+    // since the URL mode doesn't actually have the gadget XML body, it can't inline
+    // the javascript content anyway - thus could us just ignore the 'forcedJsLibs' part.
+    $forcedJsLibs = array();
+    $sortedFeatureGroups = array();
+    $registry->sortFeatures($gadget->features, $forcedJsLibs, $sortedFeatureGroups);
+    
+    // join the groups
     $features = array();
-    $forcedLibs = Config::get('focedJsLibs');
-    if ($forcedLibs == null) {
-      $features = $gadget->features;
-    } else {
-      $features = explode(':', $forcedLibs);
+    foreach ($sortedFeatureGroups as $featureGroup) {
+      $features = array_merge($features, $featureGroup['features']);
     }
+    
     $query .= $this->appendLibsToQuery($features);
     $query .= '&lang=' . urlencode(isset($_GET['lang']) ? $_GET['lang'] : 'en');
     $query .= '&country=' . urlencode(isset($_GET['country']) ? $_GET['country'] : 'US');

Modified: incubator/shindig/trunk/php/src/gadgets/servlet/JsServlet.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/servlet/JsServlet.php?rev=905469&r1=905468&r2=905469&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/servlet/JsServlet.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/servlet/JsServlet.php Tue Feb  2 00:13:49 2010
@@ -25,7 +25,7 @@
 /**
  * This event handler deals with the /js/core:caja:etc.js request which content type=url gadgets can use
  * to retrieve our features javascript code, or used to make the most frequently used part of the feature
- * library external, and hence cachable by the browser
+ * library external, and hence cachable by the browser.
  */
 class JsServlet extends HttpServlet {
 
@@ -56,7 +56,7 @@
     $missing = array();
     $context = new GadgetContext('GADGET');
     $registry = new GadgetFeatureRegistry(Config::get('features_path'));
-    if ($registry->resolveFeatures($needed, $found, $missing)) {
+    if ($registry->resolveFeatures($needed, $found, $missing, false)) {
       $isGadgetContext = !isset($_GET["c"]) || $_GET['c'] == 0 ? true : false;
       $jsData = '';
       foreach ($found as $feature) {