diff --git a/package.json b/package.json index 16d78d20e4..afacc50006 100644 --- a/package.json +++ b/package.json @@ -165,6 +165,7 @@ "nightwatch_local_firefox": "nightwatch --config nightwatch.js --env firefox", "nightwatch_local_chrome": "nightwatch --config nightwatch.js --env chrome", "nightwatch_local_ballot": "nightwatch ./test-browser/tests/ballot.test.js --config nightwatch.js --env chrome ", + "nightwatch_local_usingWorker": "nightwatch ./test-browser/tests/usingWebWorker.test.js --config nightwatch.js --env chrome ", "nightwatch_local_libraryDeployment": "nightwatch ./test-browser/tests/libraryDeployment.test.js --config nightwatch.js --env chrome ", "nightwatch_local_solidityImport": "nightwatch ./test-browser/tests/solidityImport.test.js --config nightwatch.js --env chrome ", "nightwatch_local_recorder": "nightwatch ./test-browser/tests/recorder.test.js --config nightwatch.js --env chrome ", diff --git a/src/app/compiler/compiler-helpers.js b/src/app/compiler/compiler-helpers.js index a20ffa13b1..820e5c298f 100644 --- a/src/app/compiler/compiler-helpers.js +++ b/src/app/compiler/compiler-helpers.js @@ -1,5 +1,5 @@ 'use strict' -import { canUseWorker } from './compiler-utils' +import { canUseWorker, urlFromVersion } from './compiler-utils' import { Compiler } from 'remix-solidity' import CompilerAbstract from './compiler-abstract' @@ -9,7 +9,7 @@ export const compile = async (compilationTargets, settings) => { const compiler = new Compiler(() => {}) compiler.set('evmVersion', settings.evmVersion) compiler.set('optimize', settings.optimize) - compiler.loadVersion(canUseWorker(settings.version), settings.compilerUrl) + compiler.loadVersion(canUseWorker(settings.version), urlFromVersion(settings.version)) compiler.event.register('compilationFinished', (success, compilationData, source) => { if (!success) return reject(compilationData) resolve(new CompilerAbstract(settings.version, compilationData, source)) diff --git a/src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js b/src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js index 04956b5ac7..5df4e66390 100644 --- a/src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js +++ b/src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js @@ -1,7 +1,6 @@ const ethutil = require('ethereumjs-util') import * as packageJson from '../../../package.json' import { Plugin } from '@remixproject/engine' -import { urlFromVersion } from './compiler-utils' import { compile } from './compiler-helpers' import globalRegistry from '../../global/registry' @@ -108,8 +107,7 @@ export default class FetchAndCompile extends Plugin { version: data.metadata.compiler.version, languageName: data.metadata.language, evmVersion: data.metadata.settings.evmVersion, - optimize: data.metadata.settings.optimizer.enabled, - compilerUrl: urlFromVersion(data.metadata.compiler.version) + optimize: data.metadata.settings.optimizer.enabled } try { setTimeout(_ => this.emit('compiling', settings), 0) diff --git a/src/app/compiler/compiler-utils.js b/src/app/compiler/compiler-utils.js index f7afcf9307..f7222f582d 100644 --- a/src/app/compiler/compiler-utils.js +++ b/src/app/compiler/compiler-utils.js @@ -1,48 +1,44 @@ const semver = require('semver') +const minixhr = require('minixhr') /* global Worker */ -export const baseUrl = 'https://solc-bin.ethereum.org/bin' +export const baseURLBin = 'https://solc-bin.ethereum.org/bin' +export const baseURLWasm = 'https://solc-bin.ethereum.org/wasm' +export const pathToURL = {} + +/** + * Retrieves the URL of the given compiler version + * @param version is the version of compiler with or without 'soljson-v' prefix and .js postfix + */ export function urlFromVersion (version) { - return `${baseUrl}/soljson-v${version}.js` + if (!version.startsWith('soljson-v')) version = 'soljson-v' + version + if (!version.endsWith('.js')) version = version + '.js' + return `${pathToURL[version]}/${version}` } + /** * Checks if the worker can be used to load a compiler. * checks a compiler whitelist, browser support and OS. */ export function canUseWorker (selectedVersion) { - // Following restrictions should be deleted when Solidity will release fixed versions of compilers. - // See https://github.com/ethereum/remix-ide/issues/2461 - const isChrome = !!window.chrome - const os = retrieveOS() - // define a whitelist for Linux - const linuxWL = ['0.4.26', '0.5.3', '0.5.4', '0.5.5'] const version = semver.coerce(selectedVersion) - // defining whitelist for chrome - let isFromWhiteList = false - switch (os) { - case 'Windows': - isFromWhiteList = semver.gt(version, '0.5.2') || version === '0.4.26' - break - case 'Linux': - isFromWhiteList = semver.gt(version, '0.5.13') || linuxWL.includes(version) - break - default : - isFromWhiteList = true - } - return browserSupportWorker() && (!isChrome || (isChrome && isFromWhiteList)) + const isNightly = selectedVersion.includes('nightly') + return browserSupportWorker() && ( + semver.gt(version, '0.6.3') || + semver.gt(version, '0.3.6') && !isNightly + ) } function browserSupportWorker () { return document.location.protocol !== 'file:' && Worker !== undefined } -function retrieveOS () { - let osName = 'Unknown OS' - if (navigator.platform.indexOf('Win') !== -1) { - osName = 'Windows' - } else if (navigator.platform.indexOf('Linux') !== -1) { - osName = 'Linux' - } - return osName +// returns a promise for minixhr +export function promisedMiniXhr (url) { + return new Promise((resolve, reject) => { + minixhr(url, (json, event) => { + resolve({ json, event }) + }) + }) } diff --git a/src/app/editor/example-contracts.js b/src/app/editor/example-contracts.js index 945abe0349..34c6ea9a64 100644 --- a/src/app/editor/example-contracts.js +++ b/src/app/editor/example-contracts.js @@ -1,5 +1,15 @@ 'use strict' +const basic = `pragma solidity >=0.2.0 <0.7.0; + +/** + * @title Basic contract + */ +contract Basic { + uint someVar; + constructor() public {} +}` + const storage = `pragma solidity >=0.4.22 <0.7.0; /** @@ -245,5 +255,6 @@ module.exports = { storage: { name: '1_Storage.sol', content: storage }, owner: { name: '2_Owner.sol', content: owner }, ballot: { name: '3_Ballot.sol', content: ballot }, - ballot_test: { name: '4_Ballot_test.sol', content: ballotTest } + ballot_test: { name: '4_Ballot_test.sol', content: ballotTest }, + basic: { name: 'basic.sol', content: basic } } diff --git a/src/app/files/fileProvider.js b/src/app/files/fileProvider.js index 116f42b368..1fa54ab11c 100644 --- a/src/app/files/fileProvider.js +++ b/src/app/files/fileProvider.js @@ -93,7 +93,10 @@ class FileProvider { cb = cb || function () {} var unprefixedpath = this.removePrefix(path) var exists = window.remixFileSystem.existsSync(unprefixedpath) - if (exists && window.remixFileSystem.readFileSync(unprefixedpath, 'utf8') === content) return true + if (exists && window.remixFileSystem.readFileSync(unprefixedpath, 'utf8') === content) { + cb() + return true + } if (!exists && unprefixedpath.indexOf('/') !== -1) { this.createDir(path) } diff --git a/src/app/tabs/compile-tab.js b/src/app/tabs/compile-tab.js index a5d8e4de10..52de47dd7e 100644 --- a/src/app/tabs/compile-tab.js +++ b/src/app/tabs/compile-tab.js @@ -116,6 +116,7 @@ class CompileTab extends ViewPlugin { this.fileManager.events.on('noFileSelected', this.data.eventHandlers.onNoFileSelected) this.data.eventHandlers.onCompilationFinished = (success, data, source) => { + this._view.errorContainer.appendChild(yo``) if (success) { // forwarding the event to the appManager infra this.emit('compilationFinished', source.target, source, 'soljson', data) @@ -411,7 +412,7 @@ class CompileTab extends ViewPlugin { render () { if (this._view.el) return this._view.el this.onActivationInternal() - this._view.errorContainer = yo`
` + this._view.errorContainer = yo`` this._view.contractSelection = this.contractSelection() this._view.compilerContainer = this.compilerContainer.render() this.compilerContainer.activate() diff --git a/src/app/tabs/compileTab/compilerContainer.js b/src/app/tabs/compileTab/compilerContainer.js index 5c51fd8ce9..b91f933bf3 100644 --- a/src/app/tabs/compileTab/compilerContainer.js +++ b/src/app/tabs/compileTab/compilerContainer.js @@ -1,12 +1,11 @@ const yo = require('yo-yo') -const minixhr = require('minixhr') const helper = require('../../../lib/helper') const addTooltip = require('../../ui/tooltip') const semver = require('semver') const modalDialogCustom = require('../../ui/modal-dialog-custom') const css = require('../styles/compile-tab-styles') -import { canUseWorker } from '../../compiler/compiler-utils' +import { canUseWorker, baseURLBin, baseURLWasm, urlFromVersion, pathToURL, promisedMiniXhr } from '../../compiler/compiler-utils' class CompilerContainer { @@ -24,8 +23,7 @@ class CompilerContainer { timeout: 300, allversions: null, selectedVersion: null, - defaultVersion: 'soljson-v0.6.6+commit.6c089d02.js', // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler - baseurl: 'https://solc-bin.ethereum.org/bin' + defaultVersion: 'soljson-v0.6.6+commit.6c089d02.js' // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler } } @@ -383,13 +381,13 @@ class CompilerContainer { if (this.data.selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(this.data.selectedVersion)) { return console.log('loading ' + this.data.selectedVersion + ' not allowed') } - url = `${this.data.baseurl}/${this.data.selectedVersion}` + url = `${urlFromVersion(this.data.selectedVersion)}` } // Workers cannot load js on "file:"-URLs and we get a // "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium, // resort to non-worker version in that case. - if (canUseWorker(this.data.selectedVersion)) { + if (this.data.selectedVersion !== 'builtin' && canUseWorker(this.data.selectedVersion)) { this.compileTabLogic.compiler.loadVersion(true, url) this.setVersionText('(loading using worker)') } else { @@ -413,27 +411,42 @@ class CompilerContainer { if (this._view.version) this._view.version.innerText = text } - fetchAllVersion (callback) { - minixhr(`${this.data.baseurl}/list.json`, (json, event) => { - // @TODO: optimise and cache results to improve app loading times #2461 - var allversions, selectedVersion - if (event.type !== 'error') { - try { - const data = JSON.parse(json) - allversions = data.builds.slice().reverse() - selectedVersion = this.data.defaultVersion - if (this.queryParams.get().version) selectedVersion = this.queryParams.get().version - } catch (e) { - addTooltip('Cannot load compiler version list. It might have been blocked by an advertisement blocker. Please try deactivating any of them from this page and reload.') - } - } else { - allversions = [{ path: 'builtin', longVersion: 'latest local version' }] - selectedVersion = 'builtin' + // fetching both normal and wasm builds and creating a [version, baseUrl] map + async fetchAllVersion (callback) { + let allVersions, selectedVersion, allVersionsWasm + // fetch normal builds + const binRes = await promisedMiniXhr(`${baseURLBin}/list.json`) + // fetch wasm builds + const wasmRes = await promisedMiniXhr(`${baseURLWasm}/list.json`) + if (binRes.event.type === 'error' && wasmRes.event.type === 'error') { + allVersions = [{ path: 'builtin', longVersion: 'latest local version' }] + selectedVersion = 'builtin' + callback(allVersions, selectedVersion) + } + try { + allVersions = JSON.parse(binRes.json).builds.slice().reverse() + selectedVersion = this.data.defaultVersion + if (this.queryParams.get().version) selectedVersion = this.queryParams.get().version + if (wasmRes.event.type !== 'error') { + allVersionsWasm = JSON.parse(wasmRes.json).builds.slice().reverse() } - callback(allversions, selectedVersion) - }) + } catch (e) { + addTooltip('Cannot load compiler version list. It might have been blocked by an advertisement blocker. Please try deactivating any of them from this page and reload. Error: ' + e) + } + // replace in allVersions those compiler builds which exist in allVersionsWasm with new once + if (allVersionsWasm && allVersions) { + allVersions.forEach((compiler, index) => { + const wasmIndex = allVersionsWasm.findIndex(wasmCompiler => { return wasmCompiler.longVersion === compiler.longVersion }) + if (wasmIndex !== -1) { + allVersions[index] = allVersionsWasm[wasmIndex] + pathToURL[compiler.path] = baseURLWasm + } else { + pathToURL[compiler.path] = baseURLBin + } + }) + } + callback(allVersions, selectedVersion) } - scheduleCompilation () { if (!this.config.get('autoCompile')) return if (this.data.compileTimeout) window.clearTimeout(this.data.compileTimeout) diff --git a/src/app/tabs/test-tab.js b/src/app/tabs/test-tab.js index 2dfb270a9f..abc9986a1d 100644 --- a/src/app/tabs/test-tab.js +++ b/src/app/tabs/test-tab.js @@ -4,7 +4,7 @@ var tooltip = require('../ui/tooltip') var css = require('./styles/test-tab-styles') var remixTests = require('remix-tests') import { ViewPlugin } from '@remixproject/engine' -import { canUseWorker, baseUrl } from '../compiler/compiler-utils' +import { canUseWorker, urlFromVersion } from '../compiler/compiler-utils' const TestTabLogic = require('./testTab/testTab') @@ -301,7 +301,7 @@ module.exports = class TestTab extends ViewPlugin { let runningTest = {} runningTest[path] = { content } const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig() - const currentCompilerUrl = baseUrl + '/' + currentVersion + const currentCompilerUrl = urlFromVersion(currentVersion) const compilerConfig = { currentCompilerUrl, evmVersion, @@ -327,7 +327,7 @@ module.exports = class TestTab extends ViewPlugin { const runningTest = {} runningTest[testFilePath] = { content } const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig() - const currentCompilerUrl = baseUrl + '/' + currentVersion + const currentCompilerUrl = urlFromVersion(currentVersion) const compilerConfig = { currentCompilerUrl, evmVersion, diff --git a/src/lib/helper.js b/src/lib/helper.js index cda3bfe519..15f692fcc1 100644 --- a/src/lib/helper.js +++ b/src/lib/helper.js @@ -84,4 +84,3 @@ function find (args, query) { }) return isMatch } - diff --git a/test-browser/commands/noWorkerErrorFor.js b/test-browser/commands/noWorkerErrorFor.js new file mode 100644 index 0000000000..614e710c14 --- /dev/null +++ b/test-browser/commands/noWorkerErrorFor.js @@ -0,0 +1,28 @@ +const EventEmitter = require('events') + +class NoWorkerErrorFor extends EventEmitter { + command (version) { + this.api.perform((done) => { + noWorkerErrorFor(this.api, version, () => { + done() + this.emit('complete') + }) + }) + return this + } +} + +function noWorkerErrorFor (browser, version, callback) { + browser + .setSolidityCompilerVersion(version) + .click('*[data-id="compilerContainerCompileBtn"]') + .waitForElementPresent('*[data-id="compilationFinishedWith_' + version + '"]', 10000) + .notContainsText('*[data-id="compiledErrors"]', 'worker error:undefined') + .notContainsText('*[data-id="compiledErrors"]', 'Uncaught RangeError: Maximum call stack size exceeded') + .notContainsText('*[data-id="compiledErrors"]', 'RangeError: Maximum call stack size exceeded') + .perform(() => { + callback() + }) +} + +module.exports = NoWorkerErrorFor diff --git a/test-browser/tests/runAndDeploy.js b/test-browser/tests/runAndDeploy.js index 7f705171b3..1fddeedb54 100644 --- a/test-browser/tests/runAndDeploy.js +++ b/test-browser/tests/runAndDeploy.js @@ -31,8 +31,7 @@ module.exports = { .assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgHash"]') .assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgSignature"]') .modalFooterOKClick() - .pause(2000) - .waitForElementPresent('*[data-id="modalDialogContainer"]') + .waitForElementPresent('*[data-id="modalDialogContainer"]', 12000) .assert.elementPresent('*[data-id="settingsRemixRunSignMsgHash"]') .assert.elementPresent('*[data-id="settingsRemixRunSignMsgSignature"]') .modalFooterOKClick() @@ -145,8 +144,8 @@ module.exports = { .clickLaunchIcon('udapp') .waitForElementPresent('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]') - .pause(2000) - .waitForElementPresent('*[data-id="modalDialogContainer"]') + .waitForElementPresent('*[data-id="modalDialogContainer"]', 15000) + .pause(10000) .assert.containsText('*[data-id="modalDialogModalBody"]', 'You are creating a transaction on the main network. Click confirm if you are sure to continue.') .modalFooterCancelClick() }, diff --git a/test-browser/tests/solidityUnittests.test.js b/test-browser/tests/solidityUnittests.test.js index 3a4882ad51..cdb99c4e03 100644 --- a/test-browser/tests/solidityUnittests.test.js +++ b/test-browser/tests/solidityUnittests.test.js @@ -86,7 +86,7 @@ module.exports = { .scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]') .pause(5000) .click('*[data-id="testTabRunTestsTabStopAction"]') - .pause(2000) + .pause(1000) .assert.containsText('*[data-id="testTabRunTestsTabStopAction"]', 'Stopping') .waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 40000) .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'browser/ks2b_test.sol') diff --git a/test-browser/tests/usingWebWorker.test.js b/test-browser/tests/usingWebWorker.test.js new file mode 100644 index 0000000000..c59da319d3 --- /dev/null +++ b/test-browser/tests/usingWebWorker.test.js @@ -0,0 +1,37 @@ +'use strict' +var examples = require('../../src/app/editor/example-contracts') +var init = require('../helpers/init') +var sauce = require('./sauce') + +var sources = [ + {'browser/basic.sol': {content: examples.basic.content}} +] + +module.exports = { + before: function (browser, done) { + init(browser, done) + }, + '@sources': function () { + return sources + }, + 'Using Web Worker': function (browser) { + browser + .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) + .clickLaunchIcon('fileExplorers') + .openFile('browser/basic.sol') + .clickLaunchIcon('solidity') + .execute(() => { + document.getElementById('nightlies').checked = true + }) + .noWorkerErrorFor('soljson-v0.3.4+commit.7dab890.js') + .noWorkerErrorFor('soljson-v0.6.5+commit.f956cc89.js') + .noWorkerErrorFor('soljson-v0.6.8-nightly.2020.5.14+commit.a6d0067b.js') + .noWorkerErrorFor('soljson-v0.6.0-nightly.2019.12.17+commit.d13438ee.js') + .noWorkerErrorFor('soljson-v0.4.26+commit.4563c3fc.js') + .execute(() => { + document.getElementById('nightlies').checked = false + }) + }, + + tearDown: sauce +}