Search code examples
backbone.jsgoogle-chrome-extensionrequirejschaplinjs

Chrome extension using RequireJS, Backbone (Chaplin) conflicts


I am creating a Google Chrome Extension that have to add content on the visited websites (like a toolbox).

I have to use RequireJS and BackboneJS (Chaplin) and everything is ok except when i'm visiting a website using RequireJS (and Backbone, but the problem seems to come from RequireJS conflicts). (This is when I use content scripts to include a -script- tag that includes RequireJS.)

I suppose it's normal to have conflicts if I add content directly in the page so I tried the solution here : Loading multiple instances of requireJS and Backbone

It seems to work (for now), but the website is trying to reload his own RequireJS file (with his path, but in my extension) before loading mine and I'm afraid it could lead to unexpected behaviour. Plus, I have to precise my file paths in requirejs.config or it's looking for them in Bitbucket sources (cloudfront). (Maybe it's normal though)

Example with bitbucket :

Denying load of chrome-extension://mgncmiffelpdhlbkkmmaedbodabdchea/https://d3oaxc4q5k2d6q.cloudfront.net/m/7aaf1677069c/amd/build/main.js?8uxr. Resources must be listed in the web_accessible_resources manifest key in order to be loaded by pages outside the extension.

<--------- This file is Bitbucket's RequireJS, Bitbucket is still working fine though

Is there another solution I didn't find yet ? Or am I doing it wrong ? I'm a beginner with RequireJS (and Chrome ext.. and Backbone...) so I might have missed something.

Here is the Content script part in manifest.json

"content_scripts": [
{
  "matches": ["https://*/*", "http://*/*"],
  "js": ["bower_components/requirejs/require.js",
  "extension/init-app.js",
  "extension/main.js"]
}],

init-app.js is Rob's script

require.load = function(context, moduleName, url) {


  url = chrome.extension.getURL(url);
  var x = new XMLHttpRequest();
  // Append Math.random()... to bust the cache
  x.open('GET', url + '?' + Math.random().toString(36).slice(-4));
  x.onload = function() {
      var code = x.responseText;
      x += '\n//@ sourceURL=' + url; // Optional, for debugging.
      window.eval(code);
      context.completeLoad(moduleName);
  };
  x.onerror = function() {
      // Log error if you wish. This is usually not needed, because
      // Chrome's developer tools does already log "404 Not found"
      // errors for scripts to the console.
  };
  x.send();
};

and main.js contain requirejs.config + app

// Configure the AMD module loader
requirejs.config({
  skipDataMain: true,
  // The path where your JavaScripts are located

  baseUrl: 'extension',
  // Specify the paths of vendor libraries

  paths: {
    jquery: '../bower_components/jquery/jquery',
    underscore: '../bower_components/lodash/dist/lodash',
    backbone: '../bower_components/backbone/backbone',
    handlebars: '../bower_components/handlebars/handlebars',
    text: '../bower_components/requirejs-text/text',
    chaplin: '../bower_components/chaplin/chaplin',
    application: '/extension/application',
    routes: '/extension/routes',
  },
  // Underscore and Backbone are not AMD-capable per default,
  // so we need to use the AMD wrapping of RequireJS
  shim: {
    underscore: {
      exports: '_'
    },
    backbone: {
      deps: ['underscore', 'jquery'],
      exports: 'Backbone'
    },
    handlebars: {
      exports: 'Handlebars'
    }
  }
  // For easier development, disable browser caching
  // Of course, this should be removed in a production environment
  //, urlArgs: 'bust=' +  (new Date()).getTime()
});


// Bootstrap the application
require(['application', 'routes'], function(Application, routes) {

  new Application({routes: routes, controllerPath: 'scripts/controllers/', controllerSuffix: '-controller'});
});

It works on gooogle.com for instance, but I get

GET chrome-extension://ccgfmmmnebacpnbdpdnphmnmicaooddg/extension/Home.js?9zfr net::ERR_FILE_NOT_FOUND

on https://www.cloud9trader.com (website using RequireJS) because it has

<script data-main="/0.2.59/scripts/Home.js" src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.14/require.min.js"></script>

in its source. To summarize I just need the script to ignore the "current" website Require file.


Solution

  • The skipDataMain option is synchronously checked when require.js is loaded. Setting this variable after loading require.js has no effect on the loader any more, because the data-main scan has already run at that point.

    The correct way to skip data-main is to declare the configuration before loading require.js, as follows:

    // extension/config.js
    var require = {
        skipDataMain: true
    };
    

    manifest.json:

    {
        ...
        "content_scripts": [{
            "matches": ["https://*/*", "http://*/*"],
            "js": [
                "extension/config.js",
                "bower_components/requirejs/require.js",
                "extension/init-app.js",
                "extension/main.js"
            ]
        }],
        ...
    }