loleaflet/README | 19 + loleaflet/build/deps.js | 12 + loleaflet/debug/document/document_simple_example.html | 1 loleaflet/dist/leaflet.css | 5 loleaflet/package.json | 5 loleaflet/spec/headlessLoadTest.js | 179 ++++++++++++++++++ loleaflet/spec/loadtest/LoadTestSpec.js | 7 loleaflet/spec/suites/layer/marker/MarkerSpec.js | 2 loleaflet/spec/suites/layer/tile/GridLayerSpec.js | 74 ------- loleaflet/spec/suites/layer/vector/CircleSpec.js | 2 loleaflet/spec/suites/map/MapSpec.js | 2 loleaflet/src/control/Control.Styles.js | 61 ++++++ loleaflet/src/control/Styles.js | 17 + loleaflet/src/layer/Layer.js | 3 loleaflet/src/layer/tile/GridLayer.js | 24 +- loleaflet/src/layer/tile/TileLayer.js | 5 loleaflet/src/map/Map.js | 18 + loolwsd/LOOLSession.cpp | 40 +++- loolwsd/LOOLSession.hpp | 6 loolwsd/TileCache.cpp | 42 ++-- loolwsd/TileCache.hpp | 6 loolwsd/protocol.txt | 4 22 files changed, 412 insertions(+), 122 deletions(-)
New commits: commit 573c6e5b3d3b82029560210878b13498b4f2e5c0 Author: Mihai Varga <[email protected]> Date: Thu Aug 20 14:43:40 2015 +0300 loleaflet: always return the current size diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js index c3f091a..ebe67ee 100644 --- a/loleaflet/src/map/Map.js +++ b/loleaflet/src/map/Map.js @@ -333,13 +333,11 @@ L.Map = L.Evented.extend({ }, getSize: function () { - if (!this._size || this._sizeChanged) { - this._size = new L.Point( - this._container.clientWidth, - this._container.clientHeight); + this._size = new L.Point( + this._container.clientWidth, + this._container.clientHeight); - this._sizeChanged = false; - } + this._sizeChanged = false; return this._size.clone(); }, commit 7bd000a8fc3caeb36724f94c2dbe77cb7bfc16b0 Author: Mihai Varga <[email protected]> Date: Wed Aug 19 18:28:14 2015 +0300 loleaflet: adapted some of the unit tests diff --git a/loleaflet/spec/suites/layer/marker/MarkerSpec.js b/loleaflet/spec/suites/layer/marker/MarkerSpec.js index 2d28fac..d33a8c9 100644 --- a/loleaflet/spec/suites/layer/marker/MarkerSpec.js +++ b/loleaflet/spec/suites/layer/marker/MarkerSpec.js @@ -32,7 +32,7 @@ describe("Marker", function () { var afterIcon = marker._icon; expect(beforeIcon).to.be(afterIcon); - expect(afterIcon.src).to.contain(icon2._getIconUrl('icon')); + expect(afterIcon.src.replace(/\.\.\//g, '')).to.be.(icon2._getIconUrl('icon').replace(/\.\.\//g, '')); }); it("preserves draggability", function () { diff --git a/loleaflet/spec/suites/layer/tile/GridLayerSpec.js b/loleaflet/spec/suites/layer/tile/GridLayerSpec.js index 1ca4227..4181f7e 100644 --- a/loleaflet/spec/suites/layer/tile/GridLayerSpec.js +++ b/loleaflet/spec/suites/layer/tile/GridLayerSpec.js @@ -32,80 +32,6 @@ describe('GridLayer', function () { }); }); - it('positions tiles correctly with wrapping and bounding', function () { - - map.setView([0, 0], 1); - - var tiles = []; - - var grid = L.gridLayer(); - grid.createTile = function (coords) { - var tile = document.createElement('div'); - tiles.push({coords: coords, tile: tile}); - return tile; - }; - - map.addLayer(grid); - - var loaded = {}; - - for (var i = 0; i < tiles.length; i++) { - var coords = tiles[i].coords, - pos = L.DomUtil.getPosition(tiles[i].tile); - - loaded[pos.x + ':' + pos.y] = [coords.x, coords.y]; - } - - expect(loaded).to.eql({ - '144:44': [0, 0], - '400:44': [1, 0], - '144:300': [0, 1], - '400:300': [1, 1], - '-112:44': [1, 0], - '656:44': [0, 0], - '-112:300': [1, 1], - '656:300': [0, 1] - }); - }); - - describe('tile pyramid', function () { - var clock; - - beforeEach(function () { - clock = sinon.useFakeTimers(); - }); - - afterEach(function () { - clock.restore(); - }); - - it('removes tiles for unused zoom levels', function (done) { - map.remove(); - map = L.map(div, {fadeAnimation: false}); - map.setView([0, 0], 1); - - var grid = L.gridLayer(); - var tiles = {}; - - grid.createTile = function (coords) { - tiles[grid._tileCoordsToKey(coords)] = true; - return document.createElement('div'); - }; - - grid.on('tileunload', function (e) { - delete tiles[grid._tileCoordsToKey(e.coords)]; - if (Object.keys(tiles).length === 1) { - expect(Object.keys(tiles)).to.eql(['0:0:0']); - done(); - } - }); - - map.addLayer(grid); - map.setZoom(0, {animate: false}); - clock.tick(250); - }); - }); - describe("#onAdd", function () { it('is called after viewreset on first map load', function () { var layer = L.gridLayer().addTo(map); diff --git a/loleaflet/spec/suites/layer/vector/CircleSpec.js b/loleaflet/spec/suites/layer/vector/CircleSpec.js index 0b45bc8..d48a2c2 100644 --- a/loleaflet/spec/suites/layer/vector/CircleSpec.js +++ b/loleaflet/spec/suites/layer/vector/CircleSpec.js @@ -4,7 +4,7 @@ describe('Circle', function () { var map, circle; beforeEach(function () { - map = L.map(document.createElement('div')).setView([0, 0], 4); + map = L.map(document.createElement('div'), {crs: L.CRS.EPSG3857}).setView([0, 0], 4); circle = L.circle([50, 30], 200).addTo(map); }); diff --git a/loleaflet/spec/suites/map/MapSpec.js b/loleaflet/spec/suites/map/MapSpec.js index e7261ce..2125eae 100644 --- a/loleaflet/spec/suites/map/MapSpec.js +++ b/loleaflet/spec/suites/map/MapSpec.js @@ -2,7 +2,7 @@ describe("Map", function () { var map, spy; beforeEach(function () { - map = L.map(document.createElement('div')); + map = L.map(document.createElement('div'), {crs: L.CRS.EPSG3857}); }); describe("#remove", function () { commit 8a99c8877acda14e7848a8d5724726b8549820dc Author: Mihai Varga <[email protected]> Date: Wed Aug 19 18:27:33 2015 +0300 loleaflet: fixed some things reported by failing unit tests diff --git a/loleaflet/src/layer/Layer.js b/loleaflet/src/layer/Layer.js index 9d6fb1c..0af279f 100644 --- a/loleaflet/src/layer/Layer.js +++ b/loleaflet/src/layer/Layer.js @@ -59,7 +59,8 @@ L.Layer = L.Evented.extend({ }, _initDocument: function () {}, - _onMessage: function () {} + _onMessage: function () {}, + sendMessage: function () {} }); diff --git a/loleaflet/src/layer/tile/GridLayer.js b/loleaflet/src/layer/tile/GridLayer.js index 5d5cbfd..9ba1301 100644 --- a/loleaflet/src/layer/tile/GridLayer.js +++ b/loleaflet/src/layer/tile/GridLayer.js @@ -40,8 +40,10 @@ L.GridLayer = L.Layer.extend({ this._viewReset(); this._map._docLayer = this; - this._map.socket.onopen = L.bind(this._initDocument, this); - this._map.socket.onmessage = L.bind(this._onMessage, this); + if (this._map.socket) { + this._map.socket.onopen = L.bind(this._initDocument, this); + this._map.socket.onmessage = L.bind(this._onMessage, this); + } if (this._map.socket && this._map.socket.readyState === 1) { // the connection is already open this._initDocument(); @@ -65,18 +67,24 @@ L.GridLayer = L.Layer.extend({ this._tileZoom = null; clearTimeout(this._preFetchIdle); clearInterval(this._tilesPreFetcher); - this._map.socket.onmessage = function () {}; - this._map.socket.onclose = function () {}; - this._map.socket.onerror = function () {}; - this._map.socket.close(); + if (this._map.socket) { + this._map.socket.onmessage = function () {}; + this._map.socket.onclose = function () {}; + this._map.socket.onerror = function () {}; + this._map.socket.close(); + } if (this._cursorMarker) { this._cursorMarker.remove(); } if (this._graphicMarker) { this._graphicMarker.remove(); } - this._startMarker.remove(); - this._endMarker.remove(); + if (this._startMarker) { + this._startMarker.remove(); + } + if (this._endMarker) { + this._endMarker.remove(); + } }, bringToFront: function () { diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js index 5799ca7..c3f091a 100644 --- a/loleaflet/src/map/Map.js +++ b/loleaflet/src/map/Map.js @@ -249,8 +249,12 @@ L.Map = L.Evented.extend({ this.fire('unload'); } - this.removeLayer(this._docLayer._selections); - this.removeLayer(this._docLayer); + if (this._docLayer) { + if (this._docLayer._selections) { + this.removeLayer(this._docLayer._selections); + } + this.removeLayer(this._docLayer); + } this.removeControls(); return this; }, commit e46766de1904f51b356aba1cb4e3258b5bb5d180 Author: Mihai Varga <[email protected]> Date: Wed Aug 19 10:57:42 2015 +0300 loleaflet: styles control diff --git a/loleaflet/build/deps.js b/loleaflet/build/deps.js index 98b5ee6..5488e50 100644 --- a/loleaflet/build/deps.js +++ b/loleaflet/build/deps.js @@ -289,6 +289,13 @@ var deps = { desc: 'Handles vex dialogs for displaying alerts' }, + ControlStyles: { + src: ['control/Control.js', + 'control/Control.Styles.js'], + heading: 'Controls', + desc: 'Handles styles selection' + }, + ControlAttrib: { src: ['control/Control.js', 'control/Control.Attribution.js'], diff --git a/loleaflet/debug/document/document_simple_example.html b/loleaflet/debug/document/document_simple_example.html index 9d31f45..eb55c1e 100644 --- a/loleaflet/debug/document/document_simple_example.html +++ b/loleaflet/debug/document/document_simple_example.html @@ -67,6 +67,7 @@ }); ////// Controls ///// + globalMap.addControl(L.control.styles()); globalMap.addControl(L.control.buttons()); globalMap.addControl(L.control.zoom()); globalMap.addControl(L.control.parts()); diff --git a/loleaflet/dist/leaflet.css b/loleaflet/dist/leaflet.css index c7c43b7..7c09fb6 100644 --- a/loleaflet/dist/leaflet.css +++ b/loleaflet/dist/leaflet.css @@ -762,6 +762,11 @@ input.leaflet-control-search-bar { padding: 0.3em; } +.leaflet-control-styles { + padding: 0.2em 0.4em 0.4em 0em; + background-color: white; +} + .leaflet-control-zoom.leaflet-bar { margin-left: 0.5em; } diff --git a/loleaflet/src/control/Control.Styles.js b/loleaflet/src/control/Control.Styles.js new file mode 100644 index 0000000..7bedc43 --- /dev/null +++ b/loleaflet/src/control/Control.Styles.js @@ -0,0 +1,61 @@ +/* + * L.Control.Styles is used to display a dropdown list of styles + */ + +L.Control.Styles = L.Control.extend({ + options: { + info: '- Please select a style -' + }, + + onAdd: function (map) { + var stylesName = 'leaflet-control-styles'; + this._container = L.DomUtil.create('select', stylesName + ' leaflet-bar'), + + map.on('updatepermission', this._onUpdatePermission, this); + map.on('updatestyles', this._initList, this); + L.DomEvent.on(this._container, 'change', this._onChange, this); + + return this._container; + }, + + onRemove: function (map) { + map.off('updatepermission', this._searchResultFound, this); + }, + + _initList: function (e) { + var container = this._container; + var first = L.DomUtil.create('option', '', container); + first.innerHTML = this.options.info; + if (this._map._docLayer._docType === 'text') { + var styles = e.styles.ParagraphStyles.slice(0, 12); + styles.forEach(function (style) { + var item = L.DomUtil.create('option', '', container); + item.value = style; + item.innerHTML = style; + }); + } + }, + + _onUpdatePermission: function (e) { + if (e.perm === 'edit') { + this._container.disabled = false; + } + else { + this._container.disabled = true; + } + }, + + _onChange: function (e) { + var style = e.target.value; + if (style === this.options.info) { + return; + } + if (this._map._docLayer._docType === 'text') { + this._map.setStyle(style, 'ParagraphStyles'); + } + } +}); + +L.control.styles = function (options) { + return new L.Control.Styles(options); +}; commit 438e4008591313c2be7ce2263e9091bf0d5e90e5 Author: Mihai Varga <[email protected]> Date: Tue Aug 18 21:24:58 2015 +0300 loleaflet: document style setter and getter diff --git a/loleaflet/README b/loleaflet/README index 78853f9..995f3ed 100644 --- a/loleaflet/README +++ b/loleaflet/README @@ -172,6 +172,15 @@ Writer pages: + e.pages = number of pages + e.docType = document type, should be 'text' +Styles: + - API: + map.getStyles() + + returns a JSON object as a mapping of style families to a list of styles + map.setStyle(style, familyName) + - events: + map.on('updatestyles', function (e) {}) where: + e.styles = a JSON object as a mapping of style families to a list of styles + Error: - events map.on('error', function (e) {}) where diff --git a/loleaflet/build/deps.js b/loleaflet/build/deps.js index d527e7d..98b5ee6 100644 --- a/loleaflet/build/deps.js +++ b/loleaflet/build/deps.js @@ -332,6 +332,11 @@ var deps = { desc: 'Scroll handler.' }, + Styles: { + src: ['control/Styles.js'], + desc: 'Document styles handler.' + }, + AnimationPan: { src: [ 'dom/DomEvent.js', diff --git a/loleaflet/src/control/Styles.js b/loleaflet/src/control/Styles.js new file mode 100644 index 0000000..ca233de --- /dev/null +++ b/loleaflet/src/control/Styles.js @@ -0,0 +1,17 @@ +L.Map.include({ + getStyles: function () { + return this._docLayer._docStyles; + }, + + setStyle: function (style, familyName) { + if (!style || !familyName) { + this.fire('error', {cmd: 'setStyle', kind: 'incorrectparam'}); + return; + } + var msg = 'uno .uno:StyleApply {' + + '"Style":{"type":"string", "value": "' + style + '"},' + + '"FamilyName":{"type":"string", "value":"' + familyName + '"}' + + '}'; + this._docLayer.sendMessage(msg); + } +}); diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 8d7191e..010f7b0 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -105,6 +105,7 @@ L.TileLayer = L.GridLayer.extend({ } this.sendMessage(msg); this.sendMessage('status'); + this.sendMessage('styles'); } this._map.on('drag resize zoomend', this._updateScrollOffset, this); this._map.on('clearselection', this._clearSelections, this); @@ -509,6 +510,10 @@ L.TileLayer = L.GridLayer.extend({ var originalPhrase = textMsg.substring(16); this._map.fire('search', {originalPhrase: originalPhrase, count: 0}); } + else if (textMsg.startsWith('styles:')) { + this._docStyles = JSON.parse(textMsg.substring(8)); + this._map.fire('updatestyles', {styles: this._docStyles}); + } else if (textMsg.startsWith('error:')) { command = this._parseServerCmd(textMsg); this._map.fire('error', {cmd: command.errorCmd, kind: command.errorKind}); commit 1cfd1352ce70c546f2b11e32eadf8a408b49382d Author: Mihai Varga <[email protected]> Date: Tue Aug 18 21:01:05 2015 +0300 loolwsd: renamed getStatus/saveStatus to getTextFile/saveTextFile And added a getStyles method that uses the above methods to cache document styles diff --git a/loolwsd/LOOLSession.cpp b/loolwsd/LOOLSession.cpp index 1202639..b48af37 100644 --- a/loolwsd/LOOLSession.cpp +++ b/loolwsd/LOOLSession.cpp @@ -195,7 +195,11 @@ bool MasterProcessSession::handleInput(const char *buffer, int length) } else if (tokens[0] == "status:") { - peer->_tileCache->saveStatus(std::string(buffer, length)); + peer->_tileCache->saveTextFile(std::string(buffer, length), "status.txt"); + } + else if (tokens[0] == "styles:") + { + peer->_tileCache->saveTextFile(std::string(buffer, length), "styles.txt"); } else if (tokens[0] == "invalidatetiles:") { @@ -277,6 +281,7 @@ bool MasterProcessSession::handleInput(const char *buffer, int length) tokens[0] != "setclientpart" && tokens[0] != "setpage" && tokens[0] != "status" && + tokens[0] != "styles" && tokens[0] != "tile" && tokens[0] != "uno") { @@ -301,6 +306,10 @@ bool MasterProcessSession::handleInput(const char *buffer, int length) { return getStatus(buffer, length); } + else if (tokens[0] == "styles") + { + return getStyles(buffer, length); + } else if (tokens[0] == "tile") { sendTile(buffer, length, tokens); @@ -394,7 +403,7 @@ bool MasterProcessSession::getStatus(const char *buffer, int length) { std::string status; - status = _tileCache->getStatus(); + status = _tileCache->getTextFile("status.txt"); if (status.size() > 0) { sendTextFrame(status); @@ -407,6 +416,23 @@ bool MasterProcessSession::getStatus(const char *buffer, int length) return true; } +bool MasterProcessSession::getStyles(const char *buffer, int length) +{ + std::string styles; + + styles = _tileCache->getTextFile("styles.txt"); + if (styles.size() > 0) + { + sendTextFrame(styles); + return true; + } + + if (_peer.expired()) + dispatchChild(); + forwardToPeer(buffer, length); + return true; +} + void MasterProcessSession::sendTile(const char *buffer, int length, StringTokenizer& tokens) { int part, width, height, tilePosX, tilePosY, tileWidth, tileHeight; @@ -608,6 +634,10 @@ bool ChildProcessSession::handleInput(const char *buffer, int length) { return getStatus(buffer, length); } + else if (tokens[0] == "styles") + { + return getStyles(buffer, length); + } else if (tokens[0] == "tile") { sendTile(buffer, length, tokens); @@ -819,6 +849,12 @@ bool ChildProcessSession::getStatus(const char *buffer, int length) return true; } +bool ChildProcessSession::getStyles(const char *buffer, int length) +{ + sendTextFrame("styles: " + std::string(_loKitDocument->pClass->getStyles(_loKitDocument))); + return true; +} + void ChildProcessSession::sendTile(const char *buffer, int length, StringTokenizer& tokens) { int part, width, height, tilePosX, tilePosY, tileWidth, tileHeight; diff --git a/loolwsd/LOOLSession.hpp b/loolwsd/LOOLSession.hpp index 0030f28..a63e40e 100644 --- a/loolwsd/LOOLSession.hpp +++ b/loolwsd/LOOLSession.hpp @@ -45,6 +45,8 @@ public: virtual bool getStatus(const char *buffer, int length) = 0; + virtual bool getStyles(const char *buffer, int length) = 0; + virtual bool handleInput(const char *buffer, int length) = 0; protected: @@ -109,6 +111,8 @@ public: virtual bool getStatus(const char *buffer, int length); + virtual bool getStyles(const char *buffer, int length); + protected: bool invalidateTiles(const char *buffer, int length, Poco::StringTokenizer& tokens); @@ -154,6 +158,8 @@ public: virtual bool getStatus(const char *buffer, int length); + virtual bool getStyles(const char *buffer, int length); + LibreOfficeKitDocument *_loKitDocument; std::string _docType; diff --git a/loolwsd/TileCache.cpp b/loolwsd/TileCache.cpp index 01714a4..76ca730 100644 --- a/loolwsd/TileCache.cpp +++ b/loolwsd/TileCache.cpp @@ -106,37 +106,37 @@ void TileCache::saveTile(int part, int width, int height, int tilePosX, int tile outStream.close(); } -std::string TileCache::getStatus() +std::string TileCache::getTextFile(std::string fileName) { - const char statusFile[] = "/status.txt"; + const char *textFile = std::string("/" + fileName).c_str(); std::string dirName = cacheDirName(false); if (_hasUnsavedChanges) { - // try the Editing cache first, and prefer its status.txt if it exists + // try the Editing cache first, and prefer it if it exists std::string editingDirName = cacheDirName(true); File dir(editingDirName); - File status(editingDirName + statusFile); - if (dir.exists() && dir.isDirectory() && status.exists() && !status.isDirectory()) + File text(editingDirName + textFile); + if (dir.exists() && dir.isDirectory() && text.exists() && !text.isDirectory()) dirName = editingDirName; } if (!File(dirName).exists() || !File(dirName).isDirectory()) return ""; - std::string fileName = dirName + statusFile; - std::fstream statusStream(fileName, std::ios::in); - if (!statusStream.is_open()) + fileName = dirName + textFile; + std::fstream textStream(fileName, std::ios::in); + if (!textStream.is_open()) return ""; std::vector<char> result; - statusStream.seekg(0, std::ios_base::end); - std::streamsize size = statusStream.tellg(); + textStream.seekg(0, std::ios_base::end); + std::streamsize size = textStream.tellg(); result.resize(size); - statusStream.seekg(0, std::ios_base::beg); - statusStream.read(result.data(), size); - statusStream.close(); + textStream.seekg(0, std::ios_base::beg); + textStream.read(result.data(), size); + textStream.close(); if (result[result.size()-1] == '\n') result.resize(result.size() - 1); @@ -170,24 +170,22 @@ void TileCache::setEditing(bool editing) _isEditing = editing; } -void TileCache::saveStatus(const std::string& status) +void TileCache::saveTextFile(const std::string& text, std::string fileName) { std::string dirName = cacheDirName(_hasUnsavedChanges); File(dirName).createDirectories(); - StringTokenizer tokens(status, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); + StringTokenizer tokens(text, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); - assert(tokens[0] == "status:"); + fileName = dirName + "/" + fileName; + std::fstream textStream(fileName, std::ios::out); - std::string fileName = dirName + "/status.txt"; - std::fstream statusStream(fileName, std::ios::out); - - if (!statusStream.is_open()) + if (!textStream.is_open()) return; - statusStream << status << std::endl; - statusStream.close(); + textStream << text << std::endl; + textStream.close(); } void TileCache::invalidateTiles(int part, int x, int y, int width, int height) diff --git a/loolwsd/TileCache.hpp b/loolwsd/TileCache.hpp index 1e01078..8294784 100644 --- a/loolwsd/TileCache.hpp +++ b/loolwsd/TileCache.hpp @@ -40,7 +40,7 @@ public: std::unique_ptr<std::fstream> lookupTile(int part, int width, int height, int tilePosX, int tilePosY, int tileWidth, int tileHeight); void saveTile(int part, int width, int height, int tilePosX, int tilePosY, int tileWidth, int tileHeight, const char *data, size_t size); - std::string getStatus(); + std::string getTextFile(std::string fileName); /// Notify the cache that the document was saved - to copy tiles from the Editing cache to Persistent. void documentSaved(); @@ -48,8 +48,8 @@ public: /// Notify whether we need to use the Editing cache. void setEditing(bool editing = true); - // The parameter is a status: message - void saveStatus(const std::string& status); + // The parameter is a message + void saveTextFile(const std::string& text, std::string fileName); // The tiles parameter is an invalidatetiles: message as sent by the child process void invalidateTiles(const std::string& tiles); diff --git a/loolwsd/protocol.txt b/loolwsd/protocol.txt index 734d317..369bb69 100644 --- a/loolwsd/protocol.txt +++ b/loolwsd/protocol.txt @@ -65,6 +65,8 @@ selectgraphic type=<type> x=<x> y=<y> status +styles + tile part=<partNumber> width=<width> height=<height> tileposx=<xpos> tileposy=<ypos> tilewidth=<tileWidth> tileheight=<tileHeight> All parameters are numbers. @@ -113,6 +115,8 @@ status: type=<typeName> parts=<numberOfParts> current=<currentPartNumber> width= <typeName> is 'text, 'spreadsheet', 'presentation', 'drawing' or 'other. Others are numbers. if the document has multiple parts and those have names, part names follow separated by '\n' +styles: {"styleFamily": ["styles in family"], etc. } + textselectioncontent: <content> Current selection's content commit b5688840adb7eb72340e28d0e40d0e463b0780c4 Author: Mihai Varga <[email protected]> Date: Tue Aug 18 14:34:10 2015 +0300 loleaflet: mention how to run the tests diff --git a/loleaflet/README b/loleaflet/README index 0ce0f70..78853f9 100644 --- a/loleaflet/README +++ b/loleaflet/README @@ -179,6 +179,16 @@ Error: + [e.cmd] = the command that caused the error + [e.kind] = the kind of error +Testing +------- + - to simulate an editing session and to get the tile loading times + + open spec/tilebench.html in the browser + - to simulate a client opening several documents in the browser + + open spec/loadtest.html in the browser + - to simulate a client opening several documents in the console + + run: mocha headlessLoadTest.js + + Contributing ------------ commit 7e7e458b1ef8107f211cbdd76c9df453eeca1497 Author: Mihai Varga <[email protected]> Date: Tue Aug 18 14:27:17 2015 +0300 loleaflet: headless load test diff --git a/loleaflet/package.json b/loleaflet/package.json index b6e4b12..9c423c4 100644 --- a/loleaflet/package.json +++ b/loleaflet/package.json @@ -38,5 +38,8 @@ "type": "BSD-2-Clause", "url": "https://github.com/Leaflet/Leaflet/blob/master/LICENSE" } - ] + ], + "dependencies": { + "ws": "~0.7.2" + } } diff --git a/loleaflet/spec/headlessLoadTest.js b/loleaflet/spec/headlessLoadTest.js new file mode 100644 index 0000000..fa88982 --- /dev/null +++ b/loleaflet/spec/headlessLoadTest.js @@ -0,0 +1,179 @@ +var WebSocket = require('ws'); +var events = require('events'); + +if (typeof String.prototype.startsWith != 'function') { + String.prototype.startsWith = function (str){ + return this.indexOf(str) === 0; + }; +} + +describe('LoadTest', function () { + // 10s timeout + this.timeout(10000); + // set the slow time to 5ms knowing each test takes more than that, + // so the run time is always printed + this.slow(5); + var testsRan = 0, + tileSize = 256, + tileSizeTwips = 3000, + host = 'ws://localhost:9980'; + + var docPath = 'file:///PATH'; + var docs = ['eval.odt', 'lorem.odt']; + + var _parseServerCmd = function (msg) { + var tokens = msg.split(/[ \n]+/); + var command = {}; + for (var i = 0; i < tokens.length; i++) { + if (tokens[i].substring(0, 9) === 'tileposx=') { + command.x = parseInt(tokens[i].substring(9)); + } + else if (tokens[i].substring(0, 9) === 'tileposy=') { + command.y = parseInt(tokens[i].substring(9)); + } + else if (tokens[i].substring(0, 10) === 'tilewidth=') { + command.tileWidth = parseInt(tokens[i].substring(10)); + } + else if (tokens[i].substring(0, 11) === 'tileheight=') { + command.tileHeight = parseInt(tokens[i].substring(11)); + } + else if (tokens[i].substring(0, 6) === 'width=') { + command.width = parseInt(tokens[i].substring(6)); + } + else if (tokens[i].substring(0, 7) === 'height=') { + command.height = parseInt(tokens[i].substring(7)); + } + else if (tokens[i].substring(0, 5) === 'part=') { + command.part = parseInt(tokens[i].substring(5)); + } + else if (tokens[i].substring(0, 6) === 'parts=') { + command.parts = parseInt(tokens[i].substring(6)); + } + else if (tokens[i].substring(0, 8) === 'current=') { + command.currentPart = parseInt(tokens[i].substring(8)); + } + } + return command; + }; + + before(function () { + if (docPath === 'file:///PATH') { + throw new Error('Document file path not set'); + } + else if (docPath[docPath.length - 1] !== '/') { + docPath += '/'; + } + }); + + docs.forEach(function (testDoc) { + testsRan += 1; + describe('Document #' + testsRan + ' (' + testDoc + ')', function () { + var ws; + var requestedTiles = 0; + var docWidthTwips, docHeightTwips, midY, endY; + var eventEmitter = new events.EventEmitter(); + + var onMessage = function (evt) { + var bytes, index, textMsg; + + if (typeof (evt.data) === 'string') { + textMsg = evt.data; + } + else if (typeof (evt.data) === 'object') { + bytes = new Uint8Array(evt.data); + index = 0; + // search for the first newline which marks the end of the message + while (index < bytes.length && bytes[index] !== 10) { + index++; + } + textMsg = String.fromCharCode.apply(null, bytes.subarray(0, index)); + } + + if (textMsg.startsWith('status:')) { + command = _parseServerCmd(textMsg); + docWidthTwips = command.width; + docHeightTwips = command.height; + endY = Math.floor(docHeightTwips / tileSizeTwips); + midY = Math.floor(endY / 2); + eventEmitter.emit('status'); + } + else if (textMsg.startsWith('tile:')) { + requestedTiles -= 1; + if (requestedTiles <= 0) { + eventEmitter.emit('alltilesloaded'); + } + } + else if (textMsg.startsWith('error:')) { + console.log(textMsg); + throw new Error(textMsg); + } + }; + + var requestTiles = function (x, y) { + requestedTiles += 1; + ws.send('tile ' + + 'part=0 ' + + 'width=' + tileSize + ' ' + + 'height=' + tileSize + ' ' + + 'tileposx=' + x * tileSizeTwips + ' ' + + 'tileposy=' + y * tileSizeTwips + ' ' + + 'tilewidth=' + tileSizeTwips + ' ' + + 'tileheight=' + tileSizeTwips); + }; + + var isValidTile = function (x, y) { + return x >= 0 && y >= 0 && (x * tileSizeTwips < docWidthTwips) && (y * tileSizeTwips < docHeightTwips); + }; + + after(function () { + ws.onmessage = function () {}; + ws.close(); + }); + + it('Connect to the server', function (done) { + eventEmitter.once('status', done); + ws = new WebSocket(host); + ws.onmessage = onMessage; + ws.onerror = function (e) {console.log(e)}; + ws.binaryType = 'arraybuffer'; + ws.onopen = function () { + ws.send('load url=' + docPath + testDoc); + ws.send('status'); + }; + }); + + it('Load the document', function (done) { + eventEmitter.once('alltilesloaded', done); + for (var i = 0; i < 3; i++) { + for (j = 0; j < 5; j++) { + if (isValidTile(j, i)) { + requestTiles(j, i); + } + } + } + }); + + it('Scroll to the middle', function (done) { + eventEmitter.once('alltilesloaded', done); + for (var i = midY; i < midY + 3; i++) { + for (j = 0; j < 5; j++) { + if (isValidTile(j, i)) { + requestTiles(j, i); + } + } + } + }); + + it('Scroll to the end', function (done) { + eventEmitter.once('alltilesloaded', done); + for (var i = endY; i > endY - 3; i--) { + for (j = 0; j < 5; j++) { + if (isValidTile(j, i)) { + requestTiles(j, i); + } + } + } + }); + }); + }); +}); commit 6dc45b5825316ba9aeb1389acd9580fab7b4ad66 Author: Mihai Varga <[email protected]> Date: Tue Aug 18 13:52:00 2015 +0300 loleaflet: load test check if doc path is set diff --git a/loleaflet/spec/loadtest/LoadTestSpec.js b/loleaflet/spec/loadtest/LoadTestSpec.js index e04350a..c339926 100644 --- a/loleaflet/spec/loadtest/LoadTestSpec.js +++ b/loleaflet/spec/loadtest/LoadTestSpec.js @@ -1,6 +1,9 @@ describe('LoadTest', function () { // 25 s timeout this.timeout(25000); + // set the slow time to 5ms knowing each test takes more than that, + // so the run time is always printed + this.slow(5); var testsRan = 0, checkTimeOut = null, map = null, @@ -9,6 +12,10 @@ describe('LoadTest', function () { y = 0; before(function() { + if (docPath === 'file:///PATH') { + throw new Error('Document file path not set'); + } + map = L.map('map-test', { center: [0, 0], zoom: 10, _______________________________________________ Libreoffice-commits mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
