Browse Source

Run WebODF rendering inside sandboxed iframe.

pull/197/head
Joachim Bauch 10 years ago
parent
commit
dd6d11dd53
  1. 7
      build/build.js
  2. 24
      src/styles/components/_presentation.scss
  3. 210
      static/js/directives/odfcanvas.js
  4. 251
      static/js/sandboxes/webodf.js
  5. 39
      static/partials/odfcanvas_sandbox.html

7
build/build.js

@ -90,6 +90,13 @@ @@ -90,6 +90,13 @@
override: {
skipModuleInsertion: true
}
},
{
name: 'sandboxes/webodf',
dir: './out/sandboxes',
override: {
skipModuleInsertion: true
}
}
]
})

24
src/styles/components/_presentation.scss

@ -205,30 +205,6 @@ @@ -205,30 +205,6 @@
width: 100%;
}
}
.odfcanvas {
cursor: default;
user-select: none;
body {
background-color: transparent;
}
document {
display: block;
}
}
.odfcontainer {
display: none;
margin: 0;
padding: 0;
&.showonepage {
overflow: hidden;
text-align: center;
}
}
}
.pageinfo input {

210
static/js/directives/odfcanvas.js

@ -20,109 +20,101 @@ @@ -20,109 +20,101 @@
*/
"use strict";
define(['require', 'underscore', 'jquery'], function(require, _, $) {
define(['require', 'underscore', 'jquery', 'text!partials/odfcanvas_sandbox.html'], function(require, _, $, sandboxTemplate) {
return ["$window", "$compile", "translation", "safeApply", function($window, $compile, translation, safeApply) {
var webodf = null;
return ["$window", "$compile", "$http", "translation", "safeApply", "restURL", "sandbox", function($window, $compile, $http, translation, safeApply, restURL, sandbox) {
var DOCUMENT_TYPE_PRESENTATION = "presentation";
var DOCUMENT_TYPE_SPREADSHEET = "spreadsheet";
var DOCUMENT_TYPE_TEXT = "text";
var nsResolver = function(prefix) {
var ns = {
'draw': "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
'presentation': "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
'text': "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
'office': "urn:oasis:names:tc:opendocument:xmlns:office:1.0"
};
return ns[prefix] || console.log('prefix [' + prefix + '] unknown.');
}
var ODFCanvas_readFile = function(path, encoding, callback) {
if (typeof path === "string") {
webodf.runtime.orig_readFile.call(webodf.runtime, path, encoding, callback);
return;
}
var fp = path.file || path;
if (typeof URL !== "undefined" && URL.createObjectURL) {
var url = URL.createObjectURL(fp);
webodf.runtime.orig_readFile.call(webodf.runtime, url, encoding, function() {
URL.revokeObjectURL(url);
callback.apply(callback, arguments);
});
return;
}
console.error("TODO(fancycode): implement readFile for", path);
};
var ODFCanvas_loadXML = function(path, callback) {
if (typeof path === "string") {
webodf.runtime.orig_loadXML.call(webodf.runtime, path, callback);
return;
}
var fp = path.file || path;
if (typeof URL !== "undefined" && URL.createObjectURL) {
var url = URL.createObjectURL(fp);
webodf.runtime.orig_loadXML.call(webodf.runtime, url, function() {
URL.revokeObjectURL(url);
callback.apply(callback, arguments);
});
return;
}
var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
console.error("TODO(fancycode): implement loadXML for", path);
};
var container = $($element);
var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
var odfCanvas;
var template = sandboxTemplate;
template = template.replace(/__PARENT_ORIGIN__/g, $window.location.protocol + "//" + $window.location.host);
template = template.replace(/__WEBODF_SANDBOX_JS_URL__/g, restURL.createAbsoluteUrl(require.toUrl('sandboxes/webodf') + ".js"));
template = template.replace(/__WEBODF_URL__/g, restURL.createAbsoluteUrl(require.toUrl('webodf') + ".js"));
var sandboxApi = sandbox.createSandbox($("iframe", container)[0], template);
sandboxApi.e.on("message", function(event, message) {
var msg = message.data;
var data = msg[msg.type] || {};
switch (msg.type) {
case "ready":
break;
case "webodf.loading":
$scope.$apply(function(scope) {
scope.$emit("presentationLoading", data.source);
container.hide();
});
break;
case "webodf.loaded":
odfCanvas._odfLoaded(data.url, data.type, data.numPages);
break;
case "webodf.keyUp":
$scope.$apply(function(scope) {
scope.$emit("keyUp", data.key);
});
break;
default:
console.log("Unknown message received", message);
break;
}
});
var ODFCanvas = function(scope, container, canvasDom) {
var ODFCanvas = function(scope, container) {
this.scope = scope;
this.container = container;
this.canvasDom = canvasDom;
this.canvas = null;
this.doc = null;
this.maxPageNumber = -1;
this.currentPageNumber = -1;
this.pendingPageNumber = null;
};
ODFCanvas.prototype._close = function() {
if (this.canvas) {
this.canvas.destroy(function() {
// ignore callback
});
this.canvas = null;
}
ODFCanvas.prototype.close = function() {
sandboxApi.postMessage("closeFile", {"close": true});
this.maxPageNumber = -1;
this.currentPageNumber = -1;
this.pendingPageNumber = null;
};
ODFCanvas.prototype.close = function() {
this._close();
this.doc = null;
};
ODFCanvas.prototype.open = function(presentation) {
this.scope.$emit("presentationOpening", presentation);
presentation.open(_.bind(function(source) {
console.log("Loading ODF from", source);
this._openFile(source);
this.close();
if (typeof source === "string") {
// got a url
this._openFile(source);
return;
}
var fp = source.file || source;
if (typeof URL !== "undefined" && URL.createObjectURL) {
this.url = URL.createObjectURL(fp);
this._openFile(this.url);
} else {
var fileReader = new FileReader();
fileReader.onload = _.bind(function(evt) {
var buffer = evt.target.result;
var uint8Array = new Uint8Array(buffer);
this._openFile(uint8Array);
}, this);
fileReader.readAsArrayBuffer(fp);
}
}, this));
};
ODFCanvas.prototype._odfLoaded = function() {
ODFCanvas.prototype._odfLoaded = function(url, document_type, numPages) {
this.scope.$apply(_.bind(function(scope) {
var odfcontainer = this.canvas.odfContainer();
this.document_type = odfcontainer.getDocumentType();
// pages only supported for presentations
var pages = [];
switch (this.document_type) {
this.document_type = document_type;
switch (document_type) {
case DOCUMENT_TYPE_PRESENTATION:
pages = odfcontainer.rootElement.getElementsByTagNameNS(nsResolver('draw'), 'page');
this.container.addClass("showonepage");
break;
@ -131,13 +123,12 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { @@ -131,13 +123,12 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
break;
}
this.maxPageNumber = Math.max(1, pages.length);
this.maxPageNumber = numPages;
this.currentPageNumber = -1;
console.log("ODF loaded", odfcontainer);
var odfDoc = {
this.doc = {
numPages: this.maxPageNumber
};
scope.$emit("presentationLoaded", odfcontainer.getUrl(), odfDoc);
scope.$emit("presentationLoaded", url, this.doc);
if (this.pendingPageNumber !== null) {
this._showPage(this.pendingPageNumber);
this.pendingPageNumber = null;
@ -145,41 +136,19 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { @@ -145,41 +136,19 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
}, this));
};
ODFCanvas.prototype._doOpenFile = function(source) {
this.scope.$emit("presentationLoading", source);
this.container.hide();
if (!this.canvas) {
this.canvas = new webodf.odf.OdfCanvas(this.canvasDom[0]);
this.canvas.addListener("statereadychange", _.bind(function() {
this._odfLoaded();
}, this));
}
this.canvas.setZoomLevel(1);
this.canvas.load(source);
};
ODFCanvas.prototype._openFile = function(source) {
if (webodf === null) {
// load webodf.js lazily
require(['webodf'], _.bind(function(webodf_) {
console.log("Using webodf.js " + webodf_.webodf.Version);
webodf = webodf_;
// monkey-patch IO functions
webodf.runtime.orig_readFile = webodf.runtime.readFile;
webodf.runtime.readFile = ODFCanvas_readFile;
webodf.runtime.orig_loadXML = webodf.runtime.loadXML;
webodf.runtime.loadXML = ODFCanvas_loadXML;
this.scope.$apply(_.bind(function(scope) {
this._doOpenFile(source);
}, this));
if (typeof(source) === "string") {
// we can't load urls from inside the sandbox, do so here and transmit the contents
$http.get(source, {
responseType: "arraybuffer"
}).then(_.bind(function(response) {
this._openFile(response.data);
}, this));
} else {
this._doOpenFile(source);
return;
}
console.log("Opening file", source);
sandboxApi.postMessage("openFile", {"source": source});
};
ODFCanvas.prototype._showPage = function(page) {
@ -200,7 +169,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { @@ -200,7 +169,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
this.redrawPage();
}
this.currentPageNumber = page;
this.canvas.showPage(page);
sandboxApi.postMessage("showPage", {"page": page});
this.scope.$emit("presentationPageRendering", page);
this.scope.$emit("presentationPageRendered", page, this.maxPageNumber);
}, this));
@ -208,22 +177,12 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { @@ -208,22 +177,12 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
};
ODFCanvas.prototype.redrawPage = function() {
if (this.canvas) {
switch (this.document_type) {
case DOCUMENT_TYPE_PRESENTATION:
this.canvas.fitToContainingElement(this.container.width(), this.container.height());
break;
default:
this.canvas.fitToWidth(this.container.width());
break;
}
}
sandboxApi.postMessage("redrawPage", {"redraw": true});
};
ODFCanvas.prototype.showPage = function(page) {
if (page >= 1 && page <= this.maxPageNumber) {
if (!this.canvas) {
if (!this.doc) {
this.pendingPageNumber = page;
} else {
this._showPage(page);
@ -231,9 +190,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { @@ -231,9 +190,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
}
};
var container = $($element);
var canvas = $($element).find(".odfcanvas");
var odfCanvas = new ODFCanvas($scope, container, canvas);
odfCanvas = new ODFCanvas($scope, container);
$scope.$watch("currentPresentation", function(presentation, previousPresentation) {
if (presentation) {
@ -251,6 +208,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { @@ -251,6 +208,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
$scope.$on("$destroy", function() {
odfCanvas.close();
odfCanvas = null;
sandboxApi.destroy();
});
$scope.$watch("currentPageNumber", function(page, oldValue) {
@ -273,7 +231,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) { @@ -273,7 +231,7 @@ define(['require', 'underscore', 'jquery'], function(require, _, $) {
return {
restrict: 'E',
replace: true,
template: '<div class="canvasContainer odfcontainer"><div class="odfcanvas"></div></div>',
template: '<div class="canvasContainer odfcontainer"><iframe allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" sandbox="allow-scripts allow-same-origin"></iframe></div>',
controller: controller
};

251
static/js/sandboxes/webodf.js

@ -0,0 +1,251 @@ @@ -0,0 +1,251 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
"use strict";
(function () {
var DOCUMENT_TYPE_PRESENTATION = "presentation";
var DOCUMENT_TYPE_SPREADSHEET = "spreadsheet";
var DOCUMENT_TYPE_TEXT = "text";
var nsResolver = function(prefix) {
var ns = {
'draw': "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
'presentation': "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
'text': "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
'office': "urn:oasis:names:tc:opendocument:xmlns:office:1.0"
};
return ns[prefix] || console.log('prefix [' + prefix + '] unknown.');
}
var body = document.getElementsByTagName("body")[0];
var script = document.getElementsByTagName("script")[0];
var PARENT_ORIGIN = script.getAttribute("data-parent-origin");
var WEBODF_URL = script.getAttribute("data-webodf-url");
var container = document.getElementById("container");
var webodfScript = null;
var webodf = null;
var runtime = null;
var ODFCanvas_readFile = function(path, encoding, callback) {
if (typeof path === "string") {
runtime.orig_readFile.call(runtime, path, encoding, callback);
return;
}
// we're loading typed arrays in the sandbox
callback(null, new Uint8Array(path));
};
var ODFCanvas_loadXML = function(path, callback) {
if (typeof path === "string") {
runtime.orig_loadXML.call(runtime, path, callback);
return;
}
// we're loading typed arrays in the sandbox
console.log("LoadXML", path);
var bb = new Blob([new Uint8Array(path)]);
var f = new FileReader();
f.onload = function(e) {
var parser = new DOMParser();
var doc = parser.parseFromString(e.target.result, "text/xml");
callback(null, doc);
};
f.readAsText(bb);
};
var WebODFSandbox = function(window) {
this.head = document.getElementsByTagName('head')[0];
this.canvasDom = document.getElementById("odfcanvas");
this.window = window;
this.canvas = null;
this.document_type = null;
};
WebODFSandbox.prototype.postMessage = function(type, message) {
var msg = {"type": type};
msg[type] = message;
this.window.parent.postMessage(msg, PARENT_ORIGIN);
};
WebODFSandbox.prototype.openFile = function(source) {
if (!webodfScript) {
var that = this;
webodfScript = document.createElement('script');
webodfScript.type = "text/javascript";
webodfScript.src = WEBODF_URL;
webodfScript.onerror = function(evt) {
that.postMessage("webodf.error", {"msgid": "loadScriptFailed"});
that.head.removeChild(webodfScript);
webodfScript = null;
};
webodfScript.onload = function(evt) {
console.log("Using webodf.js " + that.window.webodf.Version);
webodf = that.window.odf;
// monkey-patch IO functions
runtime = that.window.runtime;
runtime.orig_readFile = runtime.readFile;
runtime.readFile = ODFCanvas_readFile;
runtime.orig_loadXML = runtime.loadXML;
runtime.loadXML = ODFCanvas_loadXML;
that._doOpenFile(source);
};
this.head.appendChild(webodfScript);
} else {
this._doOpenFile(source);
}
};
WebODFSandbox.prototype.closeFile = function() {
if (this.canvas) {
this.canvas.destroy(function() {
// ignore callback
});
this.canvas = null;
}
};
WebODFSandbox.prototype._doOpenFile = function(source) {
this.postMessage("webodf.loading", {"source": source});
if (!this.canvas) {
var that = this;
this.canvas = new webodf.OdfCanvas(this.canvasDom);
this.canvas.addListener("statereadychange", function() {
that._odfLoaded();
});
}
this.canvas.setZoomLevel(1);
this.canvas.load(source);
};
WebODFSandbox.prototype._odfLoaded = function() {
var odfcontainer = this.canvas.odfContainer();
console.log("ODF loaded", odfcontainer);
this.document_type = odfcontainer.getDocumentType();
var pages = [];
switch (this.document_type) {
case DOCUMENT_TYPE_PRESENTATION:
container.className += " showonepage";
pages = odfcontainer.rootElement.getElementsByTagNameNS(nsResolver('draw'), 'page');
break;
default:
container.className = this.canvasDom.className.replace(/(?:^|\s)showonepage(?!\S)/g, "");
break;
}
var numPages = Math.max(1, pages.length);
this.postMessage("webodf.loaded", {"url": odfcontainer.getUrl(), "type": this.document_type, "numPages": numPages});
};
WebODFSandbox.prototype.showPage = function(page) {
this.canvas.showPage(page);
this.redrawPage();
};
WebODFSandbox.prototype.redrawPage = function() {
if (this.canvas) {
switch (this.document_type) {
case DOCUMENT_TYPE_PRESENTATION:
this.canvas.fitToContainingElement(container.offsetWidth, container.offsetHeight);
break;
default:
this.canvas.fitToWidth(container.offsetWidth);
break;
}
}
};
var sandbox = new WebODFSandbox(window);
window.addEventListener("message", function(event) {
if (event.origin !== PARENT_ORIGIN) {
// only accept messages from spreed-webrtc
return;
}
var msg = event.data;
var data = msg[msg.type] || {};
switch (msg.type) {
case "openFile":
sandbox.openFile(data.source);
break;
case "closeFile":
sandbox.closeFile();
break;
case "showPage":
sandbox.showPage(data.page);
break;
case "redrawPage":
sandbox.redrawPage();
break;
default:
console.log("Unknown message received", event);
break;
}
}, false);
document.addEventListener("keyup", function(event) {
sandbox.postMessage("webodf.keyUp", {"key": event.keyCode});
event.preventDefault();
});
window.addEventListener("resize", function() {
sandbox.redrawPage();
});
var toggleFullscreen = function(elem) {
var fullScreenElement = document.fullscreenElement || document.msFullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.webkitCurrentFullScreenElement;
if (fullScreenElement) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
} else {
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) {
elem.webkitRequestFullscreen();
} else if (elem.mozRequestFullScreen) {
elem.mozRequestFullScreen();
} else if (elem.msRequestFullscreen) {
elem.msRequestFullscreen();
}
}
};
container.addEventListener("dblclick", function(event) {
toggleFullscreen(container);
});
console.log("WebODF sandbox ready.");
sandbox.postMessage("ready", {"ready": true});
})();

39
static/partials/odfcanvas_sandbox.html

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>WebODF Sandbox</title>
<style type="text/css">
html, body {
height:100%;
}
body {
margin:0;
padding:0;
max-width:100%;
max-height:100%;
}
#container {
height:100%;
width:100%;
overflow-x:hidden;
overflow-y:auto;
}
#container.showonepage {
overflow:hidden;
text-align:center;
}
#odfcanvas {
cursor:default;
user-select:none;
-webkit-user-select:none;
-moz-user-select:none;
}
</style>
</head>
<body>
<div id="container">
<div id="odfcanvas"></div>
</div>
<script src="__WEBODF_SANDBOX_JS_URL__" data-parent-origin="__PARENT_ORIGIN__" data-webodf-url="__WEBODF_URL__"></script>
</body>
</html>
Loading…
Cancel
Save