mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-30 10:58:31 -05:00
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:
parent
f387dfe226
commit
695773db1c
7 changed files with 70 additions and 68 deletions
3
Gemfile
3
Gemfile
|
@ -106,7 +106,8 @@ gem 'sidekiq-statistic'
|
||||||
# for sidekiq web
|
# for sidekiq web
|
||||||
gem 'sinatra', require: false
|
gem 'sinatra', require: false
|
||||||
|
|
||||||
gem 'therubyracer'
|
gem 'execjs', github: 'rails/execjs', require: false
|
||||||
|
gem 'mini_racer'
|
||||||
gem 'thin', require: false
|
gem 'thin', require: false
|
||||||
gem 'highline', require: false
|
gem 'highline', require: false
|
||||||
gem 'rack-protection' # security
|
gem 'rack-protection' # security
|
||||||
|
|
18
Gemfile.lock
18
Gemfile.lock
|
@ -1,3 +1,9 @@
|
||||||
|
GIT
|
||||||
|
remote: git://github.com/rails/execjs.git
|
||||||
|
revision: 22476229323cbec3befa8b690cc6c7c3957a0044
|
||||||
|
specs:
|
||||||
|
execjs (2.6.0)
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
|
@ -94,7 +100,6 @@ GEM
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
eventmachine (1.2.0.1)
|
eventmachine (1.2.0.1)
|
||||||
excon (0.45.4)
|
excon (0.45.4)
|
||||||
execjs (2.6.0)
|
|
||||||
exifr (1.2.4)
|
exifr (1.2.4)
|
||||||
fabrication (2.9.8)
|
fabrication (2.9.8)
|
||||||
fakeweb (1.3.0)
|
fakeweb (1.3.0)
|
||||||
|
@ -144,7 +149,7 @@ GEM
|
||||||
librarian (0.1.2)
|
librarian (0.1.2)
|
||||||
highline
|
highline
|
||||||
thor (~> 0.15)
|
thor (~> 0.15)
|
||||||
libv8 (3.16.14.13)
|
libv8 (5.0.71.48.3)
|
||||||
listen (0.7.3)
|
listen (0.7.3)
|
||||||
logster (1.2.3)
|
logster (1.2.3)
|
||||||
loofah (2.0.3)
|
loofah (2.0.3)
|
||||||
|
@ -159,6 +164,8 @@ GEM
|
||||||
method_source (0.8.2)
|
method_source (0.8.2)
|
||||||
mime-types (2.99.1)
|
mime-types (2.99.1)
|
||||||
mini_portile2 (2.1.0)
|
mini_portile2 (2.1.0)
|
||||||
|
mini_racer (0.1.3)
|
||||||
|
libv8 (~> 5.0)
|
||||||
minitest (5.8.4)
|
minitest (5.8.4)
|
||||||
mocha (1.1.0)
|
mocha (1.1.0)
|
||||||
metaclass (~> 0.0.1)
|
metaclass (~> 0.0.1)
|
||||||
|
@ -284,7 +291,6 @@ GEM
|
||||||
redis (3.3.0)
|
redis (3.3.0)
|
||||||
redis-namespace (1.5.2)
|
redis-namespace (1.5.2)
|
||||||
redis (~> 3.0, >= 3.0.4)
|
redis (~> 3.0, >= 3.0.4)
|
||||||
ref (2.0.0)
|
|
||||||
rest-client (1.8.0)
|
rest-client (1.8.0)
|
||||||
http-cookie (>= 1.0.2, < 2.0)
|
http-cookie (>= 1.0.2, < 2.0)
|
||||||
mime-types (>= 1.16, < 3.0)
|
mime-types (>= 1.16, < 3.0)
|
||||||
|
@ -373,9 +379,6 @@ GEM
|
||||||
activesupport (>= 4.0)
|
activesupport (>= 4.0)
|
||||||
sprockets (>= 3.0.0)
|
sprockets (>= 3.0.0)
|
||||||
stackprof (0.2.9)
|
stackprof (0.2.9)
|
||||||
therubyracer (0.12.2)
|
|
||||||
libv8 (~> 3.16.14.0)
|
|
||||||
ref
|
|
||||||
thin (1.6.4)
|
thin (1.6.4)
|
||||||
daemons (~> 1.0, >= 1.0.9)
|
daemons (~> 1.0, >= 1.0.9)
|
||||||
eventmachine (~> 1.0, >= 1.0.4)
|
eventmachine (~> 1.0, >= 1.0.4)
|
||||||
|
@ -417,6 +420,7 @@ DEPENDENCIES
|
||||||
ember-rails (= 0.18.5)
|
ember-rails (= 0.18.5)
|
||||||
ember-source (= 1.12.2)
|
ember-source (= 1.12.2)
|
||||||
excon
|
excon
|
||||||
|
execjs!
|
||||||
fabrication (= 2.9.8)
|
fabrication (= 2.9.8)
|
||||||
fakeweb (~> 1.3.0)
|
fakeweb (~> 1.3.0)
|
||||||
fast_blank
|
fast_blank
|
||||||
|
@ -439,6 +443,7 @@ DEPENDENCIES
|
||||||
memory_profiler
|
memory_profiler
|
||||||
message_bus (= 2.0.0.beta.11)
|
message_bus (= 2.0.0.beta.11)
|
||||||
mime-types
|
mime-types
|
||||||
|
mini_racer
|
||||||
minitest
|
minitest
|
||||||
mocha
|
mocha
|
||||||
mock_redis
|
mock_redis
|
||||||
|
@ -493,7 +498,6 @@ DEPENDENCIES
|
||||||
sinatra
|
sinatra
|
||||||
spork-rails
|
spork-rails
|
||||||
stackprof
|
stackprof
|
||||||
therubyracer
|
|
||||||
thin
|
thin
|
||||||
timecop
|
timecop
|
||||||
uglifier
|
uglifier
|
||||||
|
|
|
@ -59,7 +59,7 @@ SCRIPT
|
||||||
begin
|
begin
|
||||||
code = transpile(node.inner_html, node['version'])
|
code = transpile(node.inner_html, node['version'])
|
||||||
node.replace("<script>#{code}</script>")
|
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>")
|
node.replace("<script type='text/discourse-js-error'>#{ex.message}</script>")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,22 +1,9 @@
|
||||||
require 'execjs'
|
require 'execjs'
|
||||||
require 'babel/transpiler'
|
require 'babel/transpiler'
|
||||||
|
require 'mini_racer'
|
||||||
|
|
||||||
module Tilt
|
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
|
class ES6ModuleTranspilerTemplate < Tilt::Template
|
||||||
self.default_mime_type = 'application/javascript'
|
self.default_mime_type = 'application/javascript'
|
||||||
|
|
||||||
|
@ -30,10 +17,19 @@ module Tilt
|
||||||
|
|
||||||
def self.create_new_context
|
def self.create_new_context
|
||||||
# timeout any eval that takes longer than 15 seconds
|
# 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("var self = this; #{File.read(Babel::Transpiler.script_path)}")
|
||||||
ctx.eval("module = {}; exports = {};");
|
ctx.eval("module = {}; exports = {};");
|
||||||
ctx.load("#{Rails.root}/lib/es6_module_transpiler/support/es6-module-transpiler.js")
|
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
|
ctx
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -60,20 +56,10 @@ module Tilt
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.protect
|
def self.protect
|
||||||
rval = nil
|
|
||||||
@mutex.synchronize do
|
@mutex.synchronize do
|
||||||
begin
|
yield
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
rval
|
|
||||||
end
|
|
||||||
|
|
||||||
def whitelisted?(path)
|
def whitelisted?(path)
|
||||||
|
|
||||||
|
@ -98,7 +84,7 @@ module Tilt
|
||||||
def babel_transpile(source)
|
def babel_transpile(source)
|
||||||
klass = self.class
|
klass = self.class
|
||||||
klass.protect do
|
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))
|
@output = klass.v8.eval(babel_source(source))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -108,7 +94,7 @@ module Tilt
|
||||||
|
|
||||||
klass = self.class
|
klass = self.class
|
||||||
klass.protect do
|
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))
|
@output = klass.v8.eval(generate_source(scope))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -154,15 +154,25 @@ module JsLocaleHelper
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.compile_message_format(locale, format)
|
@mutex = Mutex.new
|
||||||
ctx = V8::Context.new
|
def self.with_context
|
||||||
|
@mutex.synchronize do
|
||||||
|
yield @ctx ||= begin
|
||||||
|
ctx = MiniRacer::Context.new
|
||||||
ctx.load(Rails.root + 'lib/javascripts/messageformat.js')
|
ctx.load(Rails.root + 'lib/javascripts/messageformat.js')
|
||||||
|
ctx
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.compile_message_format(locale, format)
|
||||||
|
with_context do |ctx|
|
||||||
path = Rails.root + "lib/javascripts/locale/#{locale}.js"
|
path = Rails.root + "lib/javascripts/locale/#{locale}.js"
|
||||||
ctx.load(path) if File.exists?(path)
|
ctx.load(path) if File.exists?(path)
|
||||||
ctx.eval("mf = new MessageFormat('#{locale}');")
|
ctx.eval("mf = new MessageFormat('#{locale}');")
|
||||||
ctx.eval("mf.precompile(mf.parse(#{format.inspect}))")
|
ctx.eval("mf.precompile(mf.parse(#{format.inspect}))")
|
||||||
|
end
|
||||||
rescue V8::Error => e
|
rescue MiniRacer::EvalError => e
|
||||||
message = "Invalid Format: " << e.message
|
message = "Invalid Format: " << e.message
|
||||||
"function(){ return #{message.inspect};}"
|
"function(){ return #{message.inspect};}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
require 'v8'
|
require 'mini_racer'
|
||||||
require 'nokogiri'
|
require 'nokogiri'
|
||||||
require_dependency 'url_helper'
|
require_dependency 'url_helper'
|
||||||
require_dependency 'excerpt_parser'
|
require_dependency 'excerpt_parser'
|
||||||
|
@ -7,7 +7,9 @@ require_dependency 'discourse_tagging'
|
||||||
|
|
||||||
module PrettyText
|
module PrettyText
|
||||||
|
|
||||||
class Helpers
|
module Helpers
|
||||||
|
extend self
|
||||||
|
|
||||||
def t(key, opts)
|
def t(key, opts)
|
||||||
key = "js." + key
|
key = "js." + key
|
||||||
unless opts
|
unless opts
|
||||||
|
@ -81,6 +83,7 @@ module PrettyText
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@mutex = Mutex.new
|
@mutex = Mutex.new
|
||||||
|
@ -92,9 +95,11 @@ module PrettyText
|
||||||
|
|
||||||
def self.create_new_context
|
def self.create_new_context
|
||||||
# timeout any eval that takes longer than 15 seconds
|
# 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,
|
ctx_load(ctx,
|
||||||
"vendor/assets/javascripts/md5.js",
|
"vendor/assets/javascripts/md5.js",
|
||||||
|
@ -198,6 +203,7 @@ module PrettyText
|
||||||
# we use the exact same markdown converter as the client
|
# 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)
|
# TODO: use the same extensions on both client and server (in particular the template for mentions)
|
||||||
baked = nil
|
baked = nil
|
||||||
|
text = text || ""
|
||||||
|
|
||||||
protect do
|
protect do
|
||||||
context = v8
|
context = v8
|
||||||
|
@ -206,8 +212,9 @@ module PrettyText
|
||||||
|
|
||||||
context_opts = opts || {}
|
context_opts = opts || {}
|
||||||
context_opts[:sanitize] = true unless context_opts[:sanitize] == false
|
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?
|
if Post.white_listed_image_classes.present?
|
||||||
Post.white_listed_image_classes.each do |klass|
|
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
|
# leaving this here, cause it invokes v8, don't want to implement twice
|
||||||
def self.avatar_img(avatar_template, size)
|
def self.avatar_img(avatar_template, size)
|
||||||
protect do
|
protect do
|
||||||
v8['avatarTemplate'] = avatar_template
|
v8.eval <<JS
|
||||||
v8['size'] = size
|
avatarTemplate = #{avatar_template.inspect};
|
||||||
|
size = #{size.to_i};
|
||||||
|
JS
|
||||||
decorate_context(v8)
|
decorate_context(v8)
|
||||||
v8.eval("Discourse.Utilities.avatarImg({ avatarTemplate: avatarTemplate, size: size });")
|
v8.eval("Discourse.Utilities.avatarImg({ avatarTemplate: avatarTemplate, size: size });")
|
||||||
end
|
end
|
||||||
|
@ -267,9 +276,8 @@ module PrettyText
|
||||||
|
|
||||||
def self.unescape_emoji(title)
|
def self.unescape_emoji(title)
|
||||||
protect do
|
protect do
|
||||||
v8["title"] = title
|
|
||||||
decorate_context(v8)
|
decorate_context(v8)
|
||||||
v8.eval("Discourse.Emoji.unescape(title)")
|
v8.eval("Discourse.Emoji.unescape(#{title.inspect})")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -413,15 +421,7 @@ module PrettyText
|
||||||
def self.protect
|
def self.protect
|
||||||
rval = nil
|
rval = nil
|
||||||
@mutex.synchronize do
|
@mutex.synchronize do
|
||||||
begin
|
|
||||||
rval = yield
|
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
|
|
||||||
end
|
end
|
||||||
rval
|
rval
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
require_dependency 'js_locale_helper'
|
require_dependency 'js_locale_helper'
|
||||||
|
require 'mini_racer'
|
||||||
|
|
||||||
describe JsLocaleHelper do
|
describe JsLocaleHelper do
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ describe JsLocaleHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_message_format(format)
|
def setup_message_format(format)
|
||||||
@ctx = V8::Context.new
|
@ctx = MiniRacer::Context.new
|
||||||
@ctx.eval('MessageFormat = {locale: {}};')
|
@ctx.eval('MessageFormat = {locale: {}};')
|
||||||
@ctx.load(Rails.root + 'lib/javascripts/locale/en.js')
|
@ctx.load(Rails.root + 'lib/javascripts/locale/en.js')
|
||||||
compiled = JsLocaleHelper.compile_message_format('en', format)
|
compiled = JsLocaleHelper.compile_message_format('en', format)
|
||||||
|
@ -72,7 +73,7 @@ describe JsLocaleHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles message format special keys' do
|
it 'handles message format special keys' do
|
||||||
ctx = V8::Context.new
|
ctx = MiniRacer::Context.new
|
||||||
ctx.eval("I18n = {};")
|
ctx.eval("I18n = {};")
|
||||||
|
|
||||||
JsLocaleHelper.set_translations 'en', {
|
JsLocaleHelper.set_translations 'en', {
|
||||||
|
@ -149,7 +150,7 @@ describe JsLocaleHelper do
|
||||||
SiteSetting.default_locale = 'ru'
|
SiteSetting.default_locale = 'ru'
|
||||||
I18n.locale = :uk
|
I18n.locale = :uk
|
||||||
|
|
||||||
ctx = V8::Context.new
|
ctx = MiniRacer::Context.new
|
||||||
ctx.eval('var window = this;')
|
ctx.eval('var window = this;')
|
||||||
ctx.load(Rails.root + 'app/assets/javascripts/locales/i18n.js')
|
ctx.load(Rails.root + 'app/assets/javascripts/locales/i18n.js')
|
||||||
ctx.eval(JsLocaleHelper.output_locale(I18n.locale))
|
ctx.eval(JsLocaleHelper.output_locale(I18n.locale))
|
||||||
|
@ -167,7 +168,7 @@ describe JsLocaleHelper do
|
||||||
LocaleSiteSetting.values.each do |locale|
|
LocaleSiteSetting.values.each do |locale|
|
||||||
it "generates valid date helpers for #{locale[:value]} locale" do
|
it "generates valid date helpers for #{locale[:value]} locale" do
|
||||||
js = JsLocaleHelper.output_locale(locale[:value])
|
js = JsLocaleHelper.output_locale(locale[:value])
|
||||||
ctx = V8::Context.new
|
ctx = MiniRacer::Context.new
|
||||||
ctx.eval('var window = this;')
|
ctx.eval('var window = this;')
|
||||||
ctx.load(Rails.root + 'app/assets/javascripts/locales/i18n.js')
|
ctx.load(Rails.root + 'app/assets/javascripts/locales/i18n.js')
|
||||||
ctx.eval(js)
|
ctx.eval(js)
|
||||||
|
|
Loading…
Reference in a new issue