FEATURE: upgrade from therubyracer to mini_racer

This pushes our internal V8 JavaScript engine from Chrome 32 to 50.

It also resolves some long standing issues we had with the old wrapper.
This commit is contained in:
Sam 2016-05-19 22:25:08 +10:00 committed by Sam Saffron
parent f387dfe226
commit 695773db1c
7 changed files with 70 additions and 68 deletions

View file

@ -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

View file

@ -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

View file

@ -59,7 +59,7 @@ SCRIPT
begin
code = transpile(node.inner_html, node['version'])
node.replace("<script>#{code}</script>")
rescue Tilt::ES6ModuleTranspilerTemplate::JavaScriptError => ex
rescue MiniRacer::RuntimeError => ex
node.replace("<script type='text/discourse-js-error'>#{ex.message}</script>")
end
end

View file

@ -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 <<JS
console = {
prefix: "",
log: function(msg){ rails.logger.info(console.prefix + msg); },
error: function(msg){ rails.logger.error(console.prefix + msg); }
}
JS
ctx
end
@ -60,19 +56,9 @@ module Tilt
end
def self.protect
rval = nil
@mutex.synchronize do
begin
rval = yield
# This may seem a bit odd, but we don't want to leak out
# objects that require locks on the v8 vm, to get a backtrace
# you need a lock, if this happens in the wrong spot you can
# deadlock a process
rescue V8::Error => 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

View file

@ -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

View file

@ -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 <<JS
avatarTemplate = #{avatar_template.inspect};
size = #{size.to_i};
JS
decorate_context(v8)
v8.eval("Discourse.Utilities.avatarImg({ avatarTemplate: avatarTemplate, size: size });")
end
@ -267,9 +276,8 @@ module PrettyText
def self.unescape_emoji(title)
protect do
v8["title"] = title
decorate_context(v8)
v8.eval("Discourse.Emoji.unescape(title)")
v8.eval("Discourse.Emoji.unescape(#{title.inspect})")
end
end
@ -413,15 +421,7 @@ module PrettyText
def self.protect
rval = nil
@mutex.synchronize do
begin
rval = yield
# This may seem a bit odd, but we don't want to leak out
# objects that require locks on the v8 vm, to get a backtrace
# you need a lock, if this happens in the wrong spot you can
# deadlock a process
rescue V8::Error => e
raise JavaScriptError.new(e.message, e.backtrace)
end
rval = yield
end
rval
end

View file

@ -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)