shutterbug 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- })();
@@ -1,5 +0,0 @@
1
- require 'shared_examples_for_handlers'
2
-
3
- describe Shutterbug::Handlers::JsFileHandler do
4
- it_behaves_like "a request handler"
5
- end