shutterbug 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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