diff --git a/Gemfile b/Gemfile index 178f64053..d96c77701 100644 --- a/Gemfile +++ b/Gemfile @@ -106,7 +106,8 @@ gem 'sidekiq-statistic' # for sidekiq web gem 'sinatra', require: false -gem 'therubyracer' +gem 'execjs', github: 'rails/execjs', require: false +gem 'mini_racer' gem 'thin', require: false gem 'highline', require: false gem 'rack-protection' # security diff --git a/Gemfile.lock b/Gemfile.lock index 5ed367706..3491b735a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,9 @@ +GIT + remote: git://github.com/rails/execjs.git + revision: 22476229323cbec3befa8b690cc6c7c3957a0044 + specs: + execjs (2.6.0) + GEM remote: https://rubygems.org/ specs: @@ -94,7 +100,6 @@ GEM erubis (2.7.0) eventmachine (1.2.0.1) excon (0.45.4) - execjs (2.6.0) exifr (1.2.4) fabrication (2.9.8) fakeweb (1.3.0) @@ -144,7 +149,7 @@ GEM librarian (0.1.2) highline thor (~> 0.15) - libv8 (3.16.14.13) + libv8 (5.0.71.48.3) listen (0.7.3) logster (1.2.3) loofah (2.0.3) @@ -159,6 +164,8 @@ GEM method_source (0.8.2) mime-types (2.99.1) mini_portile2 (2.1.0) + mini_racer (0.1.3) + libv8 (~> 5.0) minitest (5.8.4) mocha (1.1.0) metaclass (~> 0.0.1) @@ -284,7 +291,6 @@ GEM redis (3.3.0) redis-namespace (1.5.2) redis (~> 3.0, >= 3.0.4) - ref (2.0.0) rest-client (1.8.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 3.0) @@ -373,9 +379,6 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) stackprof (0.2.9) - therubyracer (0.12.2) - libv8 (~> 3.16.14.0) - ref thin (1.6.4) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) @@ -417,6 +420,7 @@ DEPENDENCIES ember-rails (= 0.18.5) ember-source (= 1.12.2) excon + execjs! fabrication (= 2.9.8) fakeweb (~> 1.3.0) fast_blank @@ -439,6 +443,7 @@ DEPENDENCIES memory_profiler message_bus (= 2.0.0.beta.11) mime-types + mini_racer minitest mocha mock_redis @@ -493,7 +498,6 @@ DEPENDENCIES sinatra spork-rails stackprof - therubyracer thin timecop uglifier diff --git a/app/models/site_customization.rb b/app/models/site_customization.rb index a74e188a2..562c544f4 100644 --- a/app/models/site_customization.rb +++ b/app/models/site_customization.rb @@ -59,7 +59,7 @@ SCRIPT begin code = transpile(node.inner_html, node['version']) node.replace("") - rescue Tilt::ES6ModuleTranspilerTemplate::JavaScriptError => ex + rescue MiniRacer::RuntimeError => ex node.replace("") end end diff --git a/lib/es6_module_transpiler/tilt/es6_module_transpiler_template.rb b/lib/es6_module_transpiler/tilt/es6_module_transpiler_template.rb index 58eb0e678..68b2ca990 100644 --- a/lib/es6_module_transpiler/tilt/es6_module_transpiler_template.rb +++ b/lib/es6_module_transpiler/tilt/es6_module_transpiler_template.rb @@ -1,22 +1,9 @@ require 'execjs' require 'babel/transpiler' +require 'mini_racer' module Tilt - class Console - def initialize(prefix=nil) - @prefix = prefix || '' - end - - def log(msg) - Rails.logger.info("#{@prefix}#{msg}") - end - - def error(msg) - Rails.logger.error("#{@prefix}#{msg}") - end - end - class ES6ModuleTranspilerTemplate < Tilt::Template self.default_mime_type = 'application/javascript' @@ -30,10 +17,19 @@ module Tilt def self.create_new_context # timeout any eval that takes longer than 15 seconds - ctx = V8::Context.new(timeout: 15000) + ctx = MiniRacer::Context.new(timeout: 15000) ctx.eval("var self = this; #{File.read(Babel::Transpiler.script_path)}") ctx.eval("module = {}; exports = {};"); ctx.load("#{Rails.root}/lib/es6_module_transpiler/support/es6-module-transpiler.js") + ctx.attach("rails.logger.info", proc{|err| Rails.logger.info(err.to_s)}) + ctx.attach("rails.logger.error", proc{|err| Rails.logger.error(err.to_s)}) + ctx.eval < e - raise JavaScriptError.new(e.message, e.backtrace) - end + yield end - rval end def whitelisted?(path) @@ -98,7 +84,7 @@ module Tilt def babel_transpile(source) klass = self.class klass.protect do - klass.v8['console'] = Console.new("BABEL: babel-eval: ") + klass.v8.eval("console.prefix = 'BABEL: babel-eval: ';") @output = klass.v8.eval(babel_source(source)) end end @@ -108,7 +94,7 @@ module Tilt klass = self.class klass.protect do - klass.v8['console'] = Console.new("BABEL: #{scope.logical_path}: ") + klass.v8.eval("console.prefix = 'BABEL: #{scope.logical_path}: ';") @output = klass.v8.eval(generate_source(scope)) end diff --git a/lib/js_locale_helper.rb b/lib/js_locale_helper.rb index db4821a37..eb2f1a19d 100644 --- a/lib/js_locale_helper.rb +++ b/lib/js_locale_helper.rb @@ -154,15 +154,25 @@ module JsLocaleHelper result end - def self.compile_message_format(locale, format) - ctx = V8::Context.new - ctx.load(Rails.root + 'lib/javascripts/messageformat.js') - path = Rails.root + "lib/javascripts/locale/#{locale}.js" - ctx.load(path) if File.exists?(path) - ctx.eval("mf = new MessageFormat('#{locale}');") - ctx.eval("mf.precompile(mf.parse(#{format.inspect}))") + @mutex = Mutex.new + def self.with_context + @mutex.synchronize do + yield @ctx ||= begin + ctx = MiniRacer::Context.new + ctx.load(Rails.root + 'lib/javascripts/messageformat.js') + ctx + end + end + end - rescue V8::Error => e + def self.compile_message_format(locale, format) + with_context do |ctx| + path = Rails.root + "lib/javascripts/locale/#{locale}.js" + ctx.load(path) if File.exists?(path) + ctx.eval("mf = new MessageFormat('#{locale}');") + ctx.eval("mf.precompile(mf.parse(#{format.inspect}))") + end + rescue MiniRacer::EvalError => e message = "Invalid Format: " << e.message "function(){ return #{message.inspect};}" end diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index e3e5cc756..b328f230f 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -1,4 +1,4 @@ -require 'v8' +require 'mini_racer' require 'nokogiri' require_dependency 'url_helper' require_dependency 'excerpt_parser' @@ -7,7 +7,9 @@ require_dependency 'discourse_tagging' module PrettyText - class Helpers + module Helpers + extend self + def t(key, opts) key = "js." + key unless opts @@ -81,6 +83,7 @@ module PrettyText nil end end + end @mutex = Mutex.new @@ -92,9 +95,11 @@ module PrettyText def self.create_new_context # timeout any eval that takes longer than 15 seconds - ctx = V8::Context.new(timeout: 15000) + ctx = MiniRacer::Context.new(timeout: 15000) - ctx["helpers"] = Helpers.new + Helpers.instance_methods.each do |method| + ctx.attach("helpers.#{method}", Helpers.method(method)) + end ctx_load(ctx, "vendor/assets/javascripts/md5.js", @@ -198,6 +203,7 @@ module PrettyText # we use the exact same markdown converter as the client # TODO: use the same extensions on both client and server (in particular the template for mentions) baked = nil + text = text || "" protect do context = v8 @@ -206,8 +212,9 @@ module PrettyText context_opts = opts || {} context_opts[:sanitize] = true unless context_opts[:sanitize] == false - context['opts'] = context_opts - context['raw'] = text + + context.eval("opts = #{context_opts.to_json};") + context.eval("raw = #{text.inspect};") if Post.white_listed_image_classes.present? Post.white_listed_image_classes.each do |klass| @@ -258,8 +265,10 @@ module PrettyText # leaving this here, cause it invokes v8, don't want to implement twice def self.avatar_img(avatar_template, size) protect do - v8['avatarTemplate'] = avatar_template - v8['size'] = size + v8.eval < e - raise JavaScriptError.new(e.message, e.backtrace) - end + rval = yield end rval end diff --git a/spec/components/js_locale_helper_spec.rb b/spec/components/js_locale_helper_spec.rb index 1b26efe88..125474f93 100644 --- a/spec/components/js_locale_helper_spec.rb +++ b/spec/components/js_locale_helper_spec.rb @@ -1,5 +1,6 @@ require 'rails_helper' require_dependency 'js_locale_helper' +require 'mini_racer' describe JsLocaleHelper do @@ -25,7 +26,7 @@ describe JsLocaleHelper do end def setup_message_format(format) - @ctx = V8::Context.new + @ctx = MiniRacer::Context.new @ctx.eval('MessageFormat = {locale: {}};') @ctx.load(Rails.root + 'lib/javascripts/locale/en.js') compiled = JsLocaleHelper.compile_message_format('en', format) @@ -72,7 +73,7 @@ describe JsLocaleHelper do end it 'handles message format special keys' do - ctx = V8::Context.new + ctx = MiniRacer::Context.new ctx.eval("I18n = {};") JsLocaleHelper.set_translations 'en', { @@ -149,7 +150,7 @@ describe JsLocaleHelper do SiteSetting.default_locale = 'ru' I18n.locale = :uk - ctx = V8::Context.new + ctx = MiniRacer::Context.new ctx.eval('var window = this;') ctx.load(Rails.root + 'app/assets/javascripts/locales/i18n.js') ctx.eval(JsLocaleHelper.output_locale(I18n.locale)) @@ -167,7 +168,7 @@ describe JsLocaleHelper do LocaleSiteSetting.values.each do |locale| it "generates valid date helpers for #{locale[:value]} locale" do js = JsLocaleHelper.output_locale(locale[:value]) - ctx = V8::Context.new + ctx = MiniRacer::Context.new ctx.eval('var window = this;') ctx.load(Rails.root + 'app/assets/javascripts/locales/i18n.js') ctx.eval(js)