shutterbug 0.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +4 -86
- data/lib/shutterbug.rb +1 -1
- data/lib/shutterbug/handlers.rb +0 -1
- data/lib/shutterbug/handlers/convert_handler.rb +11 -24
- data/lib/shutterbug/phantom_job.rb +25 -7
- data/lib/shutterbug/rackapp.rb +0 -1
- data/spec/shutterbug/rackapp_spec.rb +0 -8
- metadata +2 -23
- data/bower.json +0 -26
- data/demo/The_Scream.jpg +0 -0
- data/demo/canvas_example.html +0 -36
- data/demo/iframe.html +0 -30
- data/demo/iframe2.html +0 -30
- data/demo/iframe3.html +0 -29
- data/demo/iframe4.html +0 -29
- data/demo/iframe_example.html +0 -38
- data/demo/iframe_no_shutterbug.html +0 -20
- data/demo/index.html +0 -20
- data/demo/main.css +0 -9
- data/demo/nested_iframe_example.html +0 -41
- data/demo/oil-and-water/heatbath.svg +0 -20
- data/demo/oil-and-water/ke-gradient.svg +0 -27
- data/demo/oil-and-water/oil-and-water.svg +0 -487
- data/demo/simple_example.html +0 -35
- data/demo/svg_example.html +0 -571
- data/lib/shutterbug/handlers/js_file_handler.rb +0 -30
- data/lib/shutterbug/handlers/shutterbug.js +0 -388
- data/spec/shutterbug/js_file_handler_spec.rb +0 -5
@@ -1,30 +0,0 @@
|
|
1
|
-
module Shutterbug
|
2
|
-
module Handlers
|
3
|
-
class JsFileHandler
|
4
|
-
|
5
|
-
def self.config
|
6
|
-
Configuration.instance
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.js_path
|
10
|
-
"#{self.config.url_prefix}/shutterbug.js"
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.regex
|
14
|
-
/#{self.config.path_prefix}\/shutterbug.js/
|
15
|
-
end
|
16
|
-
|
17
|
-
def initialize
|
18
|
-
@javascript = File.read(js_file).gsub(/URL_PREFIX/, self.class.config.url_prefix)
|
19
|
-
end
|
20
|
-
|
21
|
-
def js_file
|
22
|
-
File.join(File.dirname(__FILE__), "shutterbug.js")
|
23
|
-
end
|
24
|
-
|
25
|
-
def handle(helper, req, env)
|
26
|
-
helper.response(@javascript, 'application/javascript')
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,388 +0,0 @@
|
|
1
|
-
/*global $ */
|
2
|
-
(function() {
|
3
|
-
var $ = window.jQuery;
|
4
|
-
|
5
|
-
var MAX_TIMEOUT = 1500;
|
6
|
-
|
7
|
-
// IE9 doesn't implement this.
|
8
|
-
var BIN_DATA_SUPPORTED = typeof(window.Blob) === "function" && typeof(window.Uint8Array) === "function";
|
9
|
-
|
10
|
-
var getBaseUrl = function() {
|
11
|
-
var base = window.location.href;
|
12
|
-
return base;
|
13
|
-
};
|
14
|
-
|
15
|
-
var _useIframeSizeHack = false;
|
16
|
-
var useIframeSizeHack = function(b) {
|
17
|
-
_useIframeSizeHack = b;
|
18
|
-
};
|
19
|
-
|
20
|
-
var cloneDomItem = function(elem, elemTag) {
|
21
|
-
var width = elem.width();
|
22
|
-
var height = elem.height();
|
23
|
-
var returnElm = $(elemTag);
|
24
|
-
|
25
|
-
returnElm.addClass($(elem).attr("class"));
|
26
|
-
returnElm.attr("style", $(elem).attr("style"));
|
27
|
-
returnElm.css('background', $(elem).css("background"));
|
28
|
-
returnElm.attr('width', width);
|
29
|
-
returnElm.attr('height', height);
|
30
|
-
return returnElm;
|
31
|
-
};
|
32
|
-
|
33
|
-
var generateFullHtmlFromFragment = function (fragment) {
|
34
|
-
return "<!DOCTYPE html>" +
|
35
|
-
"<html>" +
|
36
|
-
"<head>" +
|
37
|
-
"<base href='" + fragment.base_url + "'>" +
|
38
|
-
"<meta content='text/html;charset=utf-8' http-equiv='Content-Type'>" +
|
39
|
-
"<title>content from " + fragment.base_url + "</title>" +
|
40
|
-
fragment.css +
|
41
|
-
"</head>" +
|
42
|
-
"<body>" +
|
43
|
-
fragment.content +
|
44
|
-
"</body>" +
|
45
|
-
"</html>";
|
46
|
-
};
|
47
|
-
|
48
|
-
var getHtmlFragment = function(callback) {
|
49
|
-
var self = this;
|
50
|
-
var $element = $(this.element);
|
51
|
-
// .find('iframe').addBack("iframe") handles two cases:
|
52
|
-
// - element itself is an iframe - .addBack('iframe')
|
53
|
-
// - element descentands are iframes - .find('iframe')
|
54
|
-
var $iframes = $element.find('iframe').addBack("iframe");
|
55
|
-
this._iframeContentRequests = [];
|
56
|
-
|
57
|
-
$iframes.each(function(i, iframeElem) {
|
58
|
-
var message = {
|
59
|
-
type: 'htmlFragRequest',
|
60
|
-
id: self.id,
|
61
|
-
iframeReqId: i,
|
62
|
-
// We have to provide smaller timeout while sending message to nested iframes.
|
63
|
-
// Otherwise, when one of the nested iframes timeouts, then all will do the
|
64
|
-
// same and we won't render anything - even iframes that support Shutterbug.
|
65
|
-
iframeReqTimeout: self.iframeReqTimeout * 0.6
|
66
|
-
};
|
67
|
-
iframeElem.contentWindow.postMessage(JSON.stringify(message), "*");
|
68
|
-
var requestDeffered = new $.Deferred();
|
69
|
-
self._iframeContentRequests[i] = requestDeffered;
|
70
|
-
setTimeout(function() {
|
71
|
-
// It handles a situation in which iframe doesn't support Shutterbug.
|
72
|
-
// When we doesn't receive answer for some time, assume that we can't
|
73
|
-
// render this particular iframe (provide null as iframe description).
|
74
|
-
if (requestDeffered.state() !== "resolved") {
|
75
|
-
requestDeffered.resolve(null);
|
76
|
-
}
|
77
|
-
}, self.iframeReqTimeout);
|
78
|
-
});
|
79
|
-
|
80
|
-
$.when.apply($, this._iframeContentRequests).done(function() {
|
81
|
-
// This function is called when we receive responses from all nested iframes.
|
82
|
-
// Nested iframes descriptions will be provided as arguments.
|
83
|
-
$element.trigger('shutterbug-saycheese');
|
84
|
-
|
85
|
-
var css = $('<div>').append($('link[rel="stylesheet"]').clone()).append($('style').clone()).html();
|
86
|
-
var width = $element.width();
|
87
|
-
var height = $element.height();
|
88
|
-
var element = $element.clone();
|
89
|
-
|
90
|
-
// remove all script elements from the clone we don't want the html fragement
|
91
|
-
// changing itself
|
92
|
-
element.find("script").remove();
|
93
|
-
|
94
|
-
if (arguments.length > 0) {
|
95
|
-
var nestedIFrames = arguments;
|
96
|
-
// This supports two cases:
|
97
|
-
// - element itself is an iframe - .addBack('iframe')
|
98
|
-
// - element descentands are iframes - .find('iframe')
|
99
|
-
element.find("iframe").addBack("iframe").each(function(i, iframeElem) {
|
100
|
-
// When iframe doesn't support Shutterbug, request will timeout and null will be received.
|
101
|
-
// In such case just ignore this iframe, we won't be able to render it.
|
102
|
-
if (nestedIFrames[i] == null) return;
|
103
|
-
$(iframeElem).attr("src", "data:text/html," + generateFullHtmlFromFragment(nestedIFrames[i]));
|
104
|
-
});
|
105
|
-
}
|
106
|
-
|
107
|
-
// .addBack('canvas') handles case when the element itself is a canvas.
|
108
|
-
var replacementImgs = $element.find('canvas').addBack('canvas').map(function(i, elem) {
|
109
|
-
// Use png here, as it supports transparency and canvas can be layered on top of other elements.
|
110
|
-
var dataUrl = elem.toDataURL('image/png');
|
111
|
-
var img = cloneDomItem($(elem), "<img>");
|
112
|
-
img.attr('src', dataUrl);
|
113
|
-
return img;
|
114
|
-
});
|
115
|
-
|
116
|
-
if (element.is('canvas')) {
|
117
|
-
element = replacementImgs[0];
|
118
|
-
} else {
|
119
|
-
element.find('canvas').each(function(i, elem) {
|
120
|
-
$(elem).replaceWith(replacementImgs[i]);
|
121
|
-
});
|
122
|
-
}
|
123
|
-
|
124
|
-
element.css({
|
125
|
-
'top':0,
|
126
|
-
'left':0,
|
127
|
-
'margin':0,
|
128
|
-
'width':width,
|
129
|
-
'height':height
|
130
|
-
});
|
131
|
-
|
132
|
-
// Due to a weird layout bug in PhantomJS, inner iframes sometimes don't render
|
133
|
-
// unless we set the width small. This doesn't affect the actual output at all.
|
134
|
-
if (_useIframeSizeHack) {
|
135
|
-
width = 10;
|
136
|
-
}
|
137
|
-
|
138
|
-
var html_content = {
|
139
|
-
content: $('<div>').append(element).html(),
|
140
|
-
css: css,
|
141
|
-
width: width,
|
142
|
-
height: height,
|
143
|
-
base_url: getBaseUrl()
|
144
|
-
};
|
145
|
-
|
146
|
-
$element.trigger('shutterbug-asyouwere');
|
147
|
-
|
148
|
-
callback(html_content);
|
149
|
-
});
|
150
|
-
};
|
151
|
-
|
152
|
-
var getDomSnapshot = function() {
|
153
|
-
// Start timer.
|
154
|
-
var self = this;
|
155
|
-
var time = 0;
|
156
|
-
var counter = $("<span>");
|
157
|
-
counter.html(time);
|
158
|
-
$(self.imgDst).html("creating snapshot: ").append(counter);
|
159
|
-
this.timer = setInterval(function(t) {
|
160
|
-
time = time + 1;
|
161
|
-
counter.html(time);
|
162
|
-
}, 1000);
|
163
|
-
var tagName = $(this.element).prop("tagName");
|
164
|
-
switch(tagName) {
|
165
|
-
case "CANVAS":
|
166
|
-
this.canvasSnapshot();
|
167
|
-
break;
|
168
|
-
default:
|
169
|
-
this.basicSnapshot();
|
170
|
-
break;
|
171
|
-
}
|
172
|
-
};
|
173
|
-
|
174
|
-
var canvasSnapshot = function() {
|
175
|
-
if (!BIN_DATA_SUPPORTED) {
|
176
|
-
return this.basicSnapshot();
|
177
|
-
}
|
178
|
-
var self = this;
|
179
|
-
$.ajax({
|
180
|
-
type: 'GET',
|
181
|
-
url: 'URL_PREFIX/img_upload_url?format=' + this.imageFormat
|
182
|
-
}).done(function(data) {
|
183
|
-
self.directUpload(data);
|
184
|
-
}).fail(function() {
|
185
|
-
// Use basic snapshot as a fallback.
|
186
|
-
// Direct upload is not supported on server side (e.g. due to used storage).
|
187
|
-
self.basicSnapshot();
|
188
|
-
});
|
189
|
-
};
|
190
|
-
|
191
|
-
function directUpload(options) {
|
192
|
-
var $canvas = $(this.element);
|
193
|
-
var dataURL = $canvas[0].toDataURL('image/' + this.imageFormat, this.imageQuality)
|
194
|
-
var blob = dataURLtoBlob(dataURL);
|
195
|
-
var self = this;
|
196
|
-
$.ajax({
|
197
|
-
type: 'PUT',
|
198
|
-
url: options.put_url,
|
199
|
-
data: blob,
|
200
|
-
processData: false,
|
201
|
-
contentType: false
|
202
|
-
}).done(function(data) {
|
203
|
-
self.success('<img src=' + options.get_url + '>');
|
204
|
-
}).fail(function(jqXHR, textStatus, errorThrown) {
|
205
|
-
self.fail(jqXHR, textStatus, errorThrown)
|
206
|
-
});
|
207
|
-
}
|
208
|
-
|
209
|
-
function dataURLtoBlob(dataURL) {
|
210
|
-
// Convert base64/URLEncoded data component to raw binary data held in a string.
|
211
|
-
if (dataURL.split(',')[0].indexOf('base64') === -1) {
|
212
|
-
throw new Error('expected base64 data');
|
213
|
-
}
|
214
|
-
var byteString = atob(dataURL.split(',')[1]);
|
215
|
-
// Separate out the mime component.
|
216
|
-
var mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0];
|
217
|
-
// Write the bytes of the string to a typed array.
|
218
|
-
var ia = new Uint8Array(byteString.length);
|
219
|
-
for (var i = 0; i < byteString.length; i++) {
|
220
|
-
ia[i] = byteString.charCodeAt(i);
|
221
|
-
}
|
222
|
-
return new Blob([ia], {type: mimeString});
|
223
|
-
}
|
224
|
-
|
225
|
-
var basicSnapshot = function() {
|
226
|
-
var self = this;
|
227
|
-
// Ask for HTML fragment and render it on server.
|
228
|
-
this.getHtmlFragment(function(html_data) {
|
229
|
-
html_data.format = self.imageFormat;
|
230
|
-
html_data.quality = self.imageQuality;
|
231
|
-
$.ajax({
|
232
|
-
url: "URL_PREFIX/make_snapshot",
|
233
|
-
type: "POST",
|
234
|
-
data: html_data
|
235
|
-
}).success(function(msg) {
|
236
|
-
self.success(msg)
|
237
|
-
}).fail(function(jqXHR, textStatus, errorThrown) {
|
238
|
-
self.fail(jqXHR, textStatus, errorThrown);
|
239
|
-
});
|
240
|
-
});
|
241
|
-
};
|
242
|
-
|
243
|
-
var success = function(imageTag) {
|
244
|
-
if (this.imgDst) {
|
245
|
-
$(this.imgDst).html(imageTag);
|
246
|
-
}
|
247
|
-
if (this.callback) {
|
248
|
-
this.callback(imageTag);
|
249
|
-
}
|
250
|
-
clearInterval(this.timer);
|
251
|
-
}
|
252
|
-
|
253
|
-
var fail = function(jqXHR, textStatus, errorThrown) {
|
254
|
-
if (this.imgDst) {
|
255
|
-
$(this.imgDst).html("snapshot failed");
|
256
|
-
}
|
257
|
-
if (this.failCallback) {
|
258
|
-
this.failCallback(jqXHR, textStatus, errorThrown);
|
259
|
-
}
|
260
|
-
clearInterval(this.timer);
|
261
|
-
}
|
262
|
-
|
263
|
-
var requestHtmlFrag = function() {
|
264
|
-
var destination = $(this.element)[0].contentWindow;
|
265
|
-
var message = {
|
266
|
-
type: 'htmlFragRequest',
|
267
|
-
id: this.id
|
268
|
-
};
|
269
|
-
destination.postMessage(JSON.stringify(message), "*");
|
270
|
-
};
|
271
|
-
|
272
|
-
var htmlSnap = function() {
|
273
|
-
this.getHtmlFragment(function callback(fragment) {
|
274
|
-
// FIXME btoa is not intended to encode text it is for for 8bit per char strings
|
275
|
-
// so if you send it a UTF8 string with a special char in it, it will fail
|
276
|
-
// this SO has a note about handling this:
|
277
|
-
// http://stackoverflow.com/questions/246801/how-can-you-encode-a-string-to-base64-in-javascript
|
278
|
-
// also note that btoa is only available in IE10+
|
279
|
-
var encodedContent = btoa(generateFullHtmlFromFragment(fragment));
|
280
|
-
window.open("data:text/html;base64," + encodedContent);
|
281
|
-
});
|
282
|
-
};
|
283
|
-
|
284
|
-
var imageSnap = function() {
|
285
|
-
var oldImgDst = this.imgDst,
|
286
|
-
oldCallback = this.callback,
|
287
|
-
self = this;
|
288
|
-
this.imgDst = null;
|
289
|
-
this.callback = function (msg){
|
290
|
-
// extract the url out of the returned html fragment
|
291
|
-
var imgUrl = msg.match(/src='([^']*)'/)[1]
|
292
|
-
window.open(imgUrl);
|
293
|
-
self.imgDst = oldImgDst;
|
294
|
-
self.callback = oldCallback;
|
295
|
-
}
|
296
|
-
this.getDomSnapshot();
|
297
|
-
};
|
298
|
-
|
299
|
-
var setFailureCallback = function(failCallback) {
|
300
|
-
this.failCallback = failCallback;
|
301
|
-
};
|
302
|
-
|
303
|
-
// TODO: Construct using opts instead of positional arguments.
|
304
|
-
window.Shutterbug = function(selector, imgDst, callback, id, jQuery, opt) {
|
305
|
-
if (typeof(jQuery) != "undefined" && jQuery != null) {
|
306
|
-
$ = jQuery;
|
307
|
-
}
|
308
|
-
// If we still don't have a valid jQuery, try setting it from the global jQuery default.
|
309
|
-
// This can happen if shutterbug.js is included before jquery.js
|
310
|
-
if ((typeof($) == "undefined" || $ == null) && typeof(window.$) != "undefined" && window.$ != null) {
|
311
|
-
$ = window.$;
|
312
|
-
}
|
313
|
-
|
314
|
-
opt = opt || {};
|
315
|
-
|
316
|
-
var shutterbugInstance = {
|
317
|
-
element: selector,
|
318
|
-
imgDst: imgDst,
|
319
|
-
callback: callback,
|
320
|
-
id: id,
|
321
|
-
imageFormat: opt.format || "png",
|
322
|
-
imageQuality: opt.quality || 1,
|
323
|
-
getDomSnapshot: getDomSnapshot,
|
324
|
-
basicSnapshot: basicSnapshot,
|
325
|
-
canvasSnapshot: canvasSnapshot,
|
326
|
-
directUpload: directUpload,
|
327
|
-
success: success,
|
328
|
-
fail: fail,
|
329
|
-
getHtmlFragment: getHtmlFragment,
|
330
|
-
requestHtmlFrag: requestHtmlFrag,
|
331
|
-
htmlSnap: htmlSnap,
|
332
|
-
imageSnap: imageSnap,
|
333
|
-
useIframeSizeHack: useIframeSizeHack,
|
334
|
-
iframeReqTimeout: MAX_TIMEOUT,
|
335
|
-
setFailureCallback: setFailureCallback
|
336
|
-
};
|
337
|
-
|
338
|
-
var handleMessage = function(message, signature, func) {
|
339
|
-
var data = message.data;
|
340
|
-
if (typeof data === 'string') {
|
341
|
-
try {
|
342
|
-
data = JSON.parse(data);
|
343
|
-
if (data.type === signature) {
|
344
|
-
func(data);
|
345
|
-
}
|
346
|
-
} catch(e) {
|
347
|
-
// Not a json message. Ignore it. We only speak json.
|
348
|
-
}
|
349
|
-
}
|
350
|
-
};
|
351
|
-
|
352
|
-
var htmlFragRequestListen = function(message) {
|
353
|
-
var send_response = function(data) {
|
354
|
-
// Update timeout. When we receive a request from parent, we have to finish nested iframes
|
355
|
-
// rendering in that time. Otherwise parent rendering will timeout.
|
356
|
-
// Backward compatibility: Shutterbug v0.1.x don't send iframeReqTimeout.
|
357
|
-
shutterbugInstance.iframeReqTimeout = data.iframeReqTimeout != null ? data.iframeReqTimeout : MAX_TIMEOUT;
|
358
|
-
shutterbugInstance.getHtmlFragment(function(html) {
|
359
|
-
var response = {
|
360
|
-
type: 'htmlFragResponse',
|
361
|
-
value: html,
|
362
|
-
iframeReqId: data.iframeReqId,
|
363
|
-
id: data.id // return to sender only...
|
364
|
-
};
|
365
|
-
message.source.postMessage(JSON.stringify(response), "*");
|
366
|
-
});
|
367
|
-
};
|
368
|
-
handleMessage(message, 'htmlFragRequest', send_response);
|
369
|
-
};
|
370
|
-
|
371
|
-
var htmlFragResponseListen = function(message) {
|
372
|
-
var send_response = function(data) {
|
373
|
-
if (data.id === shutterbugInstance.id) {
|
374
|
-
// Backward compatibility: Shutterbug v0.1.x don't send iframeReqId.
|
375
|
-
var ifeameReqId = data.iframeReqId != null ? data.iframeReqId : 0;
|
376
|
-
shutterbugInstance._iframeContentRequests[ifeameReqId].resolve(data.value);
|
377
|
-
}
|
378
|
-
};
|
379
|
-
handleMessage(message, 'htmlFragResponse', send_response);
|
380
|
-
};
|
381
|
-
|
382
|
-
$(document).ready(function () {
|
383
|
-
window.addEventListener('message', htmlFragRequestListen, false);
|
384
|
-
window.addEventListener('message', htmlFragResponseListen, false);
|
385
|
-
});
|
386
|
-
return shutterbugInstance;
|
387
|
-
};
|
388
|
-
})();
|