Skip to content

Commit

Permalink
fix: Throw invalid delta error if change is out of range and added V2…
Browse files Browse the repository at this point in the history
… for worker and worker client (#4721)

* fix: Throw invalid delta error if change is out of range and added V2 for worker and worker client

* fixed eslint
  • Loading branch information
andrewnester authored May 30, 2022
1 parent 87ad55d commit f269889
Show file tree
Hide file tree
Showing 3 changed files with 372 additions and 3 deletions.
18 changes: 15 additions & 3 deletions lib/ace/worker/mirror.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
define(function(require, exports, module) {
"use strict";

var Range = require("../range").Range;
var Document = require("../document").Document;
var lang = require("../lib/lang");

Expand All @@ -18,11 +17,24 @@ var Mirror = exports.Mirror = function(sender) {
doc.applyDeltas(data);
} else {
for (var i = 0; i < data.length; i += 2) {
var d, err;
if (Array.isArray(data[i+1])) {
var d = {action: "insert", start: data[i], lines: data[i+1]};
d = {action: "insert", start: data[i], lines: data[i+1]};
} else {
var d = {action: "remove", start: data[i], end: data[i+1]};
d = {action: "remove", start: data[i], end: data[i+1]};
}

if ((d.action == "insert" ? d.start : d.end).row >= doc.$lines.length) {
err = new Error("Invalid delta");
err.data = {
path: _self.$path,
linesLength: doc.$lines.length,
start: d.start,
end: d.end
};
throw err;
}

doc.applyDelta(d, true);
}
}
Expand Down
89 changes: 89 additions & 0 deletions lib/ace/worker/v2/worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*global self*/
define(function (require, exports, module) {
var EventEmitter = require("ace/lib/event_emitter").EventEmitter;
var oop = require("ace/lib/oop");

if (typeof window == "undefined") {
if (typeof self != "undefined") window = self;
if (typeof global != "undefined") window = global;
}

if (!window.console) {
window.console = function () {
var msgs = Array.prototype.slice.call(arguments, 0);
postMessage({ type: "log", data: msgs });
};
window.console.error =
window.console.warn =
window.console.log =
window.console.trace =
window.console;
}
window.window = window;

window.onerror = function (message, file, line, col, err) {
postMessage({
type: "error",
data: {
message: message,
data: err.data,
file: file,
line: line,
col: col,
stack: err.stack
}
});
};

var Sender = function () {};
(function () {
oop.implement(this, EventEmitter);

this.callback = function (data, callbackId) {
postMessage({
type: "call",
id: callbackId,
data: data
});
};

this.emit = function (name, data) {
postMessage({
type: "event",
name: name,
data: data
});
};
}.call(Sender.prototype));

window.initSender = function initSender(clazz) {
sender = window.sender = new Sender();
main = window.main = new clazz(sender);
};

var main = (window.main = null);
var sender = (window.sender = null);

window.updateRequireConfig = function (config) {
window.require.config(config);
};

window.onmessage = function (e) {
var msg = e.data;
if (msg.event && sender) {
sender._signal(msg.event, msg.data);
} else if (msg.command) {
if (main && main[msg.command]) main[msg.command].apply(main, msg.args);
else if (window[msg.command]) window[msg.command].apply(window, msg.args);
else throw new Error("Unknown command:" + msg.command);
} else {
if (msg.requireConfig) {
if (!window.require || typeof define != "function")
importScripts(msg.requireConfig.requireSourceUrl);
window.require.config(msg.requireConfig);
}
}
};

module.exports = window;
});
268 changes: 268 additions & 0 deletions lib/ace/worker/v2/worker_client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2010, Ajax.org B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Ajax.org B.V. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** END LICENSE BLOCK ***** */

define(function (require, exports, module) {
"use strict";

var oop = require("../../lib/oop");
var net = require("../../lib/net");
var EventEmitter = require("../../lib/event_emitter").EventEmitter;
var config = require("../../config");

function $workerBlob() {
// workerUrl can be protocol relative
// importScripts only takes fully qualified urls
/* istanbul ignore next */
var script =
"this.onmessage = " +
function (e) {
var msg = e.data;
if (msg.command) {
msg.command
.split(".")
.reduce(function (o, k) {
return o[k];
}, this)
.apply(this, msg.args);
}
+"\n//# sourceURL=ace/worker/bootstrap";
};

try {
return new Blob([script], { type: "application/javascript" });
} catch (e) {
// Backwards-compatibility
var BlobBuilder =
window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
var blobBuilder = new BlobBuilder();
blobBuilder.append(script);
return blobBuilder.getBlob("application/javascript");
}
}

function createWorker(workerUrl) {
var blob = $workerBlob();
var URL = window.URL || window.webkitURL;
var blobURL = URL.createObjectURL(blob);
// calling URL.revokeObjectURL before worker is terminated breaks it on IE Edge
var worker = new Worker(blobURL);
if (workerUrl)
worker.postMessage({
command: "importScripts",
args: [net.qualifyURL(workerUrl)]
});
return worker;
}

var WorkerClient = function (id, workerUrl, importScripts) {
this.$sendDeltaQueue = this.$sendDeltaQueue.bind(this);
this.changeListener = this.changeListener.bind(this);
this.onMessage = this.onMessage.bind(this);

// nameToUrl is renamed to toUrl in requirejs 2
if (require.nameToUrl && !require.toUrl) require.toUrl = require.nameToUrl;

/* eslint-disable no-undef */
var requireConfig = requirejs.getConfig();
if (requireConfig.requireSourceUrl) {
this.$worker = createWorker(requireConfig.requireSourceUrl);
this.$worker.postMessage({
command: "require.config",
args: [requireConfig]
});
} else {
this.$worker = createWorker(workerUrl);
}
if (id) {
this.send("require", [[id]]);
}
if (importScripts) {
this.send("importScripts", importScripts);
}

this.callbackId = 1;
this.callbacks = {};

this.$worker.onmessage = this.onMessage;
};

(function () {
oop.implement(this, EventEmitter);

this.onMessage = function (e) {
var msg = e.data;
switch (msg.type) {
case "event":
this._signal(msg.name, { data: msg.data });
break;
case "call":
var callback = this.callbacks[msg.id];
if (callback) {
callback(msg.data);
delete this.callbacks[msg.id];
}
break;
case "error":
this.reportError(msg.data);
break;
case "log":
window.console && console.log && console.log.apply(console, msg.data);
break;
}
};

this.reportError = function (err) {
window.console && console.error && console.error(err);
};

this.$normalizePath = function (path) {
return net.qualifyURL(path);
};

this.terminate = function () {
this._signal("terminate", {});
this.deltaQueue = null;
this.$worker.terminate();
this.$worker = null;
this.send = this.reportError = function () {};
if (this.$doc) this.$doc.off("change", this.changeListener);
this.$doc = null;
};

this.send = function (cmd, args) {
this.$worker.postMessage({ command: cmd, args: args });
};

this.call = function (cmd, args, callback) {
if (callback) {
var id = this.callbackId++;
this.callbacks[id] = callback;
args.push(id);
}
this.send(cmd, args);
};

this.emit = function (event, data) {
try {
// firefox refuses to clone objects which have function properties
// TODO: cleanup event
if (data.data && data.data.err && data.data.err.message)
data.data.err = {
message: data.data.err.message,
stack: data.data.err.stack,
code: data.data.err.code
};
this.$worker.postMessage({ event: event, data: { data: data.data } });
} catch (ex) {
console.error(ex.stack);
}
};

this.attachToDocument = function (doc) {
if (this.$doc) this.terminate();

this.$doc = doc;
this.call("setValue", [doc.getValue()]);
doc.on("change", this.changeListener);
};

this.changeListener = function (delta) {
if (!this.deltaQueue) {
this.deltaQueue = [];
setTimeout(this.$sendDeltaQueue, 0);
}
if (delta.action == "insert")
this.deltaQueue.push(delta.start, delta.lines);
else this.deltaQueue.push(delta.start, delta.end);
};

this.$sendDeltaQueue = function () {
var q = this.deltaQueue;
if (!q) return;
this.deltaQueue = null;
if (q.length > 50 && q.length > this.$doc.getLength() >> 1) {
this.call("setValue", [this.$doc.getValue()]);
} else this.emit("change", { data: q });
};
}.call(WorkerClient.prototype));

var UIWorkerClient = function (id, path) {
this.$sendDeltaQueue = this.$sendDeltaQueue.bind(this);
this.changeListener = this.changeListener.bind(this);
this.callbackId = 1;
this.callbacks = {};
this.messageBuffer = [];

var main = null;
var emitSync = false;
var sender = Object.create(EventEmitter);
var _self = this;

this.$worker = {};
this.$worker.terminate = function () {};
this.$worker.postMessage = function (e) {
_self.messageBuffer.push(e);
if (main) {
if (emitSync) setTimeout(processNext);
else processNext();
}
};
this.setEmitSync = function (val) {
emitSync = val;
};

var processNext = function () {
var msg = _self.messageBuffer.shift();
if (msg.command && main[msg.command])
main[msg.command].apply(main, msg.args);
else if (msg.event) sender._signal(msg.event, msg.data);
};

sender.postMessage = function (msg) {
_self.onMessage({ data: msg });
};
sender.callback = function (data, callbackId) {
this.postMessage({ type: "call", id: callbackId, data: data });
};
sender.emit = function (name, data) {
this.postMessage({ type: "event", name: name, data: data });
};

config.loadModule(path, function () {
while (_self.messageBuffer.length) processNext();
});
};

UIWorkerClient.prototype = WorkerClient.prototype;

exports.UIWorkerClient = UIWorkerClient;
exports.WorkerClient = WorkerClient;
exports.createWorker = createWorker;
});

0 comments on commit f269889

Please sign in to comment.