| | 1 | ;var MXI_DEBUG = false; |
| | 2 | /** |
| | 3 | * mOxie - multi-runtime File API & XMLHttpRequest L2 Polyfill |
| | 4 | * v1.3.5 |
| | 5 | * |
| | 6 | * Copyright 2013, Moxiecode Systems AB |
| | 7 | * Released under GPL License. |
| | 8 | * |
| | 9 | * License: http://www.plupload.com/license |
| | 10 | * Contributing: http://www.plupload.com/contributing |
| | 11 | * |
| | 12 | * Date: 2016-05-15 |
| | 13 | */ |
| | 14 | /** |
| | 15 | * Compiled inline version. (Library mode) |
| | 16 | */ |
| | 17 | |
| | 18 | /** |
| | 19 | * Modified for WordPress, Silverlight and Flash runtimes support was removed. |
| | 20 | * See https://core.trac.wordpress.org/ticket/41755. |
| | 21 | */ |
| | 22 | |
| | 23 | /*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */ |
| | 24 | /*globals $code */ |
| | 25 | |
| | 26 | (function(exports, undefined) { |
| | 27 | "use strict"; |
| | 28 | |
| | 29 | var modules = {}; |
| | 30 | |
| | 31 | function require(ids, callback) { |
| | 32 | var module, defs = []; |
| | 33 | |
| | 34 | for (var i = 0; i < ids.length; ++i) { |
| | 35 | module = modules[ids[i]] || resolve(ids[i]); |
| | 36 | if (!module) { |
| | 37 | throw 'module definition dependecy not found: ' + ids[i]; |
| | 38 | } |
| | 39 | |
| | 40 | defs.push(module); |
| | 41 | } |
| | 42 | |
| | 43 | callback.apply(null, defs); |
| | 44 | } |
| | 45 | |
| | 46 | function define(id, dependencies, definition) { |
| | 47 | if (typeof id !== 'string') { |
| | 48 | throw 'invalid module definition, module id must be defined and be a string'; |
| | 49 | } |
| | 50 | |
| | 51 | if (dependencies === undefined) { |
| | 52 | throw 'invalid module definition, dependencies must be specified'; |
| | 53 | } |
| | 54 | |
| | 55 | if (definition === undefined) { |
| | 56 | throw 'invalid module definition, definition function must be specified'; |
| | 57 | } |
| | 58 | |
| | 59 | require(dependencies, function() { |
| | 60 | modules[id] = definition.apply(null, arguments); |
| | 61 | }); |
| | 62 | } |
| | 63 | |
| | 64 | function defined(id) { |
| | 65 | return !!modules[id]; |
| | 66 | } |
| | 67 | |
| | 68 | function resolve(id) { |
| | 69 | var target = exports; |
| | 70 | var fragments = id.split(/[.\/]/); |
| | 71 | |
| | 72 | for (var fi = 0; fi < fragments.length; ++fi) { |
| | 73 | if (!target[fragments[fi]]) { |
| | 74 | return; |
| | 75 | } |
| | 76 | |
| | 77 | target = target[fragments[fi]]; |
| | 78 | } |
| | 79 | |
| | 80 | return target; |
| | 81 | } |
| | 82 | |
| | 83 | function expose(ids) { |
| | 84 | for (var i = 0; i < ids.length; i++) { |
| | 85 | var target = exports; |
| | 86 | var id = ids[i]; |
| | 87 | var fragments = id.split(/[.\/]/); |
| | 88 | |
| | 89 | for (var fi = 0; fi < fragments.length - 1; ++fi) { |
| | 90 | if (target[fragments[fi]] === undefined) { |
| | 91 | target[fragments[fi]] = {}; |
| | 92 | } |
| | 93 | |
| | 94 | target = target[fragments[fi]]; |
| | 95 | } |
| | 96 | |
| | 97 | target[fragments[fragments.length - 1]] = modules[id]; |
| | 98 | } |
| | 99 | } |
| | 100 | |
| | 101 | // Included from: src/javascript/core/utils/Basic.js |
| | 102 | |
| | 103 | /** |
| | 104 | * Basic.js |
| | 105 | * |
| | 106 | * Copyright 2013, Moxiecode Systems AB |
| | 107 | * Released under GPL License. |
| | 108 | * |
| | 109 | * License: http://www.plupload.com/license |
| | 110 | * Contributing: http://www.plupload.com/contributing |
| | 111 | */ |
| | 112 | |
| | 113 | define('moxie/core/utils/Basic', [], function() { |
| | 114 | /** |
| | 115 | Gets the true type of the built-in object (better version of typeof). |
| | 116 | @author Angus Croll (http://javascriptweblog.wordpress.com/) |
| | 117 | |
| | 118 | @method typeOf |
| | 119 | @for Utils |
| | 120 | @static |
| | 121 | @param {Object} o Object to check. |
| | 122 | @return {String} Object [[Class]] |
| | 123 | */ |
| | 124 | var typeOf = function(o) { |
| | 125 | var undef; |
| | 126 | |
| | 127 | if (o === undef) { |
| | 128 | return 'undefined'; |
| | 129 | } else if (o === null) { |
| | 130 | return 'null'; |
| | 131 | } else if (o.nodeType) { |
| | 132 | return 'node'; |
| | 133 | } |
| | 134 | |
| | 135 | // the snippet below is awesome, however it fails to detect null, undefined and arguments types in IE lte 8 |
| | 136 | return ({}).toString.call(o).match(/\s([a-z|A-Z]+)/)[1].toLowerCase(); |
| | 137 | }; |
| | 138 | |
| | 139 | /** |
| | 140 | Extends the specified object with another object. |
| | 141 | |
| | 142 | @method extend |
| | 143 | @static |
| | 144 | @param {Object} target Object to extend. |
| | 145 | @param {Object} [obj]* Multiple objects to extend with. |
| | 146 | @return {Object} Same as target, the extended object. |
| | 147 | */ |
| | 148 | var extend = function(target) { |
| | 149 | var undef; |
| | 150 | |
| | 151 | each(arguments, function(arg, i) { |
| | 152 | if (i > 0) { |
| | 153 | each(arg, function(value, key) { |
| | 154 | if (value !== undef) { |
| | 155 | if (typeOf(target[key]) === typeOf(value) && !!~inArray(typeOf(value), ['array', 'object'])) { |
| | 156 | extend(target[key], value); |
| | 157 | } else { |
| | 158 | target[key] = value; |
| | 159 | } |
| | 160 | } |
| | 161 | }); |
| | 162 | } |
| | 163 | }); |
| | 164 | return target; |
| | 165 | }; |
| | 166 | |
| | 167 | /** |
| | 168 | Executes the callback function for each item in array/object. If you return false in the |
| | 169 | callback it will break the loop. |
| | 170 | |
| | 171 | @method each |
| | 172 | @static |
| | 173 | @param {Object} obj Object to iterate. |
| | 174 | @param {function} callback Callback function to execute for each item. |
| | 175 | */ |
| | 176 | var each = function(obj, callback) { |
| | 177 | var length, key, i, undef; |
| | 178 | |
| | 179 | if (obj) { |
| | 180 | if (typeOf(obj.length) === 'number') { // it might be Array, FileList or even arguments object |
| | 181 | // Loop array items |
| | 182 | for (i = 0, length = obj.length; i < length; i++) { |
| | 183 | if (callback(obj[i], i) === false) { |
| | 184 | return; |
| | 185 | } |
| | 186 | } |
| | 187 | } else if (typeOf(obj) === 'object') { |
| | 188 | // Loop object items |
| | 189 | for (key in obj) { |
| | 190 | if (obj.hasOwnProperty(key)) { |
| | 191 | if (callback(obj[key], key) === false) { |
| | 192 | return; |
| | 193 | } |
| | 194 | } |
| | 195 | } |
| | 196 | } |
| | 197 | } |
| | 198 | }; |
| | 199 | |
| | 200 | /** |
| | 201 | Checks if object is empty. |
| | 202 | |
| | 203 | @method isEmptyObj |
| | 204 | @static |
| | 205 | @param {Object} o Object to check. |
| | 206 | @return {Boolean} |
| | 207 | */ |
| | 208 | var isEmptyObj = function(obj) { |
| | 209 | var prop; |
| | 210 | |
| | 211 | if (!obj || typeOf(obj) !== 'object') { |
| | 212 | return true; |
| | 213 | } |
| | 214 | |
| | 215 | for (prop in obj) { |
| | 216 | return false; |
| | 217 | } |
| | 218 | |
| | 219 | return true; |
| | 220 | }; |
| | 221 | |
| | 222 | /** |
| | 223 | Recieve an array of functions (usually async) to call in sequence, each function |
| | 224 | receives a callback as first argument that it should call, when it completes. Finally, |
| | 225 | after everything is complete, main callback is called. Passing truthy value to the |
| | 226 | callback as a first argument will interrupt the sequence and invoke main callback |
| | 227 | immediately. |
| | 228 | |
| | 229 | @method inSeries |
| | 230 | @static |
| | 231 | @param {Array} queue Array of functions to call in sequence |
| | 232 | @param {Function} cb Main callback that is called in the end, or in case of error |
| | 233 | */ |
| | 234 | var inSeries = function(queue, cb) { |
| | 235 | var i = 0, length = queue.length; |
| | 236 | |
| | 237 | if (typeOf(cb) !== 'function') { |
| | 238 | cb = function() {}; |
| | 239 | } |
| | 240 | |
| | 241 | if (!queue || !queue.length) { |
| | 242 | cb(); |
| | 243 | } |
| | 244 | |
| | 245 | function callNext(i) { |
| | 246 | if (typeOf(queue[i]) === 'function') { |
| | 247 | queue[i](function(error) { |
| | 248 | /*jshint expr:true */ |
| | 249 | ++i < length && !error ? callNext(i) : cb(error); |
| | 250 | }); |
| | 251 | } |
| | 252 | } |
| | 253 | callNext(i); |
| | 254 | }; |
| | 255 | |
| | 256 | |
| | 257 | /** |
| | 258 | Recieve an array of functions (usually async) to call in parallel, each function |
| | 259 | receives a callback as first argument that it should call, when it completes. After |
| | 260 | everything is complete, main callback is called. Passing truthy value to the |
| | 261 | callback as a first argument will interrupt the process and invoke main callback |
| | 262 | immediately. |
| | 263 | |
| | 264 | @method inParallel |
| | 265 | @static |
| | 266 | @param {Array} queue Array of functions to call in sequence |
| | 267 | @param {Function} cb Main callback that is called in the end, or in case of error |
| | 268 | */ |
| | 269 | var inParallel = function(queue, cb) { |
| | 270 | var count = 0, num = queue.length, cbArgs = new Array(num); |
| | 271 | |
| | 272 | each(queue, function(fn, i) { |
| | 273 | fn(function(error) { |
| | 274 | if (error) { |
| | 275 | return cb(error); |
| | 276 | } |
| | 277 | |
| | 278 | var args = [].slice.call(arguments); |
| | 279 | args.shift(); // strip error - undefined or not |
| | 280 | |
| | 281 | cbArgs[i] = args; |
| | 282 | count++; |
| | 283 | |
| | 284 | if (count === num) { |
| | 285 | cbArgs.unshift(null); |
| | 286 | cb.apply(this, cbArgs); |
| | 287 | } |
| | 288 | }); |
| | 289 | }); |
| | 290 | }; |
| | 291 | |
| | 292 | |
| | 293 | /** |
| | 294 | Find an element in array and return it's index if present, otherwise return -1. |
| | 295 | |
| | 296 | @method inArray |
| | 297 | @static |
| | 298 | @param {Mixed} needle Element to find |
| | 299 | @param {Array} array |
| | 300 | @return {Int} Index of the element, or -1 if not found |
| | 301 | */ |
| | 302 | var inArray = function(needle, array) { |
| | 303 | if (array) { |
| | 304 | if (Array.prototype.indexOf) { |
| | 305 | return Array.prototype.indexOf.call(array, needle); |
| | 306 | } |
| | 307 | |
| | 308 | for (var i = 0, length = array.length; i < length; i++) { |
| | 309 | if (array[i] === needle) { |
| | 310 | return i; |
| | 311 | } |
| | 312 | } |
| | 313 | } |
| | 314 | return -1; |
| | 315 | }; |
| | 316 | |
| | 317 | |
| | 318 | /** |
| | 319 | Returns elements of first array if they are not present in second. And false - otherwise. |
| | 320 | |
| | 321 | @private |
| | 322 | @method arrayDiff |
| | 323 | @param {Array} needles |
| | 324 | @param {Array} array |
| | 325 | @return {Array|Boolean} |
| | 326 | */ |
| | 327 | var arrayDiff = function(needles, array) { |
| | 328 | var diff = []; |
| | 329 | |
| | 330 | if (typeOf(needles) !== 'array') { |
| | 331 | needles = [needles]; |
| | 332 | } |
| | 333 | |
| | 334 | if (typeOf(array) !== 'array') { |
| | 335 | array = [array]; |
| | 336 | } |
| | 337 | |
| | 338 | for (var i in needles) { |
| | 339 | if (inArray(needles[i], array) === -1) { |
| | 340 | diff.push(needles[i]); |
| | 341 | } |
| | 342 | } |
| | 343 | return diff.length ? diff : false; |
| | 344 | }; |
| | 345 | |
| | 346 | |
| | 347 | /** |
| | 348 | Find intersection of two arrays. |
| | 349 | |
| | 350 | @private |
| | 351 | @method arrayIntersect |
| | 352 | @param {Array} array1 |
| | 353 | @param {Array} array2 |
| | 354 | @return {Array} Intersection of two arrays or null if there is none |
| | 355 | */ |
| | 356 | var arrayIntersect = function(array1, array2) { |
| | 357 | var result = []; |
| | 358 | each(array1, function(item) { |
| | 359 | if (inArray(item, array2) !== -1) { |
| | 360 | result.push(item); |
| | 361 | } |
| | 362 | }); |
| | 363 | return result.length ? result : null; |
| | 364 | }; |
| | 365 | |
| | 366 | |
| | 367 | /** |
| | 368 | Forces anything into an array. |
| | 369 | |
| | 370 | @method toArray |
| | 371 | @static |
| | 372 | @param {Object} obj Object with length field. |
| | 373 | @return {Array} Array object containing all items. |
| | 374 | */ |
| | 375 | var toArray = function(obj) { |
| | 376 | var i, arr = []; |
| | 377 | |
| | 378 | for (i = 0; i < obj.length; i++) { |
| | 379 | arr[i] = obj[i]; |
| | 380 | } |
| | 381 | |
| | 382 | return arr; |
| | 383 | }; |
| | 384 | |
| | 385 | |
| | 386 | /** |
| | 387 | Generates an unique ID. The only way a user would be able to get the same ID is if the two persons |
| | 388 | at the same exact millisecond manage to get the same 5 random numbers between 0-65535; it also uses |
| | 389 | a counter so each ID is guaranteed to be unique for the given page. It is more probable for the earth |
| | 390 | to be hit with an asteroid. |
| | 391 | |
| | 392 | @method guid |
| | 393 | @static |
| | 394 | @param {String} prefix to prepend (by default 'o' will be prepended). |
| | 395 | @method guid |
| | 396 | @return {String} Virtually unique id. |
| | 397 | */ |
| | 398 | var guid = (function() { |
| | 399 | var counter = 0; |
| | 400 | |
| | 401 | return function(prefix) { |
| | 402 | var guid = new Date().getTime().toString(32), i; |
| | 403 | |
| | 404 | for (i = 0; i < 5; i++) { |
| | 405 | guid += Math.floor(Math.random() * 65535).toString(32); |
| | 406 | } |
| | 407 | |
| | 408 | return (prefix || 'o_') + guid + (counter++).toString(32); |
| | 409 | }; |
| | 410 | }()); |
| | 411 | |
| | 412 | |
| | 413 | /** |
| | 414 | Trims white spaces around the string |
| | 415 | |
| | 416 | @method trim |
| | 417 | @static |
| | 418 | @param {String} str |
| | 419 | @return {String} |
| | 420 | */ |
| | 421 | var trim = function(str) { |
| | 422 | if (!str) { |
| | 423 | return str; |
| | 424 | } |
| | 425 | return String.prototype.trim ? String.prototype.trim.call(str) : str.toString().replace(/^\s*/, '').replace(/\s*$/, ''); |
| | 426 | }; |
| | 427 | |
| | 428 | |
| | 429 | /** |
| | 430 | Parses the specified size string into a byte value. For example 10kb becomes 10240. |
| | 431 | |
| | 432 | @method parseSizeStr |
| | 433 | @static |
| | 434 | @param {String/Number} size String to parse or number to just pass through. |
| | 435 | @return {Number} Size in bytes. |
| | 436 | */ |
| | 437 | var parseSizeStr = function(size) { |
| | 438 | if (typeof(size) !== 'string') { |
| | 439 | return size; |
| | 440 | } |
| | 441 | |
| | 442 | var muls = { |
| | 443 | t: 1099511627776, |
| | 444 | g: 1073741824, |
| | 445 | m: 1048576, |
| | 446 | k: 1024 |
| | 447 | }, |
| | 448 | mul; |
| | 449 | |
| | 450 | |
| | 451 | size = /^([0-9\.]+)([tmgk]?)$/.exec(size.toLowerCase().replace(/[^0-9\.tmkg]/g, '')); |
| | 452 | mul = size[2]; |
| | 453 | size = +size[1]; |
| | 454 | |
| | 455 | if (muls.hasOwnProperty(mul)) { |
| | 456 | size *= muls[mul]; |
| | 457 | } |
| | 458 | return Math.floor(size); |
| | 459 | }; |
| | 460 | |
| | 461 | |
| | 462 | /** |
| | 463 | * Pseudo sprintf implementation - simple way to replace tokens with specified values. |
| | 464 | * |
| | 465 | * @param {String} str String with tokens |
| | 466 | * @return {String} String with replaced tokens |
| | 467 | */ |
| | 468 | var sprintf = function(str) { |
| | 469 | var args = [].slice.call(arguments, 1); |
| | 470 | |
| | 471 | return str.replace(/%[a-z]/g, function() { |
| | 472 | var value = args.shift(); |
| | 473 | return typeOf(value) !== 'undefined' ? value : ''; |
| | 474 | }); |
| | 475 | }; |
| | 476 | |
| | 477 | |
| | 478 | return { |
| | 479 | guid: guid, |
| | 480 | typeOf: typeOf, |
| | 481 | extend: extend, |
| | 482 | each: each, |
| | 483 | isEmptyObj: isEmptyObj, |
| | 484 | inSeries: inSeries, |
| | 485 | inParallel: inParallel, |
| | 486 | inArray: inArray, |
| | 487 | arrayDiff: arrayDiff, |
| | 488 | arrayIntersect: arrayIntersect, |
| | 489 | toArray: toArray, |
| | 490 | trim: trim, |
| | 491 | sprintf: sprintf, |
| | 492 | parseSizeStr: parseSizeStr |
| | 493 | }; |
| | 494 | }); |
| | 495 | |
| | 496 | // Included from: src/javascript/core/utils/Env.js |
| | 497 | |
| | 498 | /** |
| | 499 | * Env.js |
| | 500 | * |
| | 501 | * Copyright 2013, Moxiecode Systems AB |
| | 502 | * Released under GPL License. |
| | 503 | * |
| | 504 | * License: http://www.plupload.com/license |
| | 505 | * Contributing: http://www.plupload.com/contributing |
| | 506 | */ |
| | 507 | |
| | 508 | define("moxie/core/utils/Env", [ |
| | 509 | "moxie/core/utils/Basic" |
| | 510 | ], function(Basic) { |
| | 511 | |
| | 512 | /** |
| | 513 | * UAParser.js v0.7.7 |
| | 514 | * Lightweight JavaScript-based User-Agent string parser |
| | 515 | * https://github.com/faisalman/ua-parser-js |
| | 516 | * |
| | 517 | * Copyright © 2012-2015 Faisal Salman <fyzlman@gmail.com> |
| | 518 | * Dual licensed under GPLv2 & MIT |
| | 519 | */ |
| | 520 | var UAParser = (function (undefined) { |
| | 521 | |
| | 522 | ////////////// |
| | 523 | // Constants |
| | 524 | ///////////// |
| | 525 | |
| | 526 | |
| | 527 | var EMPTY = '', |
| | 528 | UNKNOWN = '?', |
| | 529 | FUNC_TYPE = 'function', |
| | 530 | UNDEF_TYPE = 'undefined', |
| | 531 | OBJ_TYPE = 'object', |
| | 532 | MAJOR = 'major', |
| | 533 | MODEL = 'model', |
| | 534 | NAME = 'name', |
| | 535 | TYPE = 'type', |
| | 536 | VENDOR = 'vendor', |
| | 537 | VERSION = 'version', |
| | 538 | ARCHITECTURE= 'architecture', |
| | 539 | CONSOLE = 'console', |
| | 540 | MOBILE = 'mobile', |
| | 541 | TABLET = 'tablet'; |
| | 542 | |
| | 543 | |
| | 544 | /////////// |
| | 545 | // Helper |
| | 546 | ////////// |
| | 547 | |
| | 548 | |
| | 549 | var util = { |
| | 550 | has : function (str1, str2) { |
| | 551 | return str2.toLowerCase().indexOf(str1.toLowerCase()) !== -1; |
| | 552 | }, |
| | 553 | lowerize : function (str) { |
| | 554 | return str.toLowerCase(); |
| | 555 | } |
| | 556 | }; |
| | 557 | |
| | 558 | |
| | 559 | /////////////// |
| | 560 | // Map helper |
| | 561 | ////////////// |
| | 562 | |
| | 563 | |
| | 564 | var mapper = { |
| | 565 | |
| | 566 | rgx : function () { |
| | 567 | |
| | 568 | // loop through all regexes maps |
| | 569 | for (var result, i = 0, j, k, p, q, matches, match, args = arguments; i < args.length; i += 2) { |
| | 570 | |
| | 571 | var regex = args[i], // even sequence (0,2,4,..) |
| | 572 | props = args[i + 1]; // odd sequence (1,3,5,..) |
| | 573 | |
| | 574 | // construct object barebones |
| | 575 | if (typeof(result) === UNDEF_TYPE) { |
| | 576 | result = {}; |
| | 577 | for (p in props) { |
| | 578 | q = props[p]; |
| | 579 | if (typeof(q) === OBJ_TYPE) { |
| | 580 | result[q[0]] = undefined; |
| | 581 | } else { |
| | 582 | result[q] = undefined; |
| | 583 | } |
| | 584 | } |
| | 585 | } |
| | 586 | |
| | 587 | // try matching uastring with regexes |
| | 588 | for (j = k = 0; j < regex.length; j++) { |
| | 589 | matches = regex[j].exec(this.getUA()); |
| | 590 | if (!!matches) { |
| | 591 | for (p = 0; p < props.length; p++) { |
| | 592 | match = matches[++k]; |
| | 593 | q = props[p]; |
| | 594 | // check if given property is actually array |
| | 595 | if (typeof(q) === OBJ_TYPE && q.length > 0) { |
| | 596 | if (q.length == 2) { |
| | 597 | if (typeof(q[1]) == FUNC_TYPE) { |
| | 598 | // assign modified match |
| | 599 | result[q[0]] = q[1].call(this, match); |
| | 600 | } else { |
| | 601 | // assign given value, ignore regex match |
| | 602 | result[q[0]] = q[1]; |
| | 603 | } |
| | 604 | } else if (q.length == 3) { |
| | 605 | // check whether function or regex |
| | 606 | if (typeof(q[1]) === FUNC_TYPE && !(q[1].exec && q[1].test)) { |
| | 607 | // call function (usually string mapper) |
| | 608 | result[q[0]] = match ? q[1].call(this, match, q[2]) : undefined; |
| | 609 | } else { |
| | 610 | // sanitize match using given regex |
| | 611 | result[q[0]] = match ? match.replace(q[1], q[2]) : undefined; |
| | 612 | } |
| | 613 | } else if (q.length == 4) { |
| | 614 | result[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined; |
| | 615 | } |
| | 616 | } else { |
| | 617 | result[q] = match ? match : undefined; |
| | 618 | } |
| | 619 | } |
| | 620 | break; |
| | 621 | } |
| | 622 | } |
| | 623 | |
| | 624 | if(!!matches) break; // break the loop immediately if match found |
| | 625 | } |
| | 626 | return result; |
| | 627 | }, |
| | 628 | |
| | 629 | str : function (str, map) { |
| | 630 | |
| | 631 | for (var i in map) { |
| | 632 | // check if array |
| | 633 | if (typeof(map[i]) === OBJ_TYPE && map[i].length > 0) { |
| | 634 | for (var j = 0; j < map[i].length; j++) { |
| | 635 | if (util.has(map[i][j], str)) { |
| | 636 | return (i === UNKNOWN) ? undefined : i; |
| | 637 | } |
| | 638 | } |
| | 639 | } else if (util.has(map[i], str)) { |
| | 640 | return (i === UNKNOWN) ? undefined : i; |
| | 641 | } |
| | 642 | } |
| | 643 | return str; |
| | 644 | } |
| | 645 | }; |
| | 646 | |
| | 647 | |
| | 648 | /////////////// |
| | 649 | // String map |
| | 650 | ////////////// |
| | 651 | |
| | 652 | |
| | 653 | var maps = { |
| | 654 | |
| | 655 | browser : { |
| | 656 | oldsafari : { |
| | 657 | major : { |
| | 658 | '1' : ['/8', '/1', '/3'], |
| | 659 | '2' : '/4', |
| | 660 | '?' : '/' |
| | 661 | }, |
| | 662 | version : { |
| | 663 | '1.0' : '/8', |
| | 664 | '1.2' : '/1', |
| | 665 | '1.3' : '/3', |
| | 666 | '2.0' : '/412', |
| | 667 | '2.0.2' : '/416', |
| | 668 | '2.0.3' : '/417', |
| | 669 | '2.0.4' : '/419', |
| | 670 | '?' : '/' |
| | 671 | } |
| | 672 | } |
| | 673 | }, |
| | 674 | |
| | 675 | device : { |
| | 676 | sprint : { |
| | 677 | model : { |
| | 678 | 'Evo Shift 4G' : '7373KT' |
| | 679 | }, |
| | 680 | vendor : { |
| | 681 | 'HTC' : 'APA', |
| | 682 | 'Sprint' : 'Sprint' |
| | 683 | } |
| | 684 | } |
| | 685 | }, |
| | 686 | |
| | 687 | os : { |
| | 688 | windows : { |
| | 689 | version : { |
| | 690 | 'ME' : '4.90', |
| | 691 | 'NT 3.11' : 'NT3.51', |
| | 692 | 'NT 4.0' : 'NT4.0', |
| | 693 | '2000' : 'NT 5.0', |
| | 694 | 'XP' : ['NT 5.1', 'NT 5.2'], |
| | 695 | 'Vista' : 'NT 6.0', |
| | 696 | '7' : 'NT 6.1', |
| | 697 | '8' : 'NT 6.2', |
| | 698 | '8.1' : 'NT 6.3', |
| | 699 | 'RT' : 'ARM' |
| | 700 | } |
| | 701 | } |
| | 702 | } |
| | 703 | }; |
| | 704 | |
| | 705 | |
| | 706 | ////////////// |
| | 707 | // Regex map |
| | 708 | ///////////// |
| | 709 | |
| | 710 | |
| | 711 | var regexes = { |
| | 712 | |
| | 713 | browser : [[ |
| | 714 | |
| | 715 | // Presto based |
| | 716 | /(opera\smini)\/([\w\.-]+)/i, // Opera Mini |
| | 717 | /(opera\s[mobiletab]+).+version\/([\w\.-]+)/i, // Opera Mobi/Tablet |
| | 718 | /(opera).+version\/([\w\.]+)/i, // Opera > 9.80 |
| | 719 | /(opera)[\/\s]+([\w\.]+)/i // Opera < 9.80 |
| | 720 | |
| | 721 | ], [NAME, VERSION], [ |
| | 722 | |
| | 723 | /\s(opr)\/([\w\.]+)/i // Opera Webkit |
| | 724 | ], [[NAME, 'Opera'], VERSION], [ |
| | 725 | |
| | 726 | // Mixed |
| | 727 | /(kindle)\/([\w\.]+)/i, // Kindle |
| | 728 | /(lunascape|maxthon|netfront|jasmine|blazer)[\/\s]?([\w\.]+)*/i, |
| | 729 | // Lunascape/Maxthon/Netfront/Jasmine/Blazer |
| | 730 | |
| | 731 | // Trident based |
| | 732 | /(avant\s|iemobile|slim|baidu)(?:browser)?[\/\s]?([\w\.]*)/i, |
| | 733 | // Avant/IEMobile/SlimBrowser/Baidu |
| | 734 | /(?:ms|\()(ie)\s([\w\.]+)/i, // Internet Explorer |
| | 735 | |
| | 736 | // Webkit/KHTML based |
| | 737 | /(rekonq)\/([\w\.]+)*/i, // Rekonq |
| | 738 | /(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi)\/([\w\.-]+)/i |
| | 739 | // Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron |
| | 740 | ], [NAME, VERSION], [ |
| | 741 | |
| | 742 | /(trident).+rv[:\s]([\w\.]+).+like\sgecko/i // IE11 |
| | 743 | ], [[NAME, 'IE'], VERSION], [ |
| | 744 | |
| | 745 | /(edge)\/((\d+)?[\w\.]+)/i // Microsoft Edge |
| | 746 | ], [NAME, VERSION], [ |
| | 747 | |
| | 748 | /(yabrowser)\/([\w\.]+)/i // Yandex |
| | 749 | ], [[NAME, 'Yandex'], VERSION], [ |
| | 750 | |
| | 751 | /(comodo_dragon)\/([\w\.]+)/i // Comodo Dragon |
| | 752 | ], [[NAME, /_/g, ' '], VERSION], [ |
| | 753 | |
| | 754 | /(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?([\w\.]+)/i, |
| | 755 | // Chrome/OmniWeb/Arora/Tizen/Nokia |
| | 756 | /(uc\s?browser|qqbrowser)[\/\s]?([\w\.]+)/i |
| | 757 | // UCBrowser/QQBrowser |
| | 758 | ], [NAME, VERSION], [ |
| | 759 | |
| | 760 | /(dolfin)\/([\w\.]+)/i // Dolphin |
| | 761 | ], [[NAME, 'Dolphin'], VERSION], [ |
| | 762 | |
| | 763 | /((?:android.+)crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS |
| | 764 | ], [[NAME, 'Chrome'], VERSION], [ |
| | 765 | |
| | 766 | /XiaoMi\/MiuiBrowser\/([\w\.]+)/i // MIUI Browser |
| | 767 | ], [VERSION, [NAME, 'MIUI Browser']], [ |
| | 768 | |
| | 769 | /android.+version\/([\w\.]+)\s+(?:mobile\s?safari|safari)/i // Android Browser |
| | 770 | ], [VERSION, [NAME, 'Android Browser']], [ |
| | 771 | |
| | 772 | /FBAV\/([\w\.]+);/i // Facebook App for iOS |
| | 773 | ], [VERSION, [NAME, 'Facebook']], [ |
| | 774 | |
| | 775 | /version\/([\w\.]+).+?mobile\/\w+\s(safari)/i // Mobile Safari |
| | 776 | ], [VERSION, [NAME, 'Mobile Safari']], [ |
| | 777 | |
| | 778 | /version\/([\w\.]+).+?(mobile\s?safari|safari)/i // Safari & Safari Mobile |
| | 779 | ], [VERSION, NAME], [ |
| | 780 | |
| | 781 | /webkit.+?(mobile\s?safari|safari)(\/[\w\.]+)/i // Safari < 3.0 |
| | 782 | ], [NAME, [VERSION, mapper.str, maps.browser.oldsafari.version]], [ |
| | 783 | |
| | 784 | /(konqueror)\/([\w\.]+)/i, // Konqueror |
| | 785 | /(webkit|khtml)\/([\w\.]+)/i |
| | 786 | ], [NAME, VERSION], [ |
| | 787 | |
| | 788 | // Gecko based |
| | 789 | /(navigator|netscape)\/([\w\.-]+)/i // Netscape |
| | 790 | ], [[NAME, 'Netscape'], VERSION], [ |
| | 791 | /(swiftfox)/i, // Swiftfox |
| | 792 | /(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?([\w\.\+]+)/i, |
| | 793 | // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror |
| | 794 | /(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix)\/([\w\.-]+)/i, |
| | 795 | // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix |
| | 796 | /(mozilla)\/([\w\.]+).+rv\:.+gecko\/\d+/i, // Mozilla |
| | 797 | |
| | 798 | // Other |
| | 799 | /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf)[\/\s]?([\w\.]+)/i, |
| | 800 | // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf |
| | 801 | /(links)\s\(([\w\.]+)/i, // Links |
| | 802 | /(gobrowser)\/?([\w\.]+)*/i, // GoBrowser |
| | 803 | /(ice\s?browser)\/v?([\w\._]+)/i, // ICE Browser |
| | 804 | /(mosaic)[\/\s]([\w\.]+)/i // Mosaic |
| | 805 | ], [NAME, VERSION] |
| | 806 | ], |
| | 807 | |
| | 808 | engine : [[ |
| | 809 | |
| | 810 | /windows.+\sedge\/([\w\.]+)/i // EdgeHTML |
| | 811 | ], [VERSION, [NAME, 'EdgeHTML']], [ |
| | 812 | |
| | 813 | /(presto)\/([\w\.]+)/i, // Presto |
| | 814 | /(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\/([\w\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m |
| | 815 | /(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i, // KHTML/Tasman/Links |
| | 816 | /(icab)[\/\s]([23]\.[\d\.]+)/i // iCab |
| | 817 | ], [NAME, VERSION], [ |
| | 818 | |
| | 819 | /rv\:([\w\.]+).*(gecko)/i // Gecko |
| | 820 | ], [VERSION, NAME] |
| | 821 | ], |
| | 822 | |
| | 823 | os : [[ |
| | 824 | |
| | 825 | // Windows based |
| | 826 | /microsoft\s(windows)\s(vista|xp)/i // Windows (iTunes) |
| | 827 | ], [NAME, VERSION], [ |
| | 828 | /(windows)\snt\s6\.2;\s(arm)/i, // Windows RT |
| | 829 | /(windows\sphone(?:\sos)*|windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i |
| | 830 | ], [NAME, [VERSION, mapper.str, maps.os.windows.version]], [ |
| | 831 | /(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i |
| | 832 | ], [[NAME, 'Windows'], [VERSION, mapper.str, maps.os.windows.version]], [ |
| | 833 | |
| | 834 | // Mobile/Embedded OS |
| | 835 | /\((bb)(10);/i // BlackBerry 10 |
| | 836 | ], [[NAME, 'BlackBerry'], VERSION], [ |
| | 837 | /(blackberry)\w*\/?([\w\.]+)*/i, // Blackberry |
| | 838 | /(tizen)[\/\s]([\w\.]+)/i, // Tizen |
| | 839 | /(android|webos|palm\os|qnx|bada|rim\stablet\sos|meego|contiki)[\/\s-]?([\w\.]+)*/i, |
| | 840 | // Android/WebOS/Palm/QNX/Bada/RIM/MeeGo/Contiki |
| | 841 | /linux;.+(sailfish);/i // Sailfish OS |
| | 842 | ], [NAME, VERSION], [ |
| | 843 | /(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]+)*/i // Symbian |
| | 844 | ], [[NAME, 'Symbian'], VERSION], [ |
| | 845 | /\((series40);/i // Series 40 |
| | 846 | ], [NAME], [ |
| | 847 | /mozilla.+\(mobile;.+gecko.+firefox/i // Firefox OS |
| | 848 | ], [[NAME, 'Firefox OS'], VERSION], [ |
| | 849 | |
| | 850 | // Console |
| | 851 | /(nintendo|playstation)\s([wids3portablevu]+)/i, // Nintendo/Playstation |
| | 852 | |
| | 853 | // GNU/Linux based |
| | 854 | /(mint)[\/\s\(]?(\w+)*/i, // Mint |
| | 855 | /(mageia|vectorlinux)[;\s]/i, // Mageia/VectorLinux |
| | 856 | /(joli|[kxln]?ubuntu|debian|[open]*suse|gentoo|arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\/\s-]?([\w\.-]+)*/i, |
| | 857 | // Joli/Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware |
| | 858 | // Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus |
| | 859 | /(hurd|linux)\s?([\w\.]+)*/i, // Hurd/Linux |
| | 860 | /(gnu)\s?([\w\.]+)*/i // GNU |
| | 861 | ], [NAME, VERSION], [ |
| | 862 | |
| | 863 | /(cros)\s[\w]+\s([\w\.]+\w)/i // Chromium OS |
| | 864 | ], [[NAME, 'Chromium OS'], VERSION],[ |
| | 865 | |
| | 866 | // Solaris |
| | 867 | /(sunos)\s?([\w\.]+\d)*/i // Solaris |
| | 868 | ], [[NAME, 'Solaris'], VERSION], [ |
| | 869 | |
| | 870 | // BSD based |
| | 871 | /\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly |
| | 872 | ], [NAME, VERSION],[ |
| | 873 | |
| | 874 | /(ip[honead]+)(?:.*os\s*([\w]+)*\slike\smac|;\sopera)/i // iOS |
| | 875 | ], [[NAME, 'iOS'], [VERSION, /_/g, '.']], [ |
| | 876 | |
| | 877 | /(mac\sos\sx)\s?([\w\s\.]+\w)*/i, |
| | 878 | /(macintosh|mac(?=_powerpc)\s)/i // Mac OS |
| | 879 | ], [[NAME, 'Mac OS'], [VERSION, /_/g, '.']], [ |
| | 880 | |
| | 881 | // Other |
| | 882 | /((?:open)?solaris)[\/\s-]?([\w\.]+)*/i, // Solaris |
| | 883 | /(haiku)\s(\w+)/i, // Haiku |
| | 884 | /(aix)\s((\d)(?=\.|\)|\s)[\w\.]*)*/i, // AIX |
| | 885 | /(plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos|openvms)/i, |
| | 886 | // Plan9/Minix/BeOS/OS2/AmigaOS/MorphOS/RISCOS/OpenVMS |
| | 887 | /(unix)\s?([\w\.]+)*/i // UNIX |
| | 888 | ], [NAME, VERSION] |
| | 889 | ] |
| | 890 | }; |
| | 891 | |
| | 892 | |
| | 893 | ///////////////// |
| | 894 | // Constructor |
| | 895 | //////////////// |
| | 896 | |
| | 897 | |
| | 898 | var UAParser = function (uastring) { |
| | 899 | |
| | 900 | var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY); |
| | 901 | |
| | 902 | this.getBrowser = function () { |
| | 903 | return mapper.rgx.apply(this, regexes.browser); |
| | 904 | }; |
| | 905 | this.getEngine = function () { |
| | 906 | return mapper.rgx.apply(this, regexes.engine); |
| | 907 | }; |
| | 908 | this.getOS = function () { |
| | 909 | return mapper.rgx.apply(this, regexes.os); |
| | 910 | }; |
| | 911 | this.getResult = function() { |
| | 912 | return { |
| | 913 | ua : this.getUA(), |
| | 914 | browser : this.getBrowser(), |
| | 915 | engine : this.getEngine(), |
| | 916 | os : this.getOS() |
| | 917 | }; |
| | 918 | }; |
| | 919 | this.getUA = function () { |
| | 920 | return ua; |
| | 921 | }; |
| | 922 | this.setUA = function (uastring) { |
| | 923 | ua = uastring; |
| | 924 | return this; |
| | 925 | }; |
| | 926 | this.setUA(ua); |
| | 927 | }; |
| | 928 | |
| | 929 | return UAParser; |
| | 930 | })(); |
| | 931 | |
| | 932 | |
| | 933 | function version_compare(v1, v2, operator) { |
| | 934 | // From: http://phpjs.org/functions |
| | 935 | // + original by: Philippe Jausions (http://pear.php.net/user/jausions) |
| | 936 | // + original by: Aidan Lister (http://aidanlister.com/) |
| | 937 | // + reimplemented by: Kankrelune (http://www.webfaktory.info/) |
| | 938 | // + improved by: Brett Zamir (http://brett-zamir.me) |
| | 939 | // + improved by: Scott Baker |
| | 940 | // + improved by: Theriault |
| | 941 | // * example 1: version_compare('8.2.5rc', '8.2.5a'); |
| | 942 | // * returns 1: 1 |
| | 943 | // * example 2: version_compare('8.2.50', '8.2.52', '<'); |
| | 944 | // * returns 2: true |
| | 945 | // * example 3: version_compare('5.3.0-dev', '5.3.0'); |
| | 946 | // * returns 3: -1 |
| | 947 | // * example 4: version_compare('4.1.0.52','4.01.0.51'); |
| | 948 | // * returns 4: 1 |
| | 949 | |
| | 950 | // Important: compare must be initialized at 0. |
| | 951 | var i = 0, |
| | 952 | x = 0, |
| | 953 | compare = 0, |
| | 954 | // vm maps textual PHP versions to negatives so they're less than 0. |
| | 955 | // PHP currently defines these as CASE-SENSITIVE. It is important to |
| | 956 | // leave these as negatives so that they can come before numerical versions |
| | 957 | // and as if no letters were there to begin with. |
| | 958 | // (1alpha is < 1 and < 1.1 but > 1dev1) |
| | 959 | // If a non-numerical value can't be mapped to this table, it receives |
| | 960 | // -7 as its value. |
| | 961 | vm = { |
| | 962 | 'dev': -6, |
| | 963 | 'alpha': -5, |
| | 964 | 'a': -5, |
| | 965 | 'beta': -4, |
| | 966 | 'b': -4, |
| | 967 | 'RC': -3, |
| | 968 | 'rc': -3, |
| | 969 | '#': -2, |
| | 970 | 'p': 1, |
| | 971 | 'pl': 1 |
| | 972 | }, |
| | 973 | // This function will be called to prepare each version argument. |
| | 974 | // It replaces every _, -, and + with a dot. |
| | 975 | // It surrounds any nonsequence of numbers/dots with dots. |
| | 976 | // It replaces sequences of dots with a single dot. |
| | 977 | // version_compare('4..0', '4.0') == 0 |
| | 978 | // Important: A string of 0 length needs to be converted into a value |
| | 979 | // even less than an unexisting value in vm (-7), hence [-8]. |
| | 980 | // It's also important to not strip spaces because of this. |
| | 981 | // version_compare('', ' ') == 1 |
| | 982 | prepVersion = function (v) { |
| | 983 | v = ('' + v).replace(/[_\-+]/g, '.'); |
| | 984 | v = v.replace(/([^.\d]+)/g, '.$1.').replace(/\.{2,}/g, '.'); |
| | 985 | return (!v.length ? [-8] : v.split('.')); |
| | 986 | }, |
| | 987 | // This converts a version component to a number. |
| | 988 | // Empty component becomes 0. |
| | 989 | // Non-numerical component becomes a negative number. |
| | 990 | // Numerical component becomes itself as an integer. |
| | 991 | numVersion = function (v) { |
| | 992 | return !v ? 0 : (isNaN(v) ? vm[v] || -7 : parseInt(v, 10)); |
| | 993 | }; |
| | 994 | |
| | 995 | v1 = prepVersion(v1); |
| | 996 | v2 = prepVersion(v2); |
| | 997 | x = Math.max(v1.length, v2.length); |
| | 998 | for (i = 0; i < x; i++) { |
| | 999 | if (v1[i] == v2[i]) { |
| | 1000 | continue; |
| | 1001 | } |
| | 1002 | v1[i] = numVersion(v1[i]); |
| | 1003 | v2[i] = numVersion(v2[i]); |
| | 1004 | if (v1[i] < v2[i]) { |
| | 1005 | compare = -1; |
| | 1006 | break; |
| | 1007 | } else if (v1[i] > v2[i]) { |
| | 1008 | compare = 1; |
| | 1009 | break; |
| | 1010 | } |
| | 1011 | } |
| | 1012 | if (!operator) { |
| | 1013 | return compare; |
| | 1014 | } |
| | 1015 | |
| | 1016 | // Important: operator is CASE-SENSITIVE. |
| | 1017 | // "No operator" seems to be treated as "<." |
| | 1018 | // Any other values seem to make the function return null. |
| | 1019 | switch (operator) { |
| | 1020 | case '>': |
| | 1021 | case 'gt': |
| | 1022 | return (compare > 0); |
| | 1023 | case '>=': |
| | 1024 | case 'ge': |
| | 1025 | return (compare >= 0); |
| | 1026 | case '<=': |
| | 1027 | case 'le': |
| | 1028 | return (compare <= 0); |
| | 1029 | case '==': |
| | 1030 | case '=': |
| | 1031 | case 'eq': |
| | 1032 | return (compare === 0); |
| | 1033 | case '<>': |
| | 1034 | case '!=': |
| | 1035 | case 'ne': |
| | 1036 | return (compare !== 0); |
| | 1037 | case '': |
| | 1038 | case '<': |
| | 1039 | case 'lt': |
| | 1040 | return (compare < 0); |
| | 1041 | default: |
| | 1042 | return null; |
| | 1043 | } |
| | 1044 | } |
| | 1045 | |
| | 1046 | |
| | 1047 | var can = (function() { |
| | 1048 | var caps = { |
| | 1049 | define_property: (function() { |
| | 1050 | /* // currently too much extra code required, not exactly worth it |
| | 1051 | try { // as of IE8, getters/setters are supported only on DOM elements |
| | 1052 | var obj = {}; |
| | 1053 | if (Object.defineProperty) { |
| | 1054 | Object.defineProperty(obj, 'prop', { |
| | 1055 | enumerable: true, |
| | 1056 | configurable: true |
| | 1057 | }); |
| | 1058 | return true; |
| | 1059 | } |
| | 1060 | } catch(ex) {} |
| | 1061 | |
| | 1062 | if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) { |
| | 1063 | return true; |
| | 1064 | }*/ |
| | 1065 | return false; |
| | 1066 | }()), |
| | 1067 | |
| | 1068 | create_canvas: (function() { |
| | 1069 | // On the S60 and BB Storm, getContext exists, but always returns undefined |
| | 1070 | // so we actually have to call getContext() to verify |
| | 1071 | // github.com/Modernizr/Modernizr/issues/issue/97/ |
| | 1072 | var el = document.createElement('canvas'); |
| | 1073 | return !!(el.getContext && el.getContext('2d')); |
| | 1074 | }()), |
| | 1075 | |
| | 1076 | return_response_type: function(responseType) { |
| | 1077 | try { |
| | 1078 | if (Basic.inArray(responseType, ['', 'text', 'document']) !== -1) { |
| | 1079 | return true; |
| | 1080 | } else if (window.XMLHttpRequest) { |
| | 1081 | var xhr = new XMLHttpRequest(); |
| | 1082 | xhr.open('get', '/'); // otherwise Gecko throws an exception |
| | 1083 | if ('responseType' in xhr) { |
| | 1084 | xhr.responseType = responseType; |
| | 1085 | // as of 23.0.1271.64, Chrome switched from throwing exception to merely logging it to the console (why? o why?) |
| | 1086 | if (xhr.responseType !== responseType) { |
| | 1087 | return false; |
| | 1088 | } |
| | 1089 | return true; |
| | 1090 | } |
| | 1091 | } |
| | 1092 | } catch (ex) {} |
| | 1093 | return false; |
| | 1094 | }, |
| | 1095 | |
| | 1096 | // ideas for this heavily come from Modernizr (http://modernizr.com/) |
| | 1097 | use_data_uri: (function() { |
| | 1098 | var du = new Image(); |
| | 1099 | |
| | 1100 | du.onload = function() { |
| | 1101 | caps.use_data_uri = (du.width === 1 && du.height === 1); |
| | 1102 | }; |
| | 1103 | |
| | 1104 | setTimeout(function() { |
| | 1105 | du.src = "data:image/gif;base64,R0lGODlhAQABAIAAAP8AAAAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="; |
| | 1106 | }, 1); |
| | 1107 | return false; |
| | 1108 | }()), |
| | 1109 | |
| | 1110 | use_data_uri_over32kb: function() { // IE8 |
| | 1111 | return caps.use_data_uri && (Env.browser !== 'IE' || Env.version >= 9); |
| | 1112 | }, |
| | 1113 | |
| | 1114 | use_data_uri_of: function(bytes) { |
| | 1115 | return (caps.use_data_uri && bytes < 33000 || caps.use_data_uri_over32kb()); |
| | 1116 | }, |
| | 1117 | |
| | 1118 | use_fileinput: function() { |
| | 1119 | if (navigator.userAgent.match(/(Android (1.0|1.1|1.5|1.6|2.0|2.1))|(Windows Phone (OS 7|8.0))|(XBLWP)|(ZuneWP)|(w(eb)?OSBrowser)|(webOS)|(Kindle\/(1.0|2.0|2.5|3.0))/)) { |
| | 1120 | return false; |
| | 1121 | } |
| | 1122 | |
| | 1123 | var el = document.createElement('input'); |
| | 1124 | el.setAttribute('type', 'file'); |
| | 1125 | return !el.disabled; |
| | 1126 | } |
| | 1127 | }; |
| | 1128 | |
| | 1129 | return function(cap) { |
| | 1130 | var args = [].slice.call(arguments); |
| | 1131 | args.shift(); // shift of cap |
| | 1132 | return Basic.typeOf(caps[cap]) === 'function' ? caps[cap].apply(this, args) : !!caps[cap]; |
| | 1133 | }; |
| | 1134 | }()); |
| | 1135 | |
| | 1136 | |
| | 1137 | var uaResult = new UAParser().getResult(); |
| | 1138 | |
| | 1139 | |
| | 1140 | var Env = { |
| | 1141 | can: can, |
| | 1142 | |
| | 1143 | uaParser: UAParser, |
| | 1144 | |
| | 1145 | browser: uaResult.browser.name, |
| | 1146 | version: uaResult.browser.version, |
| | 1147 | os: uaResult.os.name, // everybody intuitively types it in a lowercase for some reason |
| | 1148 | osVersion: uaResult.os.version, |
| | 1149 | |
| | 1150 | verComp: version_compare, |
| | 1151 | |
| | 1152 | global_event_dispatcher: "moxie.core.EventTarget.instance.dispatchEvent" |
| | 1153 | }; |
| | 1154 | |
| | 1155 | // for backward compatibility |
| | 1156 | // @deprecated Use `Env.os` instead |
| | 1157 | Env.OS = Env.os; |
| | 1158 | |
| | 1159 | if (MXI_DEBUG) { |
| | 1160 | Env.debug = { |
| | 1161 | runtime: true, |
| | 1162 | events: false |
| | 1163 | }; |
| | 1164 | |
| | 1165 | Env.log = function() { |
| | 1166 | |
| | 1167 | function logObj(data) { |
| | 1168 | // TODO: this should recursively print out the object in a pretty way |
| | 1169 | console.appendChild(document.createTextNode(data + "\n")); |
| | 1170 | } |
| | 1171 | |
| | 1172 | var data = arguments[0]; |
| | 1173 | |
| | 1174 | if (Basic.typeOf(data) === 'string') { |
| | 1175 | data = Basic.sprintf.apply(this, arguments); |
| | 1176 | } |
| | 1177 | |
| | 1178 | if (window && window.console && window.console.log) { |
| | 1179 | window.console.log(data); |
| | 1180 | } else if (document) { |
| | 1181 | var console = document.getElementById('moxie-console'); |
| | 1182 | if (!console) { |
| | 1183 | console = document.createElement('pre'); |
| | 1184 | console.id = 'moxie-console'; |
| | 1185 | //console.style.display = 'none'; |
| | 1186 | document.body.appendChild(console); |
| | 1187 | } |
| | 1188 | |
| | 1189 | if (Basic.inArray(Basic.typeOf(data), ['object', 'array']) !== -1) { |
| | 1190 | logObj(data); |
| | 1191 | } else { |
| | 1192 | console.appendChild(document.createTextNode(data + "\n")); |
| | 1193 | } |
| | 1194 | } |
| | 1195 | }; |
| | 1196 | } |
| | 1197 | |
| | 1198 | return Env; |
| | 1199 | }); |
| | 1200 | |
| | 1201 | // Included from: src/javascript/core/I18n.js |
| | 1202 | |
| | 1203 | /** |
| | 1204 | * I18n.js |
| | 1205 | * |
| | 1206 | * Copyright 2013, Moxiecode Systems AB |
| | 1207 | * Released under GPL License. |
| | 1208 | * |
| | 1209 | * License: http://www.plupload.com/license |
| | 1210 | * Contributing: http://www.plupload.com/contributing |
| | 1211 | */ |
| | 1212 | |
| | 1213 | define("moxie/core/I18n", [ |
| | 1214 | "moxie/core/utils/Basic" |
| | 1215 | ], function(Basic) { |
| | 1216 | var i18n = {}; |
| | 1217 | |
| | 1218 | return { |
| | 1219 | /** |
| | 1220 | * Extends the language pack object with new items. |
| | 1221 | * |
| | 1222 | * @param {Object} pack Language pack items to add. |
| | 1223 | * @return {Object} Extended language pack object. |
| | 1224 | */ |
| | 1225 | addI18n: function(pack) { |
| | 1226 | return Basic.extend(i18n, pack); |
| | 1227 | }, |
| | 1228 | |
| | 1229 | /** |
| | 1230 | * Translates the specified string by checking for the english string in the language pack lookup. |
| | 1231 | * |
| | 1232 | * @param {String} str String to look for. |
| | 1233 | * @return {String} Translated string or the input string if it wasn't found. |
| | 1234 | */ |
| | 1235 | translate: function(str) { |
| | 1236 | return i18n[str] || str; |
| | 1237 | }, |
| | 1238 | |
| | 1239 | /** |
| | 1240 | * Shortcut for translate function |
| | 1241 | * |
| | 1242 | * @param {String} str String to look for. |
| | 1243 | * @return {String} Translated string or the input string if it wasn't found. |
| | 1244 | */ |
| | 1245 | _: function(str) { |
| | 1246 | return this.translate(str); |
| | 1247 | }, |
| | 1248 | |
| | 1249 | /** |
| | 1250 | * Pseudo sprintf implementation - simple way to replace tokens with specified values. |
| | 1251 | * |
| | 1252 | * @param {String} str String with tokens |
| | 1253 | * @return {String} String with replaced tokens |
| | 1254 | */ |
| | 1255 | sprintf: function(str) { |
| | 1256 | var args = [].slice.call(arguments, 1); |
| | 1257 | |
| | 1258 | return str.replace(/%[a-z]/g, function() { |
| | 1259 | var value = args.shift(); |
| | 1260 | return Basic.typeOf(value) !== 'undefined' ? value : ''; |
| | 1261 | }); |
| | 1262 | } |
| | 1263 | }; |
| | 1264 | }); |
| | 1265 | |
| | 1266 | // Included from: src/javascript/core/utils/Mime.js |
| | 1267 | |
| | 1268 | /** |
| | 1269 | * Mime.js |
| | 1270 | * |
| | 1271 | * Copyright 2013, Moxiecode Systems AB |
| | 1272 | * Released under GPL License. |
| | 1273 | * |
| | 1274 | * License: http://www.plupload.com/license |
| | 1275 | * Contributing: http://www.plupload.com/contributing |
| | 1276 | */ |
| | 1277 | |
| | 1278 | define("moxie/core/utils/Mime", [ |
| | 1279 | "moxie/core/utils/Basic", |
| | 1280 | "moxie/core/I18n" |
| | 1281 | ], function(Basic, I18n) { |
| | 1282 | |
| | 1283 | var mimeData = "" + |
| | 1284 | "application/msword,doc dot," + |
| | 1285 | "application/pdf,pdf," + |
| | 1286 | "application/pgp-signature,pgp," + |
| | 1287 | "application/postscript,ps ai eps," + |
| | 1288 | "application/rtf,rtf," + |
| | 1289 | "application/vnd.ms-excel,xls xlb," + |
| | 1290 | "application/vnd.ms-powerpoint,ppt pps pot," + |
| | 1291 | "application/zip,zip," + |
| | 1292 | "application/x-shockwave-flash,swf swfl," + |
| | 1293 | "application/vnd.openxmlformats-officedocument.wordprocessingml.document,docx," + |
| | 1294 | "application/vnd.openxmlformats-officedocument.wordprocessingml.template,dotx," + |
| | 1295 | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,xlsx," + |
| | 1296 | "application/vnd.openxmlformats-officedocument.presentationml.presentation,pptx," + |
| | 1297 | "application/vnd.openxmlformats-officedocument.presentationml.template,potx," + |
| | 1298 | "application/vnd.openxmlformats-officedocument.presentationml.slideshow,ppsx," + |
| | 1299 | "application/x-javascript,js," + |
| | 1300 | "application/json,json," + |
| | 1301 | "audio/mpeg,mp3 mpga mpega mp2," + |
| | 1302 | "audio/x-wav,wav," + |
| | 1303 | "audio/x-m4a,m4a," + |
| | 1304 | "audio/ogg,oga ogg," + |
| | 1305 | "audio/aiff,aiff aif," + |
| | 1306 | "audio/flac,flac," + |
| | 1307 | "audio/aac,aac," + |
| | 1308 | "audio/ac3,ac3," + |
| | 1309 | "audio/x-ms-wma,wma," + |
| | 1310 | "image/bmp,bmp," + |
| | 1311 | "image/gif,gif," + |
| | 1312 | "image/jpeg,jpg jpeg jpe," + |
| | 1313 | "image/photoshop,psd," + |
| | 1314 | "image/png,png," + |
| | 1315 | "image/svg+xml,svg svgz," + |
| | 1316 | "image/tiff,tiff tif," + |
| | 1317 | "text/plain,asc txt text diff log," + |
| | 1318 | "text/html,htm html xhtml," + |
| | 1319 | "text/css,css," + |
| | 1320 | "text/csv,csv," + |
| | 1321 | "text/rtf,rtf," + |
| | 1322 | "video/mpeg,mpeg mpg mpe m2v," + |
| | 1323 | "video/quicktime,qt mov," + |
| | 1324 | "video/mp4,mp4," + |
| | 1325 | "video/x-m4v,m4v," + |
| | 1326 | "video/x-flv,flv," + |
| | 1327 | "video/x-ms-wmv,wmv," + |
| | 1328 | "video/avi,avi," + |
| | 1329 | "video/webm,webm," + |
| | 1330 | "video/3gpp,3gpp 3gp," + |
| | 1331 | "video/3gpp2,3g2," + |
| | 1332 | "video/vnd.rn-realvideo,rv," + |
| | 1333 | "video/ogg,ogv," + |
| | 1334 | "video/x-matroska,mkv," + |
| | 1335 | "application/vnd.oasis.opendocument.formula-template,otf," + |
| | 1336 | "application/octet-stream,exe"; |
| | 1337 | |
| | 1338 | |
| | 1339 | var Mime = { |
| | 1340 | |
| | 1341 | mimes: {}, |
| | 1342 | |
| | 1343 | extensions: {}, |
| | 1344 | |
| | 1345 | // Parses the default mime types string into a mimes and extensions lookup maps |
| | 1346 | addMimeType: function (mimeData) { |
| | 1347 | var items = mimeData.split(/,/), i, ii, ext; |
| | 1348 | |
| | 1349 | for (i = 0; i < items.length; i += 2) { |
| | 1350 | ext = items[i + 1].split(/ /); |
| | 1351 | |
| | 1352 | // extension to mime lookup |
| | 1353 | for (ii = 0; ii < ext.length; ii++) { |
| | 1354 | this.mimes[ext[ii]] = items[i]; |
| | 1355 | } |
| | 1356 | // mime to extension lookup |
| | 1357 | this.extensions[items[i]] = ext; |
| | 1358 | } |
| | 1359 | }, |
| | 1360 | |
| | 1361 | |
| | 1362 | extList2mimes: function (filters, addMissingExtensions) { |
| | 1363 | var self = this, ext, i, ii, type, mimes = []; |
| | 1364 | |
| | 1365 | // convert extensions to mime types list |
| | 1366 | for (i = 0; i < filters.length; i++) { |
| | 1367 | ext = filters[i].extensions.split(/\s*,\s*/); |
| | 1368 | |
| | 1369 | for (ii = 0; ii < ext.length; ii++) { |
| | 1370 | |
| | 1371 | // if there's an asterisk in the list, then accept attribute is not required |
| | 1372 | if (ext[ii] === '*') { |
| | 1373 | return []; |
| | 1374 | } |
| | 1375 | |
| | 1376 | type = self.mimes[ext[ii]]; |
| | 1377 | if (type && Basic.inArray(type, mimes) === -1) { |
| | 1378 | mimes.push(type); |
| | 1379 | } |
| | 1380 | |
| | 1381 | // future browsers should filter by extension, finally |
| | 1382 | if (addMissingExtensions && /^\w+$/.test(ext[ii])) { |
| | 1383 | mimes.push('.' + ext[ii]); |
| | 1384 | } else if (!type) { |
| | 1385 | // if we have no type in our map, then accept all |
| | 1386 | return []; |
| | 1387 | } |
| | 1388 | } |
| | 1389 | } |
| | 1390 | return mimes; |
| | 1391 | }, |
| | 1392 | |
| | 1393 | |
| | 1394 | mimes2exts: function(mimes) { |
| | 1395 | var self = this, exts = []; |
| | 1396 | |
| | 1397 | Basic.each(mimes, function(mime) { |
| | 1398 | if (mime === '*') { |
| | 1399 | exts = []; |
| | 1400 | return false; |
| | 1401 | } |
| | 1402 | |
| | 1403 | // check if this thing looks like mime type |
| | 1404 | var m = mime.match(/^(\w+)\/(\*|\w+)$/); |
| | 1405 | if (m) { |
| | 1406 | if (m[2] === '*') { |
| | 1407 | // wildcard mime type detected |
| | 1408 | Basic.each(self.extensions, function(arr, mime) { |
| | 1409 | if ((new RegExp('^' + m[1] + '/')).test(mime)) { |
| | 1410 | [].push.apply(exts, self.extensions[mime]); |
| | 1411 | } |
| | 1412 | }); |
| | 1413 | } else if (self.extensions[mime]) { |
| | 1414 | [].push.apply(exts, self.extensions[mime]); |
| | 1415 | } |
| | 1416 | } |
| | 1417 | }); |
| | 1418 | return exts; |
| | 1419 | }, |
| | 1420 | |
| | 1421 | |
| | 1422 | mimes2extList: function(mimes) { |
| | 1423 | var accept = [], exts = []; |
| | 1424 | |
| | 1425 | if (Basic.typeOf(mimes) === 'string') { |
| | 1426 | mimes = Basic.trim(mimes).split(/\s*,\s*/); |
| | 1427 | } |
| | 1428 | |
| | 1429 | exts = this.mimes2exts(mimes); |
| | 1430 | |
| | 1431 | accept.push({ |
| | 1432 | title: I18n.translate('Files'), |
| | 1433 | extensions: exts.length ? exts.join(',') : '*' |
| | 1434 | }); |
| | 1435 | |
| | 1436 | // save original mimes string |
| | 1437 | accept.mimes = mimes; |
| | 1438 | |
| | 1439 | return accept; |
| | 1440 | }, |
| | 1441 | |
| | 1442 | |
| | 1443 | getFileExtension: function(fileName) { |
| | 1444 | var matches = fileName && fileName.match(/\.([^.]+)$/); |
| | 1445 | if (matches) { |
| | 1446 | return matches[1].toLowerCase(); |
| | 1447 | } |
| | 1448 | return ''; |
| | 1449 | }, |
| | 1450 | |
| | 1451 | getFileMime: function(fileName) { |
| | 1452 | return this.mimes[this.getFileExtension(fileName)] || ''; |
| | 1453 | } |
| | 1454 | }; |
| | 1455 | |
| | 1456 | Mime.addMimeType(mimeData); |
| | 1457 | |
| | 1458 | return Mime; |
| | 1459 | }); |
| | 1460 | |
| | 1461 | // Included from: src/javascript/core/utils/Dom.js |
| | 1462 | |
| | 1463 | /** |
| | 1464 | * Dom.js |
| | 1465 | * |
| | 1466 | * Copyright 2013, Moxiecode Systems AB |
| | 1467 | * Released under GPL License. |
| | 1468 | * |
| | 1469 | * License: http://www.plupload.com/license |
| | 1470 | * Contributing: http://www.plupload.com/contributing |
| | 1471 | */ |
| | 1472 | |
| | 1473 | define('moxie/core/utils/Dom', ['moxie/core/utils/Env'], function(Env) { |
| | 1474 | |
| | 1475 | /** |
| | 1476 | Get DOM Element by it's id. |
| | 1477 | |
| | 1478 | @method get |
| | 1479 | @for Utils |
| | 1480 | @param {String} id Identifier of the DOM Element |
| | 1481 | @return {DOMElement} |
| | 1482 | */ |
| | 1483 | var get = function(id) { |
| | 1484 | if (typeof id !== 'string') { |
| | 1485 | return id; |
| | 1486 | } |
| | 1487 | return document.getElementById(id); |
| | 1488 | }; |
| | 1489 | |
| | 1490 | /** |
| | 1491 | Checks if specified DOM element has specified class. |
| | 1492 | |
| | 1493 | @method hasClass |
| | 1494 | @static |
| | 1495 | @param {Object} obj DOM element like object to add handler to. |
| | 1496 | @param {String} name Class name |
| | 1497 | */ |
| | 1498 | var hasClass = function(obj, name) { |
| | 1499 | if (!obj.className) { |
| | 1500 | return false; |
| | 1501 | } |
| | 1502 | |
| | 1503 | var regExp = new RegExp("(^|\\s+)"+name+"(\\s+|$)"); |
| | 1504 | return regExp.test(obj.className); |
| | 1505 | }; |
| | 1506 | |
| | 1507 | /** |
| | 1508 | Adds specified className to specified DOM element. |
| | 1509 | |
| | 1510 | @method addClass |
| | 1511 | @static |
| | 1512 | @param {Object} obj DOM element like object to add handler to. |
| | 1513 | @param {String} name Class name |
| | 1514 | */ |
| | 1515 | var addClass = function(obj, name) { |
| | 1516 | if (!hasClass(obj, name)) { |
| | 1517 | obj.className = !obj.className ? name : obj.className.replace(/\s+$/, '') + ' ' + name; |
| | 1518 | } |
| | 1519 | }; |
| | 1520 | |
| | 1521 | /** |
| | 1522 | Removes specified className from specified DOM element. |
| | 1523 | |
| | 1524 | @method removeClass |
| | 1525 | @static |
| | 1526 | @param {Object} obj DOM element like object to add handler to. |
| | 1527 | @param {String} name Class name |
| | 1528 | */ |
| | 1529 | var removeClass = function(obj, name) { |
| | 1530 | if (obj.className) { |
| | 1531 | var regExp = new RegExp("(^|\\s+)"+name+"(\\s+|$)"); |
| | 1532 | obj.className = obj.className.replace(regExp, function($0, $1, $2) { |
| | 1533 | return $1 === ' ' && $2 === ' ' ? ' ' : ''; |
| | 1534 | }); |
| | 1535 | } |
| | 1536 | }; |
| | 1537 | |
| | 1538 | /** |
| | 1539 | Returns a given computed style of a DOM element. |
| | 1540 | |
| | 1541 | @method getStyle |
| | 1542 | @static |
| | 1543 | @param {Object} obj DOM element like object. |
| | 1544 | @param {String} name Style you want to get from the DOM element |
| | 1545 | */ |
| | 1546 | var getStyle = function(obj, name) { |
| | 1547 | if (obj.currentStyle) { |
| | 1548 | return obj.currentStyle[name]; |
| | 1549 | } else if (window.getComputedStyle) { |
| | 1550 | return window.getComputedStyle(obj, null)[name]; |
| | 1551 | } |
| | 1552 | }; |
| | 1553 | |
| | 1554 | |
| | 1555 | /** |
| | 1556 | Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields. |
| | 1557 | |
| | 1558 | @method getPos |
| | 1559 | @static |
| | 1560 | @param {Element} node HTML element or element id to get x, y position from. |
| | 1561 | @param {Element} root Optional root element to stop calculations at. |
| | 1562 | @return {object} Absolute position of the specified element object with x, y fields. |
| | 1563 | */ |
| | 1564 | var getPos = function(node, root) { |
| | 1565 | var x = 0, y = 0, parent, doc = document, nodeRect, rootRect; |
| | 1566 | |
| | 1567 | node = node; |
| | 1568 | root = root || doc.body; |
| | 1569 | |
| | 1570 | // Returns the x, y cordinate for an element on IE 6 and IE 7 |
| | 1571 | function getIEPos(node) { |
| | 1572 | var bodyElm, rect, x = 0, y = 0; |
| | 1573 | |
| | 1574 | if (node) { |
| | 1575 | rect = node.getBoundingClientRect(); |
| | 1576 | bodyElm = doc.compatMode === "CSS1Compat" ? doc.documentElement : doc.body; |
| | 1577 | x = rect.left + bodyElm.scrollLeft; |
| | 1578 | y = rect.top + bodyElm.scrollTop; |
| | 1579 | } |
| | 1580 | |
| | 1581 | return { |
| | 1582 | x : x, |
| | 1583 | y : y |
| | 1584 | }; |
| | 1585 | } |
| | 1586 | |
| | 1587 | // Use getBoundingClientRect on IE 6 and IE 7 but not on IE 8 in standards mode |
| | 1588 | if (node && node.getBoundingClientRect && Env.browser === 'IE' && (!doc.documentMode || doc.documentMode < 8)) { |
| | 1589 | nodeRect = getIEPos(node); |
| | 1590 | rootRect = getIEPos(root); |
| | 1591 | |
| | 1592 | return { |
| | 1593 | x : nodeRect.x - rootRect.x, |
| | 1594 | y : nodeRect.y - rootRect.y |
| | 1595 | }; |
| | 1596 | } |
| | 1597 | |
| | 1598 | parent = node; |
| | 1599 | while (parent && parent != root && parent.nodeType) { |
| | 1600 | x += parent.offsetLeft || 0; |
| | 1601 | y += parent.offsetTop || 0; |
| | 1602 | parent = parent.offsetParent; |
| | 1603 | } |
| | 1604 | |
| | 1605 | parent = node.parentNode; |
| | 1606 | while (parent && parent != root && parent.nodeType) { |
| | 1607 | x -= parent.scrollLeft || 0; |
| | 1608 | y -= parent.scrollTop || 0; |
| | 1609 | parent = parent.parentNode; |
| | 1610 | } |
| | 1611 | |
| | 1612 | return { |
| | 1613 | x : x, |
| | 1614 | y : y |
| | 1615 | }; |
| | 1616 | }; |
| | 1617 | |
| | 1618 | /** |
| | 1619 | Returns the size of the specified node in pixels. |
| | 1620 | |
| | 1621 | @method getSize |
| | 1622 | @static |
| | 1623 | @param {Node} node Node to get the size of. |
| | 1624 | @return {Object} Object with a w and h property. |
| | 1625 | */ |
| | 1626 | var getSize = function(node) { |
| | 1627 | return { |
| | 1628 | w : node.offsetWidth || node.clientWidth, |
| | 1629 | h : node.offsetHeight || node.clientHeight |
| | 1630 | }; |
| | 1631 | }; |
| | 1632 | |
| | 1633 | return { |
| | 1634 | get: get, |
| | 1635 | hasClass: hasClass, |
| | 1636 | addClass: addClass, |
| | 1637 | removeClass: removeClass, |
| | 1638 | getStyle: getStyle, |
| | 1639 | getPos: getPos, |
| | 1640 | getSize: getSize |
| | 1641 | }; |
| | 1642 | }); |
| | 1643 | |
| | 1644 | // Included from: src/javascript/core/Exceptions.js |
| | 1645 | |
| | 1646 | /** |
| | 1647 | * Exceptions.js |
| | 1648 | * |
| | 1649 | * Copyright 2013, Moxiecode Systems AB |
| | 1650 | * Released under GPL License. |
| | 1651 | * |
| | 1652 | * License: http://www.plupload.com/license |
| | 1653 | * Contributing: http://www.plupload.com/contributing |
| | 1654 | */ |
| | 1655 | |
| | 1656 | define('moxie/core/Exceptions', [ |
| | 1657 | 'moxie/core/utils/Basic' |
| | 1658 | ], function(Basic) { |
| | 1659 | function _findKey(obj, value) { |
| | 1660 | var key; |
| | 1661 | for (key in obj) { |
| | 1662 | if (obj[key] === value) { |
| | 1663 | return key; |
| | 1664 | } |
| | 1665 | } |
| | 1666 | return null; |
| | 1667 | } |
| | 1668 | |
| | 1669 | return { |
| | 1670 | RuntimeError: (function() { |
| | 1671 | var namecodes = { |
| | 1672 | NOT_INIT_ERR: 1, |
| | 1673 | NOT_SUPPORTED_ERR: 9, |
| | 1674 | JS_ERR: 4 |
| | 1675 | }; |
| | 1676 | |
| | 1677 | function RuntimeError(code) { |
| | 1678 | this.code = code; |
| | 1679 | this.name = _findKey(namecodes, code); |
| | 1680 | this.message = this.name + ": RuntimeError " + this.code; |
| | 1681 | } |
| | 1682 | |
| | 1683 | Basic.extend(RuntimeError, namecodes); |
| | 1684 | RuntimeError.prototype = Error.prototype; |
| | 1685 | return RuntimeError; |
| | 1686 | }()), |
| | 1687 | |
| | 1688 | OperationNotAllowedException: (function() { |
| | 1689 | |
| | 1690 | function OperationNotAllowedException(code) { |
| | 1691 | this.code = code; |
| | 1692 | this.name = 'OperationNotAllowedException'; |
| | 1693 | } |
| | 1694 | |
| | 1695 | Basic.extend(OperationNotAllowedException, { |
| | 1696 | NOT_ALLOWED_ERR: 1 |
| | 1697 | }); |
| | 1698 | |
| | 1699 | OperationNotAllowedException.prototype = Error.prototype; |
| | 1700 | |
| | 1701 | return OperationNotAllowedException; |
| | 1702 | }()), |
| | 1703 | |
| | 1704 | ImageError: (function() { |
| | 1705 | var namecodes = { |
| | 1706 | WRONG_FORMAT: 1, |
| | 1707 | MAX_RESOLUTION_ERR: 2, |
| | 1708 | INVALID_META_ERR: 3 |
| | 1709 | }; |
| | 1710 | |
| | 1711 | function ImageError(code) { |
| | 1712 | this.code = code; |
| | 1713 | this.name = _findKey(namecodes, code); |
| | 1714 | this.message = this.name + ": ImageError " + this.code; |
| | 1715 | } |
| | 1716 | |
| | 1717 | Basic.extend(ImageError, namecodes); |
| | 1718 | ImageError.prototype = Error.prototype; |
| | 1719 | |
| | 1720 | return ImageError; |
| | 1721 | }()), |
| | 1722 | |
| | 1723 | FileException: (function() { |
| | 1724 | var namecodes = { |
| | 1725 | NOT_FOUND_ERR: 1, |
| | 1726 | SECURITY_ERR: 2, |
| | 1727 | ABORT_ERR: 3, |
| | 1728 | NOT_READABLE_ERR: 4, |
| | 1729 | ENCODING_ERR: 5, |
| | 1730 | NO_MODIFICATION_ALLOWED_ERR: 6, |
| | 1731 | INVALID_STATE_ERR: 7, |
| | 1732 | SYNTAX_ERR: 8 |
| | 1733 | }; |
| | 1734 | |
| | 1735 | function FileException(code) { |
| | 1736 | this.code = code; |
| | 1737 | this.name = _findKey(namecodes, code); |
| | 1738 | this.message = this.name + ": FileException " + this.code; |
| | 1739 | } |
| | 1740 | |
| | 1741 | Basic.extend(FileException, namecodes); |
| | 1742 | FileException.prototype = Error.prototype; |
| | 1743 | return FileException; |
| | 1744 | }()), |
| | 1745 | |
| | 1746 | DOMException: (function() { |
| | 1747 | var namecodes = { |
| | 1748 | INDEX_SIZE_ERR: 1, |
| | 1749 | DOMSTRING_SIZE_ERR: 2, |
| | 1750 | HIERARCHY_REQUEST_ERR: 3, |
| | 1751 | WRONG_DOCUMENT_ERR: 4, |
| | 1752 | INVALID_CHARACTER_ERR: 5, |
| | 1753 | NO_DATA_ALLOWED_ERR: 6, |
| | 1754 | NO_MODIFICATION_ALLOWED_ERR: 7, |
| | 1755 | NOT_FOUND_ERR: 8, |
| | 1756 | NOT_SUPPORTED_ERR: 9, |
| | 1757 | INUSE_ATTRIBUTE_ERR: 10, |
| | 1758 | INVALID_STATE_ERR: 11, |
| | 1759 | SYNTAX_ERR: 12, |
| | 1760 | INVALID_MODIFICATION_ERR: 13, |
| | 1761 | NAMESPACE_ERR: 14, |
| | 1762 | INVALID_ACCESS_ERR: 15, |
| | 1763 | VALIDATION_ERR: 16, |
| | 1764 | TYPE_MISMATCH_ERR: 17, |
| | 1765 | SECURITY_ERR: 18, |
| | 1766 | NETWORK_ERR: 19, |
| | 1767 | ABORT_ERR: 20, |
| | 1768 | URL_MISMATCH_ERR: 21, |
| | 1769 | QUOTA_EXCEEDED_ERR: 22, |
| | 1770 | TIMEOUT_ERR: 23, |
| | 1771 | INVALID_NODE_TYPE_ERR: 24, |
| | 1772 | DATA_CLONE_ERR: 25 |
| | 1773 | }; |
| | 1774 | |
| | 1775 | function DOMException(code) { |
| | 1776 | this.code = code; |
| | 1777 | this.name = _findKey(namecodes, code); |
| | 1778 | this.message = this.name + ": DOMException " + this.code; |
| | 1779 | } |
| | 1780 | |
| | 1781 | Basic.extend(DOMException, namecodes); |
| | 1782 | DOMException.prototype = Error.prototype; |
| | 1783 | return DOMException; |
| | 1784 | }()), |
| | 1785 | |
| | 1786 | EventException: (function() { |
| | 1787 | function EventException(code) { |
| | 1788 | this.code = code; |
| | 1789 | this.name = 'EventException'; |
| | 1790 | } |
| | 1791 | |
| | 1792 | Basic.extend(EventException, { |
| | 1793 | UNSPECIFIED_EVENT_TYPE_ERR: 0 |
| | 1794 | }); |
| | 1795 | |
| | 1796 | EventException.prototype = Error.prototype; |
| | 1797 | |
| | 1798 | return EventException; |
| | 1799 | }()) |
| | 1800 | }; |
| | 1801 | }); |
| | 1802 | |
| | 1803 | // Included from: src/javascript/core/EventTarget.js |
| | 1804 | |
| | 1805 | /** |
| | 1806 | * EventTarget.js |
| | 1807 | * |
| | 1808 | * Copyright 2013, Moxiecode Systems AB |
| | 1809 | * Released under GPL License. |
| | 1810 | * |
| | 1811 | * License: http://www.plupload.com/license |
| | 1812 | * Contributing: http://www.plupload.com/contributing |
| | 1813 | */ |
| | 1814 | |
| | 1815 | define('moxie/core/EventTarget', [ |
| | 1816 | 'moxie/core/utils/Env', |
| | 1817 | 'moxie/core/Exceptions', |
| | 1818 | 'moxie/core/utils/Basic' |
| | 1819 | ], function(Env, x, Basic) { |
| | 1820 | /** |
| | 1821 | Parent object for all event dispatching components and objects |
| | 1822 | |
| | 1823 | @class EventTarget |
| | 1824 | @constructor EventTarget |
| | 1825 | */ |
| | 1826 | function EventTarget() { |
| | 1827 | // hash of event listeners by object uid |
| | 1828 | var eventpool = {}; |
| | 1829 | |
| | 1830 | Basic.extend(this, { |
| | 1831 | |
| | 1832 | /** |
| | 1833 | Unique id of the event dispatcher, usually overriden by children |
| | 1834 | |
| | 1835 | @property uid |
| | 1836 | @type String |
| | 1837 | */ |
| | 1838 | uid: null, |
| | 1839 | |
| | 1840 | /** |
| | 1841 | Can be called from within a child in order to acquire uniqie id in automated manner |
| | 1842 | |
| | 1843 | @method init |
| | 1844 | */ |
| | 1845 | init: function() { |
| | 1846 | if (!this.uid) { |
| | 1847 | this.uid = Basic.guid('uid_'); |
| | 1848 | } |
| | 1849 | }, |
| | 1850 | |
| | 1851 | /** |
| | 1852 | Register a handler to a specific event dispatched by the object |
| | 1853 | |
| | 1854 | @method addEventListener |
| | 1855 | @param {String} type Type or basically a name of the event to subscribe to |
| | 1856 | @param {Function} fn Callback function that will be called when event happens |
| | 1857 | @param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first |
| | 1858 | @param {Object} [scope=this] A scope to invoke event handler in |
| | 1859 | */ |
| | 1860 | addEventListener: function(type, fn, priority, scope) { |
| | 1861 | var self = this, list; |
| | 1862 | |
| | 1863 | // without uid no event handlers can be added, so make sure we got one |
| | 1864 | if (!this.hasOwnProperty('uid')) { |
| | 1865 | this.uid = Basic.guid('uid_'); |
| | 1866 | } |
| | 1867 | |
| | 1868 | type = Basic.trim(type); |
| | 1869 | |
| | 1870 | if (/\s/.test(type)) { |
| | 1871 | // multiple event types were passed for one handler |
| | 1872 | Basic.each(type.split(/\s+/), function(type) { |
| | 1873 | self.addEventListener(type, fn, priority, scope); |
| | 1874 | }); |
| | 1875 | return; |
| | 1876 | } |
| | 1877 | |
| | 1878 | type = type.toLowerCase(); |
| | 1879 | priority = parseInt(priority, 10) || 0; |
| | 1880 | |
| | 1881 | list = eventpool[this.uid] && eventpool[this.uid][type] || []; |
| | 1882 | list.push({fn : fn, priority : priority, scope : scope || this}); |
| | 1883 | |
| | 1884 | if (!eventpool[this.uid]) { |
| | 1885 | eventpool[this.uid] = {}; |
| | 1886 | } |
| | 1887 | eventpool[this.uid][type] = list; |
| | 1888 | }, |
| | 1889 | |
| | 1890 | /** |
| | 1891 | Check if any handlers were registered to the specified event |
| | 1892 | |
| | 1893 | @method hasEventListener |
| | 1894 | @param {String} type Type or basically a name of the event to check |
| | 1895 | @return {Mixed} Returns a handler if it was found and false, if - not |
| | 1896 | */ |
| | 1897 | hasEventListener: function(type) { |
| | 1898 | var list = type ? eventpool[this.uid] && eventpool[this.uid][type] : eventpool[this.uid]; |
| | 1899 | return list ? list : false; |
| | 1900 | }, |
| | 1901 | |
| | 1902 | /** |
| | 1903 | Unregister the handler from the event, or if former was not specified - unregister all handlers |
| | 1904 | |
| | 1905 | @method removeEventListener |
| | 1906 | @param {String} type Type or basically a name of the event |
| | 1907 | @param {Function} [fn] Handler to unregister |
| | 1908 | */ |
| | 1909 | removeEventListener: function(type, fn) { |
| | 1910 | type = type.toLowerCase(); |
| | 1911 | |
| | 1912 | var list = eventpool[this.uid] && eventpool[this.uid][type], i; |
| | 1913 | |
| | 1914 | if (list) { |
| | 1915 | if (fn) { |
| | 1916 | for (i = list.length - 1; i >= 0; i--) { |
| | 1917 | if (list[i].fn === fn) { |
| | 1918 | list.splice(i, 1); |
| | 1919 | break; |
| | 1920 | } |
| | 1921 | } |
| | 1922 | } else { |
| | 1923 | list = []; |
| | 1924 | } |
| | 1925 | |
| | 1926 | // delete event list if it has become empty |
| | 1927 | if (!list.length) { |
| | 1928 | delete eventpool[this.uid][type]; |
| | 1929 | |
| | 1930 | // and object specific entry in a hash if it has no more listeners attached |
| | 1931 | if (Basic.isEmptyObj(eventpool[this.uid])) { |
| | 1932 | delete eventpool[this.uid]; |
| | 1933 | } |
| | 1934 | } |
| | 1935 | } |
| | 1936 | }, |
| | 1937 | |
| | 1938 | /** |
| | 1939 | Remove all event handlers from the object |
| | 1940 | |
| | 1941 | @method removeAllEventListeners |
| | 1942 | */ |
| | 1943 | removeAllEventListeners: function() { |
| | 1944 | if (eventpool[this.uid]) { |
| | 1945 | delete eventpool[this.uid]; |
| | 1946 | } |
| | 1947 | }, |
| | 1948 | |
| | 1949 | /** |
| | 1950 | Dispatch the event |
| | 1951 | |
| | 1952 | @method dispatchEvent |
| | 1953 | @param {String/Object} Type of event or event object to dispatch |
| | 1954 | @param {Mixed} [...] Variable number of arguments to be passed to a handlers |
| | 1955 | @return {Boolean} true by default and false if any handler returned false |
| | 1956 | */ |
| | 1957 | dispatchEvent: function(type) { |
| | 1958 | var uid, list, args, tmpEvt, evt = {}, result = true, undef; |
| | 1959 | |
| | 1960 | if (Basic.typeOf(type) !== 'string') { |
| | 1961 | // we can't use original object directly (because of Silverlight) |
| | 1962 | tmpEvt = type; |
| | 1963 | |
| | 1964 | if (Basic.typeOf(tmpEvt.type) === 'string') { |
| | 1965 | type = tmpEvt.type; |
| | 1966 | |
| | 1967 | if (tmpEvt.total !== undef && tmpEvt.loaded !== undef) { // progress event |
| | 1968 | evt.total = tmpEvt.total; |
| | 1969 | evt.loaded = tmpEvt.loaded; |
| | 1970 | } |
| | 1971 | evt.async = tmpEvt.async || false; |
| | 1972 | } else { |
| | 1973 | throw new x.EventException(x.EventException.UNSPECIFIED_EVENT_TYPE_ERR); |
| | 1974 | } |
| | 1975 | } |
| | 1976 | |
| | 1977 | // check if event is meant to be dispatched on an object having specific uid |
| | 1978 | if (type.indexOf('::') !== -1) { |
| | 1979 | (function(arr) { |
| | 1980 | uid = arr[0]; |
| | 1981 | type = arr[1]; |
| | 1982 | }(type.split('::'))); |
| | 1983 | } else { |
| | 1984 | uid = this.uid; |
| | 1985 | } |
| | 1986 | |
| | 1987 | type = type.toLowerCase(); |
| | 1988 | |
| | 1989 | list = eventpool[uid] && eventpool[uid][type]; |
| | 1990 | |
| | 1991 | if (list) { |
| | 1992 | // sort event list by prority |
| | 1993 | list.sort(function(a, b) { return b.priority - a.priority; }); |
| | 1994 | |
| | 1995 | args = [].slice.call(arguments); |
| | 1996 | |
| | 1997 | // first argument will be pseudo-event object |
| | 1998 | args.shift(); |
| | 1999 | evt.type = type; |
| | 2000 | args.unshift(evt); |
| | 2001 | |
| | 2002 | if (MXI_DEBUG && Env.debug.events) { |
| | 2003 | Env.log("Event '%s' fired on %u", evt.type, uid); |
| | 2004 | } |
| | 2005 | |
| | 2006 | // Dispatch event to all listeners |
| | 2007 | var queue = []; |
| | 2008 | Basic.each(list, function(handler) { |
| | 2009 | // explicitly set the target, otherwise events fired from shims do not get it |
| | 2010 | args[0].target = handler.scope; |
| | 2011 | // if event is marked as async, detach the handler |
| | 2012 | if (evt.async) { |
| | 2013 | queue.push(function(cb) { |
| | 2014 | setTimeout(function() { |
| | 2015 | cb(handler.fn.apply(handler.scope, args) === false); |
| | 2016 | }, 1); |
| | 2017 | }); |
| | 2018 | } else { |
| | 2019 | queue.push(function(cb) { |
| | 2020 | cb(handler.fn.apply(handler.scope, args) === false); // if handler returns false stop propagation |
| | 2021 | }); |
| | 2022 | } |
| | 2023 | }); |
| | 2024 | if (queue.length) { |
| | 2025 | Basic.inSeries(queue, function(err) { |
| | 2026 | result = !err; |
| | 2027 | }); |
| | 2028 | } |
| | 2029 | } |
| | 2030 | return result; |
| | 2031 | }, |
| | 2032 | |
| | 2033 | /** |
| | 2034 | Alias for addEventListener |
| | 2035 | |
| | 2036 | @method bind |
| | 2037 | @protected |
| | 2038 | */ |
| | 2039 | bind: function() { |
| | 2040 | this.addEventListener.apply(this, arguments); |
| | 2041 | }, |
| | 2042 | |
| | 2043 | /** |
| | 2044 | Alias for removeEventListener |
| | 2045 | |
| | 2046 | @method unbind |
| | 2047 | @protected |
| | 2048 | */ |
| | 2049 | unbind: function() { |
| | 2050 | this.removeEventListener.apply(this, arguments); |
| | 2051 | }, |
| | 2052 | |
| | 2053 | /** |
| | 2054 | Alias for removeAllEventListeners |
| | 2055 | |
| | 2056 | @method unbindAll |
| | 2057 | @protected |
| | 2058 | */ |
| | 2059 | unbindAll: function() { |
| | 2060 | this.removeAllEventListeners.apply(this, arguments); |
| | 2061 | }, |
| | 2062 | |
| | 2063 | /** |
| | 2064 | Alias for dispatchEvent |
| | 2065 | |
| | 2066 | @method trigger |
| | 2067 | @protected |
| | 2068 | */ |
| | 2069 | trigger: function() { |
| | 2070 | return this.dispatchEvent.apply(this, arguments); |
| | 2071 | }, |
| | 2072 | |
| | 2073 | |
| | 2074 | /** |
| | 2075 | Handle properties of on[event] type. |
| | 2076 | |
| | 2077 | @method handleEventProps |
| | 2078 | @private |
| | 2079 | */ |
| | 2080 | handleEventProps: function(dispatches) { |
| | 2081 | var self = this; |
| | 2082 | |
| | 2083 | this.bind(dispatches.join(' '), function(e) { |
| | 2084 | var prop = 'on' + e.type.toLowerCase(); |
| | 2085 | if (Basic.typeOf(this[prop]) === 'function') { |
| | 2086 | this[prop].apply(this, arguments); |
| | 2087 | } |
| | 2088 | }); |
| | 2089 | |
| | 2090 | // object must have defined event properties, even if it doesn't make use of them |
| | 2091 | Basic.each(dispatches, function(prop) { |
| | 2092 | prop = 'on' + prop.toLowerCase(prop); |
| | 2093 | if (Basic.typeOf(self[prop]) === 'undefined') { |
| | 2094 | self[prop] = null; |
| | 2095 | } |
| | 2096 | }); |
| | 2097 | } |
| | 2098 | |
| | 2099 | }); |
| | 2100 | } |
| | 2101 | |
| | 2102 | EventTarget.instance = new EventTarget(); |
| | 2103 | |
| | 2104 | return EventTarget; |
| | 2105 | }); |
| | 2106 | |
| | 2107 | // Included from: src/javascript/runtime/Runtime.js |
| | 2108 | |
| | 2109 | /** |
| | 2110 | * Runtime.js |
| | 2111 | * |
| | 2112 | * Copyright 2013, Moxiecode Systems AB |
| | 2113 | * Released under GPL License. |
| | 2114 | * |
| | 2115 | * License: http://www.plupload.com/license |
| | 2116 | * Contributing: http://www.plupload.com/contributing |
| | 2117 | */ |
| | 2118 | |
| | 2119 | define('moxie/runtime/Runtime', [ |
| | 2120 | "moxie/core/utils/Env", |
| | 2121 | "moxie/core/utils/Basic", |
| | 2122 | "moxie/core/utils/Dom", |
| | 2123 | "moxie/core/EventTarget" |
| | 2124 | ], function(Env, Basic, Dom, EventTarget) { |
| | 2125 | var runtimeConstructors = {}, runtimes = {}; |
| | 2126 | |
| | 2127 | /** |
| | 2128 | Common set of methods and properties for every runtime instance |
| | 2129 | |
| | 2130 | @class Runtime |
| | 2131 | |
| | 2132 | @param {Object} options |
| | 2133 | @param {String} type Sanitized name of the runtime |
| | 2134 | @param {Object} [caps] Set of capabilities that differentiate specified runtime |
| | 2135 | @param {Object} [modeCaps] Set of capabilities that do require specific operational mode |
| | 2136 | @param {String} [preferredMode='browser'] Preferred operational mode to choose if no required capabilities were requested |
| | 2137 | */ |
| | 2138 | function Runtime(options, type, caps, modeCaps, preferredMode) { |
| | 2139 | /** |
| | 2140 | Dispatched when runtime is initialized and ready. |
| | 2141 | Results in RuntimeInit on a connected component. |
| | 2142 | |
| | 2143 | @event Init |
| | 2144 | */ |
| | 2145 | |
| | 2146 | /** |
| | 2147 | Dispatched when runtime fails to initialize. |
| | 2148 | Results in RuntimeError on a connected component. |
| | 2149 | |
| | 2150 | @event Error |
| | 2151 | */ |
| | 2152 | |
| | 2153 | var self = this |
| | 2154 | , _shim |
| | 2155 | , _uid = Basic.guid(type + '_') |
| | 2156 | , defaultMode = preferredMode || 'browser' |
| | 2157 | ; |
| | 2158 | |
| | 2159 | options = options || {}; |
| | 2160 | |
| | 2161 | // register runtime in private hash |
| | 2162 | runtimes[_uid] = this; |
| | 2163 | |
| | 2164 | /** |
| | 2165 | Default set of capabilities, which can be redifined later by specific runtime |
| | 2166 | |
| | 2167 | @private |
| | 2168 | @property caps |
| | 2169 | @type Object |
| | 2170 | */ |
| | 2171 | caps = Basic.extend({ |
| | 2172 | // Runtime can: |
| | 2173 | // provide access to raw binary data of the file |
| | 2174 | access_binary: false, |
| | 2175 | // provide access to raw binary data of the image (image extension is optional) |
| | 2176 | access_image_binary: false, |
| | 2177 | // display binary data as thumbs for example |
| | 2178 | display_media: false, |
| | 2179 | // make cross-domain requests |
| | 2180 | do_cors: false, |
| | 2181 | // accept files dragged and dropped from the desktop |
| | 2182 | drag_and_drop: false, |
| | 2183 | // filter files in selection dialog by their extensions |
| | 2184 | filter_by_extension: true, |
| | 2185 | // resize image (and manipulate it raw data of any file in general) |
| | 2186 | resize_image: false, |
| | 2187 | // periodically report how many bytes of total in the file were uploaded (loaded) |
| | 2188 | report_upload_progress: false, |
| | 2189 | // provide access to the headers of http response |
| | 2190 | return_response_headers: false, |
| | 2191 | // support response of specific type, which should be passed as an argument |
| | 2192 | // e.g. runtime.can('return_response_type', 'blob') |
| | 2193 | return_response_type: false, |
| | 2194 | // return http status code of the response |
| | 2195 | return_status_code: true, |
| | 2196 | // send custom http header with the request |
| | 2197 | send_custom_headers: false, |
| | 2198 | // pick up the files from a dialog |
| | 2199 | select_file: false, |
| | 2200 | // select whole folder in file browse dialog |
| | 2201 | select_folder: false, |
| | 2202 | // select multiple files at once in file browse dialog |
| | 2203 | select_multiple: true, |
| | 2204 | // send raw binary data, that is generated after image resizing or manipulation of other kind |
| | 2205 | send_binary_string: false, |
| | 2206 | // send cookies with http request and therefore retain session |
| | 2207 | send_browser_cookies: true, |
| | 2208 | // send data formatted as multipart/form-data |
| | 2209 | send_multipart: true, |
| | 2210 | // slice the file or blob to smaller parts |
| | 2211 | slice_blob: false, |
| | 2212 | // upload file without preloading it to memory, stream it out directly from disk |
| | 2213 | stream_upload: false, |
| | 2214 | // programmatically trigger file browse dialog |
| | 2215 | summon_file_dialog: false, |
| | 2216 | // upload file of specific size, size should be passed as argument |
| | 2217 | // e.g. runtime.can('upload_filesize', '500mb') |
| | 2218 | upload_filesize: true, |
| | 2219 | // initiate http request with specific http method, method should be passed as argument |
| | 2220 | // e.g. runtime.can('use_http_method', 'put') |
| | 2221 | use_http_method: true |
| | 2222 | }, caps); |
| | 2223 | |
| | 2224 | |
| | 2225 | // default to the mode that is compatible with preferred caps |
| | 2226 | if (options.preferred_caps) { |
| | 2227 | defaultMode = Runtime.getMode(modeCaps, options.preferred_caps, defaultMode); |
| | 2228 | } |
| | 2229 | |
| | 2230 | if (MXI_DEBUG && Env.debug.runtime) { |
| | 2231 | Env.log("\tdefault mode: %s", defaultMode); |
| | 2232 | } |
| | 2233 | |
| | 2234 | // small extension factory here (is meant to be extended with actual extensions constructors) |
| | 2235 | _shim = (function() { |
| | 2236 | var objpool = {}; |
| | 2237 | return { |
| | 2238 | exec: function(uid, comp, fn, args) { |
| | 2239 | if (_shim[comp]) { |
| | 2240 | if (!objpool[uid]) { |
| | 2241 | objpool[uid] = { |
| | 2242 | context: this, |
| | 2243 | instance: new _shim[comp]() |
| | 2244 | }; |
| | 2245 | } |
| | 2246 | if (objpool[uid].instance[fn]) { |
| | 2247 | return objpool[uid].instance[fn].apply(this, args); |
| | 2248 | } |
| | 2249 | } |
| | 2250 | }, |
| | 2251 | |
| | 2252 | removeInstance: function(uid) { |
| | 2253 | delete objpool[uid]; |
| | 2254 | }, |
| | 2255 | |
| | 2256 | removeAllInstances: function() { |
| | 2257 | var self = this; |
| | 2258 | Basic.each(objpool, function(obj, uid) { |
| | 2259 | if (Basic.typeOf(obj.instance.destroy) === 'function') { |
| | 2260 | obj.instance.destroy.call(obj.context); |
| | 2261 | } |
| | 2262 | self.removeInstance(uid); |
| | 2263 | }); |
| | 2264 | } |
| | 2265 | }; |
| | 2266 | }()); |
| | 2267 | |
| | 2268 | |
| | 2269 | // public methods |
| | 2270 | Basic.extend(this, { |
| | 2271 | /** |
| | 2272 | Specifies whether runtime instance was initialized or not |
| | 2273 | |
| | 2274 | @property initialized |
| | 2275 | @type {Boolean} |
| | 2276 | @default false |
| | 2277 | */ |
| | 2278 | initialized: false, // shims require this flag to stop initialization retries |
| | 2279 | |
| | 2280 | /** |
| | 2281 | Unique ID of the runtime |
| | 2282 | |
| | 2283 | @property uid |
| | 2284 | @type {String} |
| | 2285 | */ |
| | 2286 | uid: _uid, |
| | 2287 | |
| | 2288 | /** |
| | 2289 | Runtime type (e.g. flash, html5, etc) |
| | 2290 | |
| | 2291 | @property type |
| | 2292 | @type {String} |
| | 2293 | */ |
| | 2294 | type: type, |
| | 2295 | |
| | 2296 | /** |
| | 2297 | Runtime (not native one) may operate in browser or client mode. |
| | 2298 | |
| | 2299 | @property mode |
| | 2300 | @private |
| | 2301 | @type {String|Boolean} current mode or false, if none possible |
| | 2302 | */ |
| | 2303 | mode: Runtime.getMode(modeCaps, (options.required_caps), defaultMode), |
| | 2304 | |
| | 2305 | /** |
| | 2306 | id of the DOM container for the runtime (if available) |
| | 2307 | |
| | 2308 | @property shimid |
| | 2309 | @type {String} |
| | 2310 | */ |
| | 2311 | shimid: _uid + '_container', |
| | 2312 | |
| | 2313 | /** |
| | 2314 | Number of connected clients. If equal to zero, runtime can be destroyed |
| | 2315 | |
| | 2316 | @property clients |
| | 2317 | @type {Number} |
| | 2318 | */ |
| | 2319 | clients: 0, |
| | 2320 | |
| | 2321 | /** |
| | 2322 | Runtime initialization options |
| | 2323 | |
| | 2324 | @property options |
| | 2325 | @type {Object} |
| | 2326 | */ |
| | 2327 | options: options, |
| | 2328 | |
| | 2329 | /** |
| | 2330 | Checks if the runtime has specific capability |
| | 2331 | |
| | 2332 | @method can |
| | 2333 | @param {String} cap Name of capability to check |
| | 2334 | @param {Mixed} [value] If passed, capability should somehow correlate to the value |
| | 2335 | @param {Object} [refCaps] Set of capabilities to check the specified cap against (defaults to internal set) |
| | 2336 | @return {Boolean} true if runtime has such capability and false, if - not |
| | 2337 | */ |
| | 2338 | can: function(cap, value) { |
| | 2339 | var refCaps = arguments[2] || caps; |
| | 2340 | |
| | 2341 | // if cap var is a comma-separated list of caps, convert it to object (key/value) |
| | 2342 | if (Basic.typeOf(cap) === 'string' && Basic.typeOf(value) === 'undefined') { |
| | 2343 | cap = Runtime.parseCaps(cap); |
| | 2344 | } |
| | 2345 | |
| | 2346 | if (Basic.typeOf(cap) === 'object') { |
| | 2347 | for (var key in cap) { |
| | 2348 | if (!this.can(key, cap[key], refCaps)) { |
| | 2349 | return false; |
| | 2350 | } |
| | 2351 | } |
| | 2352 | return true; |
| | 2353 | } |
| | 2354 | |
| | 2355 | // check the individual cap |
| | 2356 | if (Basic.typeOf(refCaps[cap]) === 'function') { |
| | 2357 | return refCaps[cap].call(this, value); |
| | 2358 | } else { |
| | 2359 | return (value === refCaps[cap]); |
| | 2360 | } |
| | 2361 | }, |
| | 2362 | |
| | 2363 | /** |
| | 2364 | Returns container for the runtime as DOM element |
| | 2365 | |
| | 2366 | @method getShimContainer |
| | 2367 | @return {DOMElement} |
| | 2368 | */ |
| | 2369 | getShimContainer: function() { |
| | 2370 | var container, shimContainer = Dom.get(this.shimid); |
| | 2371 | |
| | 2372 | // if no container for shim, create one |
| | 2373 | if (!shimContainer) { |
| | 2374 | container = this.options.container ? Dom.get(this.options.container) : document.body; |
| | 2375 | |
| | 2376 | // create shim container and insert it at an absolute position into the outer container |
| | 2377 | shimContainer = document.createElement('div'); |
| | 2378 | shimContainer.id = this.shimid; |
| | 2379 | shimContainer.className = 'moxie-shim moxie-shim-' + this.type; |
| | 2380 | |
| | 2381 | Basic.extend(shimContainer.style, { |
| | 2382 | position: 'absolute', |
| | 2383 | top: '0px', |
| | 2384 | left: '0px', |
| | 2385 | width: '1px', |
| | 2386 | height: '1px', |
| | 2387 | overflow: 'hidden' |
| | 2388 | }); |
| | 2389 | |
| | 2390 | container.appendChild(shimContainer); |
| | 2391 | container = null; |
| | 2392 | } |
| | 2393 | |
| | 2394 | return shimContainer; |
| | 2395 | }, |
| | 2396 | |
| | 2397 | /** |
| | 2398 | Returns runtime as DOM element (if appropriate) |
| | 2399 | |
| | 2400 | @method getShim |
| | 2401 | @return {DOMElement} |
| | 2402 | */ |
| | 2403 | getShim: function() { |
| | 2404 | return _shim; |
| | 2405 | }, |
| | 2406 | |
| | 2407 | /** |
| | 2408 | Invokes a method within the runtime itself (might differ across the runtimes) |
| | 2409 | |
| | 2410 | @method shimExec |
| | 2411 | @param {Mixed} [] |
| | 2412 | @protected |
| | 2413 | @return {Mixed} Depends on the action and component |
| | 2414 | */ |
| | 2415 | shimExec: function(component, action) { |
| | 2416 | var args = [].slice.call(arguments, 2); |
| | 2417 | return self.getShim().exec.call(this, this.uid, component, action, args); |
| | 2418 | }, |
| | 2419 | |
| | 2420 | /** |
| | 2421 | Operaional interface that is used by components to invoke specific actions on the runtime |
| | 2422 | (is invoked in the scope of component) |
| | 2423 | |
| | 2424 | @method exec |
| | 2425 | @param {Mixed} []* |
| | 2426 | @protected |
| | 2427 | @return {Mixed} Depends on the action and component |
| | 2428 | */ |
| | 2429 | exec: function(component, action) { // this is called in the context of component, not runtime |
| | 2430 | var args = [].slice.call(arguments, 2); |
| | 2431 | |
| | 2432 | if (self[component] && self[component][action]) { |
| | 2433 | return self[component][action].apply(this, args); |
| | 2434 | } |
| | 2435 | return self.shimExec.apply(this, arguments); |
| | 2436 | }, |
| | 2437 | |
| | 2438 | /** |
| | 2439 | Destroys the runtime (removes all events and deletes DOM structures) |
| | 2440 | |
| | 2441 | @method destroy |
| | 2442 | */ |
| | 2443 | destroy: function() { |
| | 2444 | if (!self) { |
| | 2445 | return; // obviously already destroyed |
| | 2446 | } |
| | 2447 | |
| | 2448 | var shimContainer = Dom.get(this.shimid); |
| | 2449 | if (shimContainer) { |
| | 2450 | shimContainer.parentNode.removeChild(shimContainer); |
| | 2451 | } |
| | 2452 | |
| | 2453 | if (_shim) { |
| | 2454 | _shim.removeAllInstances(); |
| | 2455 | } |
| | 2456 | |
| | 2457 | this.unbindAll(); |
| | 2458 | delete runtimes[this.uid]; |
| | 2459 | this.uid = null; // mark this runtime as destroyed |
| | 2460 | _uid = self = _shim = shimContainer = null; |
| | 2461 | } |
| | 2462 | }); |
| | 2463 | |
| | 2464 | // once we got the mode, test against all caps |
| | 2465 | if (this.mode && options.required_caps && !this.can(options.required_caps)) { |
| | 2466 | this.mode = false; |
| | 2467 | } |
| | 2468 | } |
| | 2469 | |
| | 2470 | |
| | 2471 | /** |
| | 2472 | Default order to try different runtime types |
| | 2473 | |
| | 2474 | @property order |
| | 2475 | @type String |
| | 2476 | @static |
| | 2477 | */ |
| | 2478 | Runtime.order = 'html5,html4'; |
| | 2479 | |
| | 2480 | |
| | 2481 | /** |
| | 2482 | Retrieves runtime from private hash by it's uid |
| | 2483 | |
| | 2484 | @method getRuntime |
| | 2485 | @private |
| | 2486 | @static |
| | 2487 | @param {String} uid Unique identifier of the runtime |
| | 2488 | @return {Runtime|Boolean} Returns runtime, if it exists and false, if - not |
| | 2489 | */ |
| | 2490 | Runtime.getRuntime = function(uid) { |
| | 2491 | return runtimes[uid] ? runtimes[uid] : false; |
| | 2492 | }; |
| | 2493 | |
| | 2494 | |
| | 2495 | /** |
| | 2496 | Register constructor for the Runtime of new (or perhaps modified) type |
| | 2497 | |
| | 2498 | @method addConstructor |
| | 2499 | @static |
| | 2500 | @param {String} type Runtime type (e.g. flash, html5, etc) |
| | 2501 | @param {Function} construct Constructor for the Runtime type |
| | 2502 | */ |
| | 2503 | Runtime.addConstructor = function(type, constructor) { |
| | 2504 | constructor.prototype = EventTarget.instance; |
| | 2505 | runtimeConstructors[type] = constructor; |
| | 2506 | }; |
| | 2507 | |
| | 2508 | |
| | 2509 | /** |
| | 2510 | Get the constructor for the specified type. |
| | 2511 | |
| | 2512 | method getConstructor |
| | 2513 | @static |
| | 2514 | @param {String} type Runtime type (e.g. flash, html5, etc) |
| | 2515 | @return {Function} Constructor for the Runtime type |
| | 2516 | */ |
| | 2517 | Runtime.getConstructor = function(type) { |
| | 2518 | return runtimeConstructors[type] || null; |
| | 2519 | }; |
| | 2520 | |
| | 2521 | |
| | 2522 | /** |
| | 2523 | Get info about the runtime (uid, type, capabilities) |
| | 2524 | |
| | 2525 | @method getInfo |
| | 2526 | @static |
| | 2527 | @param {String} uid Unique identifier of the runtime |
| | 2528 | @return {Mixed} Info object or null if runtime doesn't exist |
| | 2529 | */ |
| | 2530 | Runtime.getInfo = function(uid) { |
| | 2531 | var runtime = Runtime.getRuntime(uid); |
| | 2532 | |
| | 2533 | if (runtime) { |
| | 2534 | return { |
| | 2535 | uid: runtime.uid, |
| | 2536 | type: runtime.type, |
| | 2537 | mode: runtime.mode, |
| | 2538 | can: function() { |
| | 2539 | return runtime.can.apply(runtime, arguments); |
| | 2540 | } |
| | 2541 | }; |
| | 2542 | } |
| | 2543 | return null; |
| | 2544 | }; |
| | 2545 | |
| | 2546 | |
| | 2547 | /** |
| | 2548 | Convert caps represented by a comma-separated string to the object representation. |
| | 2549 | |
| | 2550 | @method parseCaps |
| | 2551 | @static |
| | 2552 | @param {String} capStr Comma-separated list of capabilities |
| | 2553 | @return {Object} |
| | 2554 | */ |
| | 2555 | Runtime.parseCaps = function(capStr) { |
| | 2556 | var capObj = {}; |
| | 2557 | |
| | 2558 | if (Basic.typeOf(capStr) !== 'string') { |
| | 2559 | return capStr || {}; |
| | 2560 | } |
| | 2561 | |
| | 2562 | Basic.each(capStr.split(','), function(key) { |
| | 2563 | capObj[key] = true; // we assume it to be - true |
| | 2564 | }); |
| | 2565 | |
| | 2566 | return capObj; |
| | 2567 | }; |
| | 2568 | |
| | 2569 | /** |
| | 2570 | Test the specified runtime for specific capabilities. |
| | 2571 | |
| | 2572 | @method can |
| | 2573 | @static |
| | 2574 | @param {String} type Runtime type (e.g. flash, html5, etc) |
| | 2575 | @param {String|Object} caps Set of capabilities to check |
| | 2576 | @return {Boolean} Result of the test |
| | 2577 | */ |
| | 2578 | Runtime.can = function(type, caps) { |
| | 2579 | var runtime |
| | 2580 | , constructor = Runtime.getConstructor(type) |
| | 2581 | , mode |
| | 2582 | ; |
| | 2583 | if (constructor) { |
| | 2584 | runtime = new constructor({ |
| | 2585 | required_caps: caps |
| | 2586 | }); |
| | 2587 | mode = runtime.mode; |
| | 2588 | runtime.destroy(); |
| | 2589 | return !!mode; |
| | 2590 | } |
| | 2591 | return false; |
| | 2592 | }; |
| | 2593 | |
| | 2594 | |
| | 2595 | /** |
| | 2596 | Figure out a runtime that supports specified capabilities. |
| | 2597 | |
| | 2598 | @method thatCan |
| | 2599 | @static |
| | 2600 | @param {String|Object} caps Set of capabilities to check |
| | 2601 | @param {String} [runtimeOrder] Comma-separated list of runtimes to check against |
| | 2602 | @return {String} Usable runtime identifier or null |
| | 2603 | */ |
| | 2604 | Runtime.thatCan = function(caps, runtimeOrder) { |
| | 2605 | var types = (runtimeOrder || Runtime.order).split(/\s*,\s*/); |
| | 2606 | for (var i in types) { |
| | 2607 | if (Runtime.can(types[i], caps)) { |
| | 2608 | return types[i]; |
| | 2609 | } |
| | 2610 | } |
| | 2611 | return null; |
| | 2612 | }; |
| | 2613 | |
| | 2614 | |
| | 2615 | /** |
| | 2616 | Figure out an operational mode for the specified set of capabilities. |
| | 2617 | |
| | 2618 | @method getMode |
| | 2619 | @static |
| | 2620 | @param {Object} modeCaps Set of capabilities that depend on particular runtime mode |
| | 2621 | @param {Object} [requiredCaps] Supplied set of capabilities to find operational mode for |
| | 2622 | @param {String|Boolean} [defaultMode='browser'] Default mode to use |
| | 2623 | @return {String|Boolean} Compatible operational mode |
| | 2624 | */ |
| | 2625 | Runtime.getMode = function(modeCaps, requiredCaps, defaultMode) { |
| | 2626 | var mode = null; |
| | 2627 | |
| | 2628 | if (Basic.typeOf(defaultMode) === 'undefined') { // only if not specified |
| | 2629 | defaultMode = 'browser'; |
| | 2630 | } |
| | 2631 | |
| | 2632 | if (requiredCaps && !Basic.isEmptyObj(modeCaps)) { |
| | 2633 | // loop over required caps and check if they do require the same mode |
| | 2634 | Basic.each(requiredCaps, function(value, cap) { |
| | 2635 | if (modeCaps.hasOwnProperty(cap)) { |
| | 2636 | var capMode = modeCaps[cap](value); |
| | 2637 | |
| | 2638 | // make sure we always have an array |
| | 2639 | if (typeof(capMode) === 'string') { |
| | 2640 | capMode = [capMode]; |
| | 2641 | } |
| | 2642 | |
| | 2643 | if (!mode) { |
| | 2644 | mode = capMode; |
| | 2645 | } else if (!(mode = Basic.arrayIntersect(mode, capMode))) { |
| | 2646 | // if cap requires conflicting mode - runtime cannot fulfill required caps |
| | 2647 | |
| | 2648 | if (MXI_DEBUG && Env.debug.runtime) { |
| | 2649 | Env.log("\t\t%c: %v (conflicting mode requested: %s)", cap, value, capMode); |
| | 2650 | } |
| | 2651 | |
| | 2652 | return (mode = false); |
| | 2653 | } |
| | 2654 | } |
| | 2655 | |
| | 2656 | if (MXI_DEBUG && Env.debug.runtime) { |
| | 2657 | Env.log("\t\t%c: %v (compatible modes: %s)", cap, value, mode); |
| | 2658 | } |
| | 2659 | }); |
| | 2660 | |
| | 2661 | if (mode) { |
| | 2662 | return Basic.inArray(defaultMode, mode) !== -1 ? defaultMode : mode[0]; |
| | 2663 | } else if (mode === false) { |
| | 2664 | return false; |
| | 2665 | } |
| | 2666 | } |
| | 2667 | return defaultMode; |
| | 2668 | }; |
| | 2669 | |
| | 2670 | |
| | 2671 | /** |
| | 2672 | Capability check that always returns true |
| | 2673 | |
| | 2674 | @private |
| | 2675 | @static |
| | 2676 | @return {True} |
| | 2677 | */ |
| | 2678 | Runtime.capTrue = function() { |
| | 2679 | return true; |
| | 2680 | }; |
| | 2681 | |
| | 2682 | /** |
| | 2683 | Capability check that always returns false |
| | 2684 | |
| | 2685 | @private |
| | 2686 | @static |
| | 2687 | @return {False} |
| | 2688 | */ |
| | 2689 | Runtime.capFalse = function() { |
| | 2690 | return false; |
| | 2691 | }; |
| | 2692 | |
| | 2693 | /** |
| | 2694 | Evaluate the expression to boolean value and create a function that always returns it. |
| | 2695 | |
| | 2696 | @private |
| | 2697 | @static |
| | 2698 | @param {Mixed} expr Expression to evaluate |
| | 2699 | @return {Function} Function returning the result of evaluation |
| | 2700 | */ |
| | 2701 | Runtime.capTest = function(expr) { |
| | 2702 | return function() { |
| | 2703 | return !!expr; |
| | 2704 | }; |
| | 2705 | }; |
| | 2706 | |
| | 2707 | return Runtime; |
| | 2708 | }); |
| | 2709 | |
| | 2710 | // Included from: src/javascript/runtime/RuntimeClient.js |
| | 2711 | |
| | 2712 | /** |
| | 2713 | * RuntimeClient.js |
| | 2714 | * |
| | 2715 | * Copyright 2013, Moxiecode Systems AB |
| | 2716 | * Released under GPL License. |
| | 2717 | * |
| | 2718 | * License: http://www.plupload.com/license |
| | 2719 | * Contributing: http://www.plupload.com/contributing |
| | 2720 | */ |
| | 2721 | |
| | 2722 | define('moxie/runtime/RuntimeClient', [ |
| | 2723 | 'moxie/core/utils/Env', |
| | 2724 | 'moxie/core/Exceptions', |
| | 2725 | 'moxie/core/utils/Basic', |
| | 2726 | 'moxie/runtime/Runtime' |
| | 2727 | ], function(Env, x, Basic, Runtime) { |
| | 2728 | /** |
| | 2729 | Set of methods and properties, required by a component to acquire ability to connect to a runtime |
| | 2730 | |
| | 2731 | @class RuntimeClient |
| | 2732 | */ |
| | 2733 | return function RuntimeClient() { |
| | 2734 | var runtime; |
| | 2735 | |
| | 2736 | Basic.extend(this, { |
| | 2737 | /** |
| | 2738 | Connects to the runtime specified by the options. Will either connect to existing runtime or create a new one. |
| | 2739 | Increments number of clients connected to the specified runtime. |
| | 2740 | |
| | 2741 | @private |
| | 2742 | @method connectRuntime |
| | 2743 | @param {Mixed} options Can be a runtme uid or a set of key-value pairs defining requirements and pre-requisites |
| | 2744 | */ |
| | 2745 | connectRuntime: function(options) { |
| | 2746 | var comp = this, ruid; |
| | 2747 | |
| | 2748 | function initialize(items) { |
| | 2749 | var type, constructor; |
| | 2750 | |
| | 2751 | // if we ran out of runtimes |
| | 2752 | if (!items.length) { |
| | 2753 | comp.trigger('RuntimeError', new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR)); |
| | 2754 | runtime = null; |
| | 2755 | return; |
| | 2756 | } |
| | 2757 | |
| | 2758 | type = items.shift().toLowerCase(); |
| | 2759 | constructor = Runtime.getConstructor(type); |
| | 2760 | if (!constructor) { |
| | 2761 | initialize(items); |
| | 2762 | return; |
| | 2763 | } |
| | 2764 | |
| | 2765 | if (MXI_DEBUG && Env.debug.runtime) { |
| | 2766 | Env.log("Trying runtime: %s", type); |
| | 2767 | Env.log(options); |
| | 2768 | } |
| | 2769 | |
| | 2770 | // try initializing the runtime |
| | 2771 | runtime = new constructor(options); |
| | 2772 | |
| | 2773 | runtime.bind('Init', function() { |
| | 2774 | // mark runtime as initialized |
| | 2775 | runtime.initialized = true; |
| | 2776 | |
| | 2777 | if (MXI_DEBUG && Env.debug.runtime) { |
| | 2778 | Env.log("Runtime '%s' initialized", runtime.type); |
| | 2779 | } |
| | 2780 | |
| | 2781 | // jailbreak ... |
| | 2782 | setTimeout(function() { |
| | 2783 | runtime.clients++; |
| | 2784 | // this will be triggered on component |
| | 2785 | comp.trigger('RuntimeInit', runtime); |
| | 2786 | }, 1); |
| | 2787 | }); |
| | 2788 | |
| | 2789 | runtime.bind('Error', function() { |
| | 2790 | if (MXI_DEBUG && Env.debug.runtime) { |
| | 2791 | Env.log("Runtime '%s' failed to initialize", runtime.type); |
| | 2792 | } |
| | 2793 | |
| | 2794 | runtime.destroy(); // runtime cannot destroy itself from inside at a right moment, thus we do it here |
| | 2795 | initialize(items); |
| | 2796 | }); |
| | 2797 | |
| | 2798 | /*runtime.bind('Exception', function() { });*/ |
| | 2799 | |
| | 2800 | if (MXI_DEBUG && Env.debug.runtime) { |
| | 2801 | Env.log("\tselected mode: %s", runtime.mode); |
| | 2802 | } |
| | 2803 | |
| | 2804 | // check if runtime managed to pick-up operational mode |
| | 2805 | if (!runtime.mode) { |
| | 2806 | runtime.trigger('Error'); |
| | 2807 | return; |
| | 2808 | } |
| | 2809 | |
| | 2810 | runtime.init(); |
| | 2811 | } |
| | 2812 | |
| | 2813 | // check if a particular runtime was requested |
| | 2814 | if (Basic.typeOf(options) === 'string') { |
| | 2815 | ruid = options; |
| | 2816 | } else if (Basic.typeOf(options.ruid) === 'string') { |
| | 2817 | ruid = options.ruid; |
| | 2818 | } |
| | 2819 | |
| | 2820 | if (ruid) { |
| | 2821 | runtime = Runtime.getRuntime(ruid); |
| | 2822 | if (runtime) { |
| | 2823 | runtime.clients++; |
| | 2824 | return runtime; |
| | 2825 | } else { |
| | 2826 | // there should be a runtime and there's none - weird case |
| | 2827 | throw new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR); |
| | 2828 | } |
| | 2829 | } |
| | 2830 | |
| | 2831 | // initialize a fresh one, that fits runtime list and required features best |
| | 2832 | initialize((options.runtime_order || Runtime.order).split(/\s*,\s*/)); |
| | 2833 | }, |
| | 2834 | |
| | 2835 | |
| | 2836 | /** |
| | 2837 | Disconnects from the runtime. Decrements number of clients connected to the specified runtime. |
| | 2838 | |
| | 2839 | @private |
| | 2840 | @method disconnectRuntime |
| | 2841 | */ |
| | 2842 | disconnectRuntime: function() { |
| | 2843 | if (runtime && --runtime.clients <= 0) { |
| | 2844 | runtime.destroy(); |
| | 2845 | } |
| | 2846 | |
| | 2847 | // once the component is disconnected, it shouldn't have access to the runtime |
| | 2848 | runtime = null; |
| | 2849 | }, |
| | 2850 | |
| | 2851 | |
| | 2852 | /** |
| | 2853 | Returns the runtime to which the client is currently connected. |
| | 2854 | |
| | 2855 | @method getRuntime |
| | 2856 | @return {Runtime} Runtime or null if client is not connected |
| | 2857 | */ |
| | 2858 | getRuntime: function() { |
| | 2859 | if (runtime && runtime.uid) { |
| | 2860 | return runtime; |
| | 2861 | } |
| | 2862 | return runtime = null; // make sure we do not leave zombies rambling around |
| | 2863 | }, |
| | 2864 | |
| | 2865 | |
| | 2866 | /** |
| | 2867 | Handy shortcut to safely invoke runtime extension methods. |
| | 2868 | |
| | 2869 | @private |
| | 2870 | @method exec |
| | 2871 | @return {Mixed} Whatever runtime extension method returns |
| | 2872 | */ |
| | 2873 | exec: function() { |
| | 2874 | if (runtime) { |
| | 2875 | return runtime.exec.apply(this, arguments); |
| | 2876 | } |
| | 2877 | return null; |
| | 2878 | } |
| | 2879 | |
| | 2880 | }); |
| | 2881 | }; |
| | 2882 | |
| | 2883 | |
| | 2884 | }); |
| | 2885 | |
| | 2886 | // Included from: src/javascript/file/FileInput.js |
| | 2887 | |
| | 2888 | /** |
| | 2889 | * FileInput.js |
| | 2890 | * |
| | 2891 | * Copyright 2013, Moxiecode Systems AB |
| | 2892 | * Released under GPL License. |
| | 2893 | * |
| | 2894 | * License: http://www.plupload.com/license |
| | 2895 | * Contributing: http://www.plupload.com/contributing |
| | 2896 | */ |
| | 2897 | |
| | 2898 | define('moxie/file/FileInput', [ |
| | 2899 | 'moxie/core/utils/Basic', |
| | 2900 | 'moxie/core/utils/Env', |
| | 2901 | 'moxie/core/utils/Mime', |
| | 2902 | 'moxie/core/utils/Dom', |
| | 2903 | 'moxie/core/Exceptions', |
| | 2904 | 'moxie/core/EventTarget', |
| | 2905 | 'moxie/core/I18n', |
| | 2906 | 'moxie/runtime/Runtime', |
| | 2907 | 'moxie/runtime/RuntimeClient' |
| | 2908 | ], function(Basic, Env, Mime, Dom, x, EventTarget, I18n, Runtime, RuntimeClient) { |
| | 2909 | /** |
| | 2910 | Provides a convenient way to create cross-browser file-picker. Generates file selection dialog on click, |
| | 2911 | converts selected files to _File_ objects, to be used in conjunction with _Image_, preloaded in memory |
| | 2912 | with _FileReader_ or uploaded to a server through _XMLHttpRequest_. |
| | 2913 | |
| | 2914 | @class FileInput |
| | 2915 | @constructor |
| | 2916 | @extends EventTarget |
| | 2917 | @uses RuntimeClient |
| | 2918 | @param {Object|String|DOMElement} options If options is string or node, argument is considered as _browse\_button_. |
| | 2919 | @param {String|DOMElement} options.browse_button DOM Element to turn into file picker. |
| | 2920 | @param {Array} [options.accept] Array of mime types to accept. By default accepts all. |
| | 2921 | @param {String} [options.file='file'] Name of the file field (not the filename). |
| | 2922 | @param {Boolean} [options.multiple=false] Enable selection of multiple files. |
| | 2923 | @param {Boolean} [options.directory=false] Turn file input into the folder input (cannot be both at the same time). |
| | 2924 | @param {String|DOMElement} [options.container] DOM Element to use as a container for file-picker. Defaults to parentNode |
| | 2925 | for _browse\_button_. |
| | 2926 | @param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support. |
| | 2927 | |
| | 2928 | @example |
| | 2929 | <div id="container"> |
| | 2930 | <a id="file-picker" href="javascript:;">Browse...</a> |
| | 2931 | </div> |
| | 2932 | |
| | 2933 | <script> |
| | 2934 | var fileInput = new mOxie.FileInput({ |
| | 2935 | browse_button: 'file-picker', // or document.getElementById('file-picker') |
| | 2936 | container: 'container', |
| | 2937 | accept: [ |
| | 2938 | {title: "Image files", extensions: "jpg,gif,png"} // accept only images |
| | 2939 | ], |
| | 2940 | multiple: true // allow multiple file selection |
| | 2941 | }); |
| | 2942 | |
| | 2943 | fileInput.onchange = function(e) { |
| | 2944 | // do something to files array |
| | 2945 | console.info(e.target.files); // or this.files or fileInput.files |
| | 2946 | }; |
| | 2947 | |
| | 2948 | fileInput.init(); // initialize |
| | 2949 | </script> |
| | 2950 | */ |
| | 2951 | var dispatches = [ |
| | 2952 | /** |
| | 2953 | Dispatched when runtime is connected and file-picker is ready to be used. |
| | 2954 | |
| | 2955 | @event ready |
| | 2956 | @param {Object} event |
| | 2957 | */ |
| | 2958 | 'ready', |
| | 2959 | |
| | 2960 | /** |
| | 2961 | Dispatched right after [ready](#event_ready) event, and whenever [refresh()](#method_refresh) is invoked. |
| | 2962 | Check [corresponding documentation entry](#method_refresh) for more info. |
| | 2963 | |
| | 2964 | @event refresh |
| | 2965 | @param {Object} event |
| | 2966 | */ |
| | 2967 | |
| | 2968 | /** |
| | 2969 | Dispatched when selection of files in the dialog is complete. |
| | 2970 | |
| | 2971 | @event change |
| | 2972 | @param {Object} event |
| | 2973 | */ |
| | 2974 | 'change', |
| | 2975 | |
| | 2976 | 'cancel', // TODO: might be useful |
| | 2977 | |
| | 2978 | /** |
| | 2979 | Dispatched when mouse cursor enters file-picker area. Can be used to style element |
| | 2980 | accordingly. |
| | 2981 | |
| | 2982 | @event mouseenter |
| | 2983 | @param {Object} event |
| | 2984 | */ |
| | 2985 | 'mouseenter', |
| | 2986 | |
| | 2987 | /** |
| | 2988 | Dispatched when mouse cursor leaves file-picker area. Can be used to style element |
| | 2989 | accordingly. |
| | 2990 | |
| | 2991 | @event mouseleave |
| | 2992 | @param {Object} event |
| | 2993 | */ |
| | 2994 | 'mouseleave', |
| | 2995 | |
| | 2996 | /** |
| | 2997 | Dispatched when functional mouse button is pressed on top of file-picker area. |
| | 2998 | |
| | 2999 | @event mousedown |
| | 3000 | @param {Object} event |
| | 3001 | */ |
| | 3002 | 'mousedown', |
| | 3003 | |
| | 3004 | /** |
| | 3005 | Dispatched when functional mouse button is released on top of file-picker area. |
| | 3006 | |
| | 3007 | @event mouseup |
| | 3008 | @param {Object} event |
| | 3009 | */ |
| | 3010 | 'mouseup' |
| | 3011 | ]; |
| | 3012 | |
| | 3013 | function FileInput(options) { |
| | 3014 | if (MXI_DEBUG) { |
| | 3015 | Env.log("Instantiating FileInput..."); |
| | 3016 | } |
| | 3017 | |
| | 3018 | var self = this, |
| | 3019 | container, browseButton, defaults; |
| | 3020 | |
| | 3021 | // if flat argument passed it should be browse_button id |
| | 3022 | if (Basic.inArray(Basic.typeOf(options), ['string', 'node']) !== -1) { |
| | 3023 | options = { browse_button : options }; |
| | 3024 | } |
| | 3025 | |
| | 3026 | // this will help us to find proper default container |
| | 3027 | browseButton = Dom.get(options.browse_button); |
| | 3028 | if (!browseButton) { |
| | 3029 | // browse button is required |
| | 3030 | throw new x.DOMException(x.DOMException.NOT_FOUND_ERR); |
| | 3031 | } |
| | 3032 | |
| | 3033 | // figure out the options |
| | 3034 | defaults = { |
| | 3035 | accept: [{ |
| | 3036 | title: I18n.translate('All Files'), |
| | 3037 | extensions: '*' |
| | 3038 | }], |
| | 3039 | name: 'file', |
| | 3040 | multiple: false, |
| | 3041 | required_caps: false, |
| | 3042 | container: browseButton.parentNode || document.body |
| | 3043 | }; |
| | 3044 | |
| | 3045 | options = Basic.extend({}, defaults, options); |
| | 3046 | |
| | 3047 | // convert to object representation |
| | 3048 | if (typeof(options.required_caps) === 'string') { |
| | 3049 | options.required_caps = Runtime.parseCaps(options.required_caps); |
| | 3050 | } |
| | 3051 | |
| | 3052 | // normalize accept option (could be list of mime types or array of title/extensions pairs) |
| | 3053 | if (typeof(options.accept) === 'string') { |
| | 3054 | options.accept = Mime.mimes2extList(options.accept); |
| | 3055 | } |
| | 3056 | |
| | 3057 | container = Dom.get(options.container); |
| | 3058 | // make sure we have container |
| | 3059 | if (!container) { |
| | 3060 | container = document.body; |
| | 3061 | } |
| | 3062 | |
| | 3063 | // make container relative, if it's not |
| | 3064 | if (Dom.getStyle(container, 'position') === 'static') { |
| | 3065 | container.style.position = 'relative'; |
| | 3066 | } |
| | 3067 | |
| | 3068 | container = browseButton = null; // IE |
| | 3069 | |
| | 3070 | RuntimeClient.call(self); |
| | 3071 | |
| | 3072 | Basic.extend(self, { |
| | 3073 | /** |
| | 3074 | Unique id of the component |
| | 3075 | |
| | 3076 | @property uid |
| | 3077 | @protected |
| | 3078 | @readOnly |
| | 3079 | @type {String} |
| | 3080 | @default UID |
| | 3081 | */ |
| | 3082 | uid: Basic.guid('uid_'), |
| | 3083 | |
| | 3084 | /** |
| | 3085 | Unique id of the connected runtime, if any. |
| | 3086 | |
| | 3087 | @property ruid |
| | 3088 | @protected |
| | 3089 | @type {String} |
| | 3090 | */ |
| | 3091 | ruid: null, |
| | 3092 | |
| | 3093 | /** |
| | 3094 | Unique id of the runtime container. Useful to get hold of it for various manipulations. |
| | 3095 | |
| | 3096 | @property shimid |
| | 3097 | @protected |
| | 3098 | @type {String} |
| | 3099 | */ |
| | 3100 | shimid: null, |
| | 3101 | |
| | 3102 | /** |
| | 3103 | Array of selected mOxie.File objects |
| | 3104 | |
| | 3105 | @property files |
| | 3106 | @type {Array} |
| | 3107 | @default null |
| | 3108 | */ |
| | 3109 | files: null, |
| | 3110 | |
| | 3111 | /** |
| | 3112 | Initializes the file-picker, connects it to runtime and dispatches event ready when done. |
| | 3113 | |
| | 3114 | @method init |
| | 3115 | */ |
| | 3116 | init: function() { |
| | 3117 | self.bind('RuntimeInit', function(e, runtime) { |
| | 3118 | self.ruid = runtime.uid; |
| | 3119 | self.shimid = runtime.shimid; |
| | 3120 | |
| | 3121 | self.bind("Ready", function() { |
| | 3122 | self.trigger("Refresh"); |
| | 3123 | }, 999); |
| | 3124 | |
| | 3125 | // re-position and resize shim container |
| | 3126 | self.bind('Refresh', function() { |
| | 3127 | var pos, size, browseButton, shimContainer; |
| | 3128 | |
| | 3129 | browseButton = Dom.get(options.browse_button); |
| | 3130 | shimContainer = Dom.get(runtime.shimid); // do not use runtime.getShimContainer(), since it will create container if it doesn't exist |
| | 3131 | |
| | 3132 | if (browseButton) { |
| | 3133 | pos = Dom.getPos(browseButton, Dom.get(options.container)); |
| | 3134 | size = Dom.getSize(browseButton); |
| | 3135 | |
| | 3136 | if (shimContainer) { |
| | 3137 | Basic.extend(shimContainer.style, { |
| | 3138 | top : pos.y + 'px', |
| | 3139 | left : pos.x + 'px', |
| | 3140 | width : size.w + 'px', |
| | 3141 | height : size.h + 'px' |
| | 3142 | }); |
| | 3143 | } |
| | 3144 | } |
| | 3145 | shimContainer = browseButton = null; |
| | 3146 | }); |
| | 3147 | |
| | 3148 | runtime.exec.call(self, 'FileInput', 'init', options); |
| | 3149 | }); |
| | 3150 | |
| | 3151 | // runtime needs: options.required_features, options.runtime_order and options.container |
| | 3152 | self.connectRuntime(Basic.extend({}, options, { |
| | 3153 | required_caps: { |
| | 3154 | select_file: true |
| | 3155 | } |
| | 3156 | })); |
| | 3157 | }, |
| | 3158 | |
| | 3159 | /** |
| | 3160 | Disables file-picker element, so that it doesn't react to mouse clicks. |
| | 3161 | |
| | 3162 | @method disable |
| | 3163 | @param {Boolean} [state=true] Disable component if - true, enable if - false |
| | 3164 | */ |
| | 3165 | disable: function(state) { |
| | 3166 | var runtime = this.getRuntime(); |
| | 3167 | if (runtime) { |
| | 3168 | runtime.exec.call(this, 'FileInput', 'disable', Basic.typeOf(state) === 'undefined' ? true : state); |
| | 3169 | } |
| | 3170 | }, |
| | 3171 | |
| | 3172 | |
| | 3173 | /** |
| | 3174 | Reposition and resize dialog trigger to match the position and size of browse_button element. |
| | 3175 | |
| | 3176 | @method refresh |
| | 3177 | */ |
| | 3178 | refresh: function() { |
| | 3179 | self.trigger("Refresh"); |
| | 3180 | }, |
| | 3181 | |
| | 3182 | |
| | 3183 | /** |
| | 3184 | Destroy component. |
| | 3185 | |
| | 3186 | @method destroy |
| | 3187 | */ |
| | 3188 | destroy: function() { |
| | 3189 | var runtime = this.getRuntime(); |
| | 3190 | if (runtime) { |
| | 3191 | runtime.exec.call(this, 'FileInput', 'destroy'); |
| | 3192 | this.disconnectRuntime(); |
| | 3193 | } |
| | 3194 | |
| | 3195 | if (Basic.typeOf(this.files) === 'array') { |
| | 3196 | // no sense in leaving associated files behind |
| | 3197 | Basic.each(this.files, function(file) { |
| | 3198 | file.destroy(); |
| | 3199 | }); |
| | 3200 | } |
| | 3201 | this.files = null; |
| | 3202 | |
| | 3203 | this.unbindAll(); |
| | 3204 | } |
| | 3205 | }); |
| | 3206 | |
| | 3207 | this.handleEventProps(dispatches); |
| | 3208 | } |
| | 3209 | |
| | 3210 | FileInput.prototype = EventTarget.instance; |
| | 3211 | |
| | 3212 | return FileInput; |
| | 3213 | }); |
| | 3214 | |
| | 3215 | // Included from: src/javascript/core/utils/Encode.js |
| | 3216 | |
| | 3217 | /** |
| | 3218 | * Encode.js |
| | 3219 | * |
| | 3220 | * Copyright 2013, Moxiecode Systems AB |
| | 3221 | * Released under GPL License. |
| | 3222 | * |
| | 3223 | * License: http://www.plupload.com/license |
| | 3224 | * Contributing: http://www.plupload.com/contributing |
| | 3225 | */ |
| | 3226 | |
| | 3227 | define('moxie/core/utils/Encode', [], function() { |
| | 3228 | |
| | 3229 | /** |
| | 3230 | Encode string with UTF-8 |
| | 3231 | |
| | 3232 | @method utf8_encode |
| | 3233 | @for Utils |
| | 3234 | @static |
| | 3235 | @param {String} str String to encode |
| | 3236 | @return {String} UTF-8 encoded string |
| | 3237 | */ |
| | 3238 | var utf8_encode = function(str) { |
| | 3239 | return unescape(encodeURIComponent(str)); |
| | 3240 | }; |
| | 3241 | |
| | 3242 | /** |
| | 3243 | Decode UTF-8 encoded string |
| | 3244 | |
| | 3245 | @method utf8_decode |
| | 3246 | @static |
| | 3247 | @param {String} str String to decode |
| | 3248 | @return {String} Decoded string |
| | 3249 | */ |
| | 3250 | var utf8_decode = function(str_data) { |
| | 3251 | return decodeURIComponent(escape(str_data)); |
| | 3252 | }; |
| | 3253 | |
| | 3254 | /** |
| | 3255 | Decode Base64 encoded string (uses browser's default method if available), |
| | 3256 | from: https://raw.github.com/kvz/phpjs/master/functions/url/base64_decode.js |
| | 3257 | |
| | 3258 | @method atob |
| | 3259 | @static |
| | 3260 | @param {String} data String to decode |
| | 3261 | @return {String} Decoded string |
| | 3262 | */ |
| | 3263 | var atob = function(data, utf8) { |
| | 3264 | if (typeof(window.atob) === 'function') { |
| | 3265 | return utf8 ? utf8_decode(window.atob(data)) : window.atob(data); |
| | 3266 | } |
| | 3267 | |
| | 3268 | // http://kevin.vanzonneveld.net |
| | 3269 | // + original by: Tyler Akins (http://rumkin.com) |
| | 3270 | // + improved by: Thunder.m |
| | 3271 | // + input by: Aman Gupta |
| | 3272 | // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) |
| | 3273 | // + bugfixed by: Onno Marsman |
| | 3274 | // + bugfixed by: Pellentesque Malesuada |
| | 3275 | // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) |
| | 3276 | // + input by: Brett Zamir (http://brett-zamir.me) |
| | 3277 | // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) |
| | 3278 | // * example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA=='); |
| | 3279 | // * returns 1: 'Kevin van Zonneveld' |
| | 3280 | // mozilla has this native |
| | 3281 | // - but breaks in 2.0.0.12! |
| | 3282 | //if (typeof this.window.atob == 'function') { |
| | 3283 | // return atob(data); |
| | 3284 | //} |
| | 3285 | var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; |
| | 3286 | var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, |
| | 3287 | ac = 0, |
| | 3288 | dec = "", |
| | 3289 | tmp_arr = []; |
| | 3290 | |
| | 3291 | if (!data) { |
| | 3292 | return data; |
| | 3293 | } |
| | 3294 | |
| | 3295 | data += ''; |
| | 3296 | |
| | 3297 | do { // unpack four hexets into three octets using index points in b64 |
| | 3298 | h1 = b64.indexOf(data.charAt(i++)); |
| | 3299 | h2 = b64.indexOf(data.charAt(i++)); |
| | 3300 | h3 = b64.indexOf(data.charAt(i++)); |
| | 3301 | h4 = b64.indexOf(data.charAt(i++)); |
| | 3302 | |
| | 3303 | bits = h1 << 18 | h2 << 12 | h3 << 6 | h4; |
| | 3304 | |
| | 3305 | o1 = bits >> 16 & 0xff; |
| | 3306 | o2 = bits >> 8 & 0xff; |
| | 3307 | o3 = bits & 0xff; |
| | 3308 | |
| | 3309 | if (h3 == 64) { |
| | 3310 | tmp_arr[ac++] = String.fromCharCode(o1); |
| | 3311 | } else if (h4 == 64) { |
| | 3312 | tmp_arr[ac++] = String.fromCharCode(o1, o2); |
| | 3313 | } else { |
| | 3314 | tmp_arr[ac++] = String.fromCharCode(o1, o2, o3); |
| | 3315 | } |
| | 3316 | } while (i < data.length); |
| | 3317 | |
| | 3318 | dec = tmp_arr.join(''); |
| | 3319 | |
| | 3320 | return utf8 ? utf8_decode(dec) : dec; |
| | 3321 | }; |
| | 3322 | |
| | 3323 | /** |
| | 3324 | Base64 encode string (uses browser's default method if available), |
| | 3325 | from: https://raw.github.com/kvz/phpjs/master/functions/url/base64_encode.js |
| | 3326 | |
| | 3327 | @method btoa |
| | 3328 | @static |
| | 3329 | @param {String} data String to encode |
| | 3330 | @return {String} Base64 encoded string |
| | 3331 | */ |
| | 3332 | var btoa = function(data, utf8) { |
| | 3333 | if (utf8) { |
| | 3334 | data = utf8_encode(data); |
| | 3335 | } |
| | 3336 | |
| | 3337 | if (typeof(window.btoa) === 'function') { |
| | 3338 | return window.btoa(data); |
| | 3339 | } |
| | 3340 | |
| | 3341 | // http://kevin.vanzonneveld.net |
| | 3342 | // + original by: Tyler Akins (http://rumkin.com) |
| | 3343 | // + improved by: Bayron Guevara |
| | 3344 | // + improved by: Thunder.m |
| | 3345 | // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) |
| | 3346 | // + bugfixed by: Pellentesque Malesuada |
| | 3347 | // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) |
| | 3348 | // + improved by: Rafał Kukawski (http://kukawski.pl) |
| | 3349 | // * example 1: base64_encode('Kevin van Zonneveld'); |
| | 3350 | // * returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA==' |
| | 3351 | // mozilla has this native |
| | 3352 | // - but breaks in 2.0.0.12! |
| | 3353 | var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; |
| | 3354 | var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, |
| | 3355 | ac = 0, |
| | 3356 | enc = "", |
| | 3357 | tmp_arr = []; |
| | 3358 | |
| | 3359 | if (!data) { |
| | 3360 | return data; |
| | 3361 | } |
| | 3362 | |
| | 3363 | do { // pack three octets into four hexets |
| | 3364 | o1 = data.charCodeAt(i++); |
| | 3365 | o2 = data.charCodeAt(i++); |
| | 3366 | o3 = data.charCodeAt(i++); |
| | 3367 | |
| | 3368 | bits = o1 << 16 | o2 << 8 | o3; |
| | 3369 | |
| | 3370 | h1 = bits >> 18 & 0x3f; |
| | 3371 | h2 = bits >> 12 & 0x3f; |
| | 3372 | h3 = bits >> 6 & 0x3f; |
| | 3373 | h4 = bits & 0x3f; |
| | 3374 | |
| | 3375 | // use hexets to index into b64, and append result to encoded string |
| | 3376 | tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); |
| | 3377 | } while (i < data.length); |
| | 3378 | |
| | 3379 | enc = tmp_arr.join(''); |
| | 3380 | |
| | 3381 | var r = data.length % 3; |
| | 3382 | |
| | 3383 | return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3); |
| | 3384 | }; |
| | 3385 | |
| | 3386 | |
| | 3387 | return { |
| | 3388 | utf8_encode: utf8_encode, |
| | 3389 | utf8_decode: utf8_decode, |
| | 3390 | atob: atob, |
| | 3391 | btoa: btoa |
| | 3392 | }; |
| | 3393 | }); |
| | 3394 | |
| | 3395 | // Included from: src/javascript/file/Blob.js |
| | 3396 | |
| | 3397 | /** |
| | 3398 | * Blob.js |
| | 3399 | * |
| | 3400 | * Copyright 2013, Moxiecode Systems AB |
| | 3401 | * Released under GPL License. |
| | 3402 | * |
| | 3403 | * License: http://www.plupload.com/license |
| | 3404 | * Contributing: http://www.plupload.com/contributing |
| | 3405 | */ |
| | 3406 | |
| | 3407 | define('moxie/file/Blob', [ |
| | 3408 | 'moxie/core/utils/Basic', |
| | 3409 | 'moxie/core/utils/Encode', |
| | 3410 | 'moxie/runtime/RuntimeClient' |
| | 3411 | ], function(Basic, Encode, RuntimeClient) { |
| | 3412 | |
| | 3413 | var blobpool = {}; |
| | 3414 | |
| | 3415 | /** |
| | 3416 | @class Blob |
| | 3417 | @constructor |
| | 3418 | @param {String} ruid Unique id of the runtime, to which this blob belongs to |
| | 3419 | @param {Object} blob Object "Native" blob object, as it is represented in the runtime |
| | 3420 | */ |
| | 3421 | function Blob(ruid, blob) { |
| | 3422 | |
| | 3423 | function _sliceDetached(start, end, type) { |
| | 3424 | var blob, data = blobpool[this.uid]; |
| | 3425 | |
| | 3426 | if (Basic.typeOf(data) !== 'string' || !data.length) { |
| | 3427 | return null; // or throw exception |
| | 3428 | } |
| | 3429 | |
| | 3430 | blob = new Blob(null, { |
| | 3431 | type: type, |
| | 3432 | size: end - start |
| | 3433 | }); |
| | 3434 | blob.detach(data.substr(start, blob.size)); |
| | 3435 | |
| | 3436 | return blob; |
| | 3437 | } |
| | 3438 | |
| | 3439 | RuntimeClient.call(this); |
| | 3440 | |
| | 3441 | if (ruid) { |
| | 3442 | this.connectRuntime(ruid); |
| | 3443 | } |
| | 3444 | |
| | 3445 | if (!blob) { |
| | 3446 | blob = {}; |
| | 3447 | } else if (Basic.typeOf(blob) === 'string') { // dataUrl or binary string |
| | 3448 | blob = { data: blob }; |
| | 3449 | } |
| | 3450 | |
| | 3451 | Basic.extend(this, { |
| | 3452 | |
| | 3453 | /** |
| | 3454 | Unique id of the component |
| | 3455 | |
| | 3456 | @property uid |
| | 3457 | @type {String} |
| | 3458 | */ |
| | 3459 | uid: blob.uid || Basic.guid('uid_'), |
| | 3460 | |
| | 3461 | /** |
| | 3462 | Unique id of the connected runtime, if falsy, then runtime will have to be initialized |
| | 3463 | before this Blob can be used, modified or sent |
| | 3464 | |
| | 3465 | @property ruid |
| | 3466 | @type {String} |
| | 3467 | */ |
| | 3468 | ruid: ruid, |
| | 3469 | |
| | 3470 | /** |
| | 3471 | Size of blob |
| | 3472 | |
| | 3473 | @property size |
| | 3474 | @type {Number} |
| | 3475 | @default 0 |
| | 3476 | */ |
| | 3477 | size: blob.size || 0, |
| | 3478 | |
| | 3479 | /** |
| | 3480 | Mime type of blob |
| | 3481 | |
| | 3482 | @property type |
| | 3483 | @type {String} |
| | 3484 | @default '' |
| | 3485 | */ |
| | 3486 | type: blob.type || '', |
| | 3487 | |
| | 3488 | /** |
| | 3489 | @method slice |
| | 3490 | @param {Number} [start=0] |
| | 3491 | */ |
| | 3492 | slice: function(start, end, type) { |
| | 3493 | if (this.isDetached()) { |
| | 3494 | return _sliceDetached.apply(this, arguments); |
| | 3495 | } |
| | 3496 | return this.getRuntime().exec.call(this, 'Blob', 'slice', this.getSource(), start, end, type); |
| | 3497 | }, |
| | 3498 | |
| | 3499 | /** |
| | 3500 | Returns "native" blob object (as it is represented in connected runtime) or null if not found |
| | 3501 | |
| | 3502 | @method getSource |
| | 3503 | @return {Blob} Returns "native" blob object or null if not found |
| | 3504 | */ |
| | 3505 | getSource: function() { |
| | 3506 | if (!blobpool[this.uid]) { |
| | 3507 | return null; |
| | 3508 | } |
| | 3509 | return blobpool[this.uid]; |
| | 3510 | }, |
| | 3511 | |
| | 3512 | /** |
| | 3513 | Detaches blob from any runtime that it depends on and initialize with standalone value |
| | 3514 | |
| | 3515 | @method detach |
| | 3516 | @protected |
| | 3517 | @param {DOMString} [data=''] Standalone value |
| | 3518 | */ |
| | 3519 | detach: function(data) { |
| | 3520 | if (this.ruid) { |
| | 3521 | this.getRuntime().exec.call(this, 'Blob', 'destroy'); |
| | 3522 | this.disconnectRuntime(); |
| | 3523 | this.ruid = null; |
| | 3524 | } |
| | 3525 | |
| | 3526 | data = data || ''; |
| | 3527 | |
| | 3528 | // if dataUrl, convert to binary string |
| | 3529 | if (data.substr(0, 5) == 'data:') { |
| | 3530 | var base64Offset = data.indexOf(';base64,'); |
| | 3531 | this.type = data.substring(5, base64Offset); |
| | 3532 | data = Encode.atob(data.substring(base64Offset + 8)); |
| | 3533 | } |
| | 3534 | |
| | 3535 | this.size = data.length; |
| | 3536 | |
| | 3537 | blobpool[this.uid] = data; |
| | 3538 | }, |
| | 3539 | |
| | 3540 | /** |
| | 3541 | Checks if blob is standalone (detached of any runtime) |
| | 3542 | |
| | 3543 | @method isDetached |
| | 3544 | @protected |
| | 3545 | @return {Boolean} |
| | 3546 | */ |
| | 3547 | isDetached: function() { |
| | 3548 | return !this.ruid && Basic.typeOf(blobpool[this.uid]) === 'string'; |
| | 3549 | }, |
| | 3550 | |
| | 3551 | /** |
| | 3552 | Destroy Blob and free any resources it was using |
| | 3553 | |
| | 3554 | @method destroy |
| | 3555 | */ |
| | 3556 | destroy: function() { |
| | 3557 | this.detach(); |
| | 3558 | delete blobpool[this.uid]; |
| | 3559 | } |
| | 3560 | }); |
| | 3561 | |
| | 3562 | |
| | 3563 | if (blob.data) { |
| | 3564 | this.detach(blob.data); // auto-detach if payload has been passed |
| | 3565 | } else { |
| | 3566 | blobpool[this.uid] = blob; |
| | 3567 | } |
| | 3568 | } |
| | 3569 | |
| | 3570 | return Blob; |
| | 3571 | }); |
| | 3572 | |
| | 3573 | // Included from: src/javascript/file/File.js |
| | 3574 | |
| | 3575 | /** |
| | 3576 | * File.js |
| | 3577 | * |
| | 3578 | * Copyright 2013, Moxiecode Systems AB |
| | 3579 | * Released under GPL License. |
| | 3580 | * |
| | 3581 | * License: http://www.plupload.com/license |
| | 3582 | * Contributing: http://www.plupload.com/contributing |
| | 3583 | */ |
| | 3584 | |
| | 3585 | define('moxie/file/File', [ |
| | 3586 | 'moxie/core/utils/Basic', |
| | 3587 | 'moxie/core/utils/Mime', |
| | 3588 | 'moxie/file/Blob' |
| | 3589 | ], function(Basic, Mime, Blob) { |
| | 3590 | /** |
| | 3591 | @class File |
| | 3592 | @extends Blob |
| | 3593 | @constructor |
| | 3594 | @param {String} ruid Unique id of the runtime, to which this blob belongs to |
| | 3595 | @param {Object} file Object "Native" file object, as it is represented in the runtime |
| | 3596 | */ |
| | 3597 | function File(ruid, file) { |
| | 3598 | if (!file) { // avoid extra errors in case we overlooked something |
| | 3599 | file = {}; |
| | 3600 | } |
| | 3601 | |
| | 3602 | Blob.apply(this, arguments); |
| | 3603 | |
| | 3604 | if (!this.type) { |
| | 3605 | this.type = Mime.getFileMime(file.name); |
| | 3606 | } |
| | 3607 | |
| | 3608 | // sanitize file name or generate new one |
| | 3609 | var name; |
| | 3610 | if (file.name) { |
| | 3611 | name = file.name.replace(/\\/g, '/'); |
| | 3612 | name = name.substr(name.lastIndexOf('/') + 1); |
| | 3613 | } else if (this.type) { |
| | 3614 | var prefix = this.type.split('/')[0]; |
| | 3615 | name = Basic.guid((prefix !== '' ? prefix : 'file') + '_'); |
| | 3616 | |
| | 3617 | if (Mime.extensions[this.type]) { |
| | 3618 | name += '.' + Mime.extensions[this.type][0]; // append proper extension if possible |
| | 3619 | } |
| | 3620 | } |
| | 3621 | |
| | 3622 | |
| | 3623 | Basic.extend(this, { |
| | 3624 | /** |
| | 3625 | File name |
| | 3626 | |
| | 3627 | @property name |
| | 3628 | @type {String} |
| | 3629 | @default UID |
| | 3630 | */ |
| | 3631 | name: name || Basic.guid('file_'), |
| | 3632 | |
| | 3633 | /** |
| | 3634 | Relative path to the file inside a directory |
| | 3635 | |
| | 3636 | @property relativePath |
| | 3637 | @type {String} |
| | 3638 | @default '' |
| | 3639 | */ |
| | 3640 | relativePath: '', |
| | 3641 | |
| | 3642 | /** |
| | 3643 | Date of last modification |
| | 3644 | |
| | 3645 | @property lastModifiedDate |
| | 3646 | @type {String} |
| | 3647 | @default now |
| | 3648 | */ |
| | 3649 | lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString() // Thu Aug 23 2012 19:40:00 GMT+0400 (GET) |
| | 3650 | }); |
| | 3651 | } |
| | 3652 | |
| | 3653 | File.prototype = Blob.prototype; |
| | 3654 | |
| | 3655 | return File; |
| | 3656 | }); |
| | 3657 | |
| | 3658 | // Included from: src/javascript/file/FileDrop.js |
| | 3659 | |
| | 3660 | /** |
| | 3661 | * FileDrop.js |
| | 3662 | * |
| | 3663 | * Copyright 2013, Moxiecode Systems AB |
| | 3664 | * Released under GPL License. |
| | 3665 | * |
| | 3666 | * License: http://www.plupload.com/license |
| | 3667 | * Contributing: http://www.plupload.com/contributing |
| | 3668 | */ |
| | 3669 | |
| | 3670 | define('moxie/file/FileDrop', [ |
| | 3671 | 'moxie/core/I18n', |
| | 3672 | 'moxie/core/utils/Dom', |
| | 3673 | 'moxie/core/Exceptions', |
| | 3674 | 'moxie/core/utils/Basic', |
| | 3675 | 'moxie/core/utils/Env', |
| | 3676 | 'moxie/file/File', |
| | 3677 | 'moxie/runtime/RuntimeClient', |
| | 3678 | 'moxie/core/EventTarget', |
| | 3679 | 'moxie/core/utils/Mime' |
| | 3680 | ], function(I18n, Dom, x, Basic, Env, File, RuntimeClient, EventTarget, Mime) { |
| | 3681 | /** |
| | 3682 | Turn arbitrary DOM element to a drop zone accepting files. Converts selected files to _File_ objects, to be used |
| | 3683 | in conjunction with _Image_, preloaded in memory with _FileReader_ or uploaded to a server through |
| | 3684 | _XMLHttpRequest_. |
| | 3685 | |
| | 3686 | @example |
| | 3687 | <div id="drop_zone"> |
| | 3688 | Drop files here |
| | 3689 | </div> |
| | 3690 | <br /> |
| | 3691 | <div id="filelist"></div> |
| | 3692 | |
| | 3693 | <script type="text/javascript"> |
| | 3694 | var fileDrop = new mOxie.FileDrop('drop_zone'), fileList = mOxie.get('filelist'); |
| | 3695 | |
| | 3696 | fileDrop.ondrop = function() { |
| | 3697 | mOxie.each(this.files, function(file) { |
| | 3698 | fileList.innerHTML += '<div>' + file.name + '</div>'; |
| | 3699 | }); |
| | 3700 | }; |
| | 3701 | |
| | 3702 | fileDrop.init(); |
| | 3703 | </script> |
| | 3704 | |
| | 3705 | @class FileDrop |
| | 3706 | @constructor |
| | 3707 | @extends EventTarget |
| | 3708 | @uses RuntimeClient |
| | 3709 | @param {Object|String} options If options has typeof string, argument is considered as options.drop_zone |
| | 3710 | @param {String|DOMElement} options.drop_zone DOM Element to turn into a drop zone |
| | 3711 | @param {Array} [options.accept] Array of mime types to accept. By default accepts all |
| | 3712 | @param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support |
| | 3713 | */ |
| | 3714 | var dispatches = [ |
| | 3715 | /** |
| | 3716 | Dispatched when runtime is connected and drop zone is ready to accept files. |
| | 3717 | |
| | 3718 | @event ready |
| | 3719 | @param {Object} event |
| | 3720 | */ |
| | 3721 | 'ready', |
| | 3722 | |
| | 3723 | /** |
| | 3724 | Dispatched when dragging cursor enters the drop zone. |
| | 3725 | |
| | 3726 | @event dragenter |
| | 3727 | @param {Object} event |
| | 3728 | */ |
| | 3729 | 'dragenter', |
| | 3730 | |
| | 3731 | /** |
| | 3732 | Dispatched when dragging cursor leaves the drop zone. |
| | 3733 | |
| | 3734 | @event dragleave |
| | 3735 | @param {Object} event |
| | 3736 | */ |
| | 3737 | 'dragleave', |
| | 3738 | |
| | 3739 | /** |
| | 3740 | Dispatched when file is dropped onto the drop zone. |
| | 3741 | |
| | 3742 | @event drop |
| | 3743 | @param {Object} event |
| | 3744 | */ |
| | 3745 | 'drop', |
| | 3746 | |
| | 3747 | /** |
| | 3748 | Dispatched if error occurs. |
| | 3749 | |
| | 3750 | @event error |
| | 3751 | @param {Object} event |
| | 3752 | */ |
| | 3753 | 'error' |
| | 3754 | ]; |
| | 3755 | |
| | 3756 | function FileDrop(options) { |
| | 3757 | if (MXI_DEBUG) { |
| | 3758 | Env.log("Instantiating FileDrop..."); |
| | 3759 | } |
| | 3760 | |
| | 3761 | var self = this, defaults; |
| | 3762 | |
| | 3763 | // if flat argument passed it should be drop_zone id |
| | 3764 | if (typeof(options) === 'string') { |
| | 3765 | options = { drop_zone : options }; |
| | 3766 | } |
| | 3767 | |
| | 3768 | // figure out the options |
| | 3769 | defaults = { |
| | 3770 | accept: [{ |
| | 3771 | title: I18n.translate('All Files'), |
| | 3772 | extensions: '*' |
| | 3773 | }], |
| | 3774 | required_caps: { |
| | 3775 | drag_and_drop: true |
| | 3776 | } |
| | 3777 | }; |
| | 3778 | |
| | 3779 | options = typeof(options) === 'object' ? Basic.extend({}, defaults, options) : defaults; |
| | 3780 | |
| | 3781 | // this will help us to find proper default container |
| | 3782 | options.container = Dom.get(options.drop_zone) || document.body; |
| | 3783 | |
| | 3784 | // make container relative, if it is not |
| | 3785 | if (Dom.getStyle(options.container, 'position') === 'static') { |
| | 3786 | options.container.style.position = 'relative'; |
| | 3787 | } |
| | 3788 | |
| | 3789 | // normalize accept option (could be list of mime types or array of title/extensions pairs) |
| | 3790 | if (typeof(options.accept) === 'string') { |
| | 3791 | options.accept = Mime.mimes2extList(options.accept); |
| | 3792 | } |
| | 3793 | |
| | 3794 | RuntimeClient.call(self); |
| | 3795 | |
| | 3796 | Basic.extend(self, { |
| | 3797 | uid: Basic.guid('uid_'), |
| | 3798 | |
| | 3799 | ruid: null, |
| | 3800 | |
| | 3801 | files: null, |
| | 3802 | |
| | 3803 | init: function() { |
| | 3804 | self.bind('RuntimeInit', function(e, runtime) { |
| | 3805 | self.ruid = runtime.uid; |
| | 3806 | runtime.exec.call(self, 'FileDrop', 'init', options); |
| | 3807 | self.dispatchEvent('ready'); |
| | 3808 | }); |
| | 3809 | |
| | 3810 | // runtime needs: options.required_features, options.runtime_order and options.container |
| | 3811 | self.connectRuntime(options); // throws RuntimeError |
| | 3812 | }, |
| | 3813 | |
| | 3814 | destroy: function() { |
| | 3815 | var runtime = this.getRuntime(); |
| | 3816 | if (runtime) { |
| | 3817 | runtime.exec.call(this, 'FileDrop', 'destroy'); |
| | 3818 | this.disconnectRuntime(); |
| | 3819 | } |
| | 3820 | this.files = null; |
| | 3821 | |
| | 3822 | this.unbindAll(); |
| | 3823 | } |
| | 3824 | }); |
| | 3825 | |
| | 3826 | this.handleEventProps(dispatches); |
| | 3827 | } |
| | 3828 | |
| | 3829 | FileDrop.prototype = EventTarget.instance; |
| | 3830 | |
| | 3831 | return FileDrop; |
| | 3832 | }); |
| | 3833 | |
| | 3834 | // Included from: src/javascript/file/FileReader.js |
| | 3835 | |
| | 3836 | /** |
| | 3837 | * FileReader.js |
| | 3838 | * |
| | 3839 | * Copyright 2013, Moxiecode Systems AB |
| | 3840 | * Released under GPL License. |
| | 3841 | * |
| | 3842 | * License: http://www.plupload.com/license |
| | 3843 | * Contributing: http://www.plupload.com/contributing |
| | 3844 | */ |
| | 3845 | |
| | 3846 | define('moxie/file/FileReader', [ |
| | 3847 | 'moxie/core/utils/Basic', |
| | 3848 | 'moxie/core/utils/Encode', |
| | 3849 | 'moxie/core/Exceptions', |
| | 3850 | 'moxie/core/EventTarget', |
| | 3851 | 'moxie/file/Blob', |
| | 3852 | 'moxie/runtime/RuntimeClient' |
| | 3853 | ], function(Basic, Encode, x, EventTarget, Blob, RuntimeClient) { |
| | 3854 | /** |
| | 3855 | Utility for preloading o.Blob/o.File objects in memory. By design closely follows [W3C FileReader](http://www.w3.org/TR/FileAPI/#dfn-filereader) |
| | 3856 | interface. Where possible uses native FileReader, where - not falls back to shims. |
| | 3857 | |
| | 3858 | @class FileReader |
| | 3859 | @constructor FileReader |
| | 3860 | @extends EventTarget |
| | 3861 | @uses RuntimeClient |
| | 3862 | */ |
| | 3863 | var dispatches = [ |
| | 3864 | |
| | 3865 | /** |
| | 3866 | Dispatched when the read starts. |
| | 3867 | |
| | 3868 | @event loadstart |
| | 3869 | @param {Object} event |
| | 3870 | */ |
| | 3871 | 'loadstart', |
| | 3872 | |
| | 3873 | /** |
| | 3874 | Dispatched while reading (and decoding) blob, and reporting partial Blob data (progess.loaded/progress.total). |
| | 3875 | |
| | 3876 | @event progress |
| | 3877 | @param {Object} event |
| | 3878 | */ |
| | 3879 | 'progress', |
| | 3880 | |
| | 3881 | /** |
| | 3882 | Dispatched when the read has successfully completed. |
| | 3883 | |
| | 3884 | @event load |
| | 3885 | @param {Object} event |
| | 3886 | */ |
| | 3887 | 'load', |
| | 3888 | |
| | 3889 | /** |
| | 3890 | Dispatched when the read has been aborted. For instance, by invoking the abort() method. |
| | 3891 | |
| | 3892 | @event abort |
| | 3893 | @param {Object} event |
| | 3894 | */ |
| | 3895 | 'abort', |
| | 3896 | |
| | 3897 | /** |
| | 3898 | Dispatched when the read has failed. |
| | 3899 | |
| | 3900 | @event error |
| | 3901 | @param {Object} event |
| | 3902 | */ |
| | 3903 | 'error', |
| | 3904 | |
| | 3905 | /** |
| | 3906 | Dispatched when the request has completed (either in success or failure). |
| | 3907 | |
| | 3908 | @event loadend |
| | 3909 | @param {Object} event |
| | 3910 | */ |
| | 3911 | 'loadend' |
| | 3912 | ]; |
| | 3913 | |
| | 3914 | function FileReader() { |
| | 3915 | |
| | 3916 | RuntimeClient.call(this); |
| | 3917 | |
| | 3918 | Basic.extend(this, { |
| | 3919 | /** |
| | 3920 | UID of the component instance. |
| | 3921 | |
| | 3922 | @property uid |
| | 3923 | @type {String} |
| | 3924 | */ |
| | 3925 | uid: Basic.guid('uid_'), |
| | 3926 | |
| | 3927 | /** |
| | 3928 | Contains current state of FileReader object. Can take values of FileReader.EMPTY, FileReader.LOADING |
| | 3929 | and FileReader.DONE. |
| | 3930 | |
| | 3931 | @property readyState |
| | 3932 | @type {Number} |
| | 3933 | @default FileReader.EMPTY |
| | 3934 | */ |
| | 3935 | readyState: FileReader.EMPTY, |
| | 3936 | |
| | 3937 | /** |
| | 3938 | Result of the successful read operation. |
| | 3939 | |
| | 3940 | @property result |
| | 3941 | @type {String} |
| | 3942 | */ |
| | 3943 | result: null, |
| | 3944 | |
| | 3945 | /** |
| | 3946 | Stores the error of failed asynchronous read operation. |
| | 3947 | |
| | 3948 | @property error |
| | 3949 | @type {DOMError} |
| | 3950 | */ |
| | 3951 | error: null, |
| | 3952 | |
| | 3953 | /** |
| | 3954 | Initiates reading of File/Blob object contents to binary string. |
| | 3955 | |
| | 3956 | @method readAsBinaryString |
| | 3957 | @param {Blob|File} blob Object to preload |
| | 3958 | */ |
| | 3959 | readAsBinaryString: function(blob) { |
| | 3960 | _read.call(this, 'readAsBinaryString', blob); |
| | 3961 | }, |
| | 3962 | |
| | 3963 | /** |
| | 3964 | Initiates reading of File/Blob object contents to dataURL string. |
| | 3965 | |
| | 3966 | @method readAsDataURL |
| | 3967 | @param {Blob|File} blob Object to preload |
| | 3968 | */ |
| | 3969 | readAsDataURL: function(blob) { |
| | 3970 | _read.call(this, 'readAsDataURL', blob); |
| | 3971 | }, |
| | 3972 | |
| | 3973 | /** |
| | 3974 | Initiates reading of File/Blob object contents to string. |
| | 3975 | |
| | 3976 | @method readAsText |
| | 3977 | @param {Blob|File} blob Object to preload |
| | 3978 | */ |
| | 3979 | readAsText: function(blob) { |
| | 3980 | _read.call(this, 'readAsText', blob); |
| | 3981 | }, |
| | 3982 | |
| | 3983 | /** |
| | 3984 | Aborts preloading process. |
| | 3985 | |
| | 3986 | @method abort |
| | 3987 | */ |
| | 3988 | abort: function() { |
| | 3989 | this.result = null; |
| | 3990 | |
| | 3991 | if (Basic.inArray(this.readyState, [FileReader.EMPTY, FileReader.DONE]) !== -1) { |
| | 3992 | return; |
| | 3993 | } else if (this.readyState === FileReader.LOADING) { |
| | 3994 | this.readyState = FileReader.DONE; |
| | 3995 | } |
| | 3996 | |
| | 3997 | this.exec('FileReader', 'abort'); |
| | 3998 | |
| | 3999 | this.trigger('abort'); |
| | 4000 | this.trigger('loadend'); |
| | 4001 | }, |
| | 4002 | |
| | 4003 | /** |
| | 4004 | Destroy component and release resources. |
| | 4005 | |
| | 4006 | @method destroy |
| | 4007 | */ |
| | 4008 | destroy: function() { |
| | 4009 | this.abort(); |
| | 4010 | this.exec('FileReader', 'destroy'); |
| | 4011 | this.disconnectRuntime(); |
| | 4012 | this.unbindAll(); |
| | 4013 | } |
| | 4014 | }); |
| | 4015 | |
| | 4016 | // uid must already be assigned |
| | 4017 | this.handleEventProps(dispatches); |
| | 4018 | |
| | 4019 | this.bind('Error', function(e, err) { |
| | 4020 | this.readyState = FileReader.DONE; |
| | 4021 | this.error = err; |
| | 4022 | }, 999); |
| | 4023 | |
| | 4024 | this.bind('Load', function(e) { |
| | 4025 | this.readyState = FileReader.DONE; |
| | 4026 | }, 999); |
| | 4027 | |
| | 4028 | |
| | 4029 | function _read(op, blob) { |
| | 4030 | var self = this; |
| | 4031 | |
| | 4032 | this.trigger('loadstart'); |
| | 4033 | |
| | 4034 | if (this.readyState === FileReader.LOADING) { |
| | 4035 | this.trigger('error', new x.DOMException(x.DOMException.INVALID_STATE_ERR)); |
| | 4036 | this.trigger('loadend'); |
| | 4037 | return; |
| | 4038 | } |
| | 4039 | |
| | 4040 | // if source is not o.Blob/o.File |
| | 4041 | if (!(blob instanceof Blob)) { |
| | 4042 | this.trigger('error', new x.DOMException(x.DOMException.NOT_FOUND_ERR)); |
| | 4043 | this.trigger('loadend'); |
| | 4044 | return; |
| | 4045 | } |
| | 4046 | |
| | 4047 | this.result = null; |
| | 4048 | this.readyState = FileReader.LOADING; |
| | 4049 | |
| | 4050 | if (blob.isDetached()) { |
| | 4051 | var src = blob.getSource(); |
| | 4052 | switch (op) { |
| | 4053 | case 'readAsText': |
| | 4054 | case 'readAsBinaryString': |
| | 4055 | this.result = src; |
| | 4056 | break; |
| | 4057 | case 'readAsDataURL': |
| | 4058 | this.result = 'data:' + blob.type + ';base64,' + Encode.btoa(src); |
| | 4059 | break; |
| | 4060 | } |
| | 4061 | this.readyState = FileReader.DONE; |
| | 4062 | this.trigger('load'); |
| | 4063 | this.trigger('loadend'); |
| | 4064 | } else { |
| | 4065 | this.connectRuntime(blob.ruid); |
| | 4066 | this.exec('FileReader', 'read', op, blob); |
| | 4067 | } |
| | 4068 | } |
| | 4069 | } |
| | 4070 | |
| | 4071 | /** |
| | 4072 | Initial FileReader state |
| | 4073 | |
| | 4074 | @property EMPTY |
| | 4075 | @type {Number} |
| | 4076 | @final |
| | 4077 | @static |
| | 4078 | @default 0 |
| | 4079 | */ |
| | 4080 | FileReader.EMPTY = 0; |
| | 4081 | |
| | 4082 | /** |
| | 4083 | FileReader switches to this state when it is preloading the source |
| | 4084 | |
| | 4085 | @property LOADING |
| | 4086 | @type {Number} |
| | 4087 | @final |
| | 4088 | @static |
| | 4089 | @default 1 |
| | 4090 | */ |
| | 4091 | FileReader.LOADING = 1; |
| | 4092 | |
| | 4093 | /** |
| | 4094 | Preloading is complete, this is a final state |
| | 4095 | |
| | 4096 | @property DONE |
| | 4097 | @type {Number} |
| | 4098 | @final |
| | 4099 | @static |
| | 4100 | @default 2 |
| | 4101 | */ |
| | 4102 | FileReader.DONE = 2; |
| | 4103 | |
| | 4104 | FileReader.prototype = EventTarget.instance; |
| | 4105 | |
| | 4106 | return FileReader; |
| | 4107 | }); |
| | 4108 | |
| | 4109 | // Included from: src/javascript/core/utils/Url.js |
| | 4110 | |
| | 4111 | /** |
| | 4112 | * Url.js |
| | 4113 | * |
| | 4114 | * Copyright 2013, Moxiecode Systems AB |
| | 4115 | * Released under GPL License. |
| | 4116 | * |
| | 4117 | * License: http://www.plupload.com/license |
| | 4118 | * Contributing: http://www.plupload.com/contributing |
| | 4119 | */ |
| | 4120 | |
| | 4121 | define('moxie/core/utils/Url', [], function() { |
| | 4122 | /** |
| | 4123 | Parse url into separate components and fill in absent parts with parts from current url, |
| | 4124 | based on https://raw.github.com/kvz/phpjs/master/functions/url/parse_url.js |
| | 4125 | |
| | 4126 | @method parseUrl |
| | 4127 | @for Utils |
| | 4128 | @static |
| | 4129 | @param {String} url Url to parse (defaults to empty string if undefined) |
| | 4130 | @return {Object} Hash containing extracted uri components |
| | 4131 | */ |
| | 4132 | var parseUrl = function(url, currentUrl) { |
| | 4133 | var key = ['source', 'scheme', 'authority', 'userInfo', 'user', 'pass', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment'] |
| | 4134 | , i = key.length |
| | 4135 | , ports = { |
| | 4136 | http: 80, |
| | 4137 | https: 443 |
| | 4138 | } |
| | 4139 | , uri = {} |
| | 4140 | , regex = /^(?:([^:\/?#]+):)?(?:\/\/()(?:(?:()(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?))?()(?:(()(?:(?:[^?#\/]*\/)*)()(?:[^?#]*))(?:\\?([^#]*))?(?:#(.*))?)/ |
| | 4141 | , m = regex.exec(url || '') |
| | 4142 | ; |
| | 4143 | |
| | 4144 | while (i--) { |
| | 4145 | if (m[i]) { |
| | 4146 | uri[key[i]] = m[i]; |
| | 4147 | } |
| | 4148 | } |
| | 4149 | |
| | 4150 | // when url is relative, we set the origin and the path ourselves |
| | 4151 | if (!uri.scheme) { |
| | 4152 | // come up with defaults |
| | 4153 | if (!currentUrl || typeof(currentUrl) === 'string') { |
| | 4154 | currentUrl = parseUrl(currentUrl || document.location.href); |
| | 4155 | } |
| | 4156 | |
| | 4157 | uri.scheme = currentUrl.scheme; |
| | 4158 | uri.host = currentUrl.host; |
| | 4159 | uri.port = currentUrl.port; |
| | 4160 | |
| | 4161 | var path = ''; |
| | 4162 | // for urls without trailing slash we need to figure out the path |
| | 4163 | if (/^[^\/]/.test(uri.path)) { |
| | 4164 | path = currentUrl.path; |
| | 4165 | // if path ends with a filename, strip it |
| | 4166 | if (/\/[^\/]*\.[^\/]*$/.test(path)) { |
| | 4167 | path = path.replace(/\/[^\/]+$/, '/'); |
| | 4168 | } else { |
| | 4169 | // avoid double slash at the end (see #127) |
| | 4170 | path = path.replace(/\/?$/, '/'); |
| | 4171 | } |
| | 4172 | } |
| | 4173 | uri.path = path + (uri.path || ''); // site may reside at domain.com or domain.com/subdir |
| | 4174 | } |
| | 4175 | |
| | 4176 | if (!uri.port) { |
| | 4177 | uri.port = ports[uri.scheme] || 80; |
| | 4178 | } |
| | 4179 | |
| | 4180 | uri.port = parseInt(uri.port, 10); |
| | 4181 | |
| | 4182 | if (!uri.path) { |
| | 4183 | uri.path = "/"; |
| | 4184 | } |
| | 4185 | |
| | 4186 | delete uri.source; |
| | 4187 | |
| | 4188 | return uri; |
| | 4189 | }; |
| | 4190 | |
| | 4191 | /** |
| | 4192 | Resolve url - among other things will turn relative url to absolute |
| | 4193 | |
| | 4194 | @method resolveUrl |
| | 4195 | @static |
| | 4196 | @param {String|Object} url Either absolute or relative, or a result of parseUrl call |
| | 4197 | @return {String} Resolved, absolute url |
| | 4198 | */ |
| | 4199 | var resolveUrl = function(url) { |
| | 4200 | var ports = { // we ignore default ports |
| | 4201 | http: 80, |
| | 4202 | https: 443 |
| | 4203 | } |
| | 4204 | , urlp = typeof(url) === 'object' ? url : parseUrl(url); |
| | 4205 | ; |
| | 4206 | |
| | 4207 | return urlp.scheme + '://' + urlp.host + (urlp.port !== ports[urlp.scheme] ? ':' + urlp.port : '') + urlp.path + (urlp.query ? urlp.query : ''); |
| | 4208 | }; |
| | 4209 | |
| | 4210 | /** |
| | 4211 | Check if specified url has the same origin as the current document |
| | 4212 | |
| | 4213 | @method hasSameOrigin |
| | 4214 | @param {String|Object} url |
| | 4215 | @return {Boolean} |
| | 4216 | */ |
| | 4217 | var hasSameOrigin = function(url) { |
| | 4218 | function origin(url) { |
| | 4219 | return [url.scheme, url.host, url.port].join('/'); |
| | 4220 | } |
| | 4221 | |
| | 4222 | if (typeof url === 'string') { |
| | 4223 | url = parseUrl(url); |
| | 4224 | } |
| | 4225 | |
| | 4226 | return origin(parseUrl()) === origin(url); |
| | 4227 | }; |
| | 4228 | |
| | 4229 | return { |
| | 4230 | parseUrl: parseUrl, |
| | 4231 | resolveUrl: resolveUrl, |
| | 4232 | hasSameOrigin: hasSameOrigin |
| | 4233 | }; |
| | 4234 | }); |
| | 4235 | |
| | 4236 | // Included from: src/javascript/runtime/RuntimeTarget.js |
| | 4237 | |
| | 4238 | /** |
| | 4239 | * RuntimeTarget.js |
| | 4240 | * |
| | 4241 | * Copyright 2013, Moxiecode Systems AB |
| | 4242 | * Released under GPL License. |
| | 4243 | * |
| | 4244 | * License: http://www.plupload.com/license |
| | 4245 | * Contributing: http://www.plupload.com/contributing |
| | 4246 | */ |
| | 4247 | |
| | 4248 | define('moxie/runtime/RuntimeTarget', [ |
| | 4249 | 'moxie/core/utils/Basic', |
| | 4250 | 'moxie/runtime/RuntimeClient', |
| | 4251 | "moxie/core/EventTarget" |
| | 4252 | ], function(Basic, RuntimeClient, EventTarget) { |
| | 4253 | /** |
| | 4254 | Instance of this class can be used as a target for the events dispatched by shims, |
| | 4255 | when allowing them onto components is for either reason inappropriate |
| | 4256 | |
| | 4257 | @class RuntimeTarget |
| | 4258 | @constructor |
| | 4259 | @protected |
| | 4260 | @extends EventTarget |
| | 4261 | */ |
| | 4262 | function RuntimeTarget() { |
| | 4263 | this.uid = Basic.guid('uid_'); |
| | 4264 | |
| | 4265 | RuntimeClient.call(this); |
| | 4266 | |
| | 4267 | this.destroy = function() { |
| | 4268 | this.disconnectRuntime(); |
| | 4269 | this.unbindAll(); |
| | 4270 | }; |
| | 4271 | } |
| | 4272 | |
| | 4273 | RuntimeTarget.prototype = EventTarget.instance; |
| | 4274 | |
| | 4275 | return RuntimeTarget; |
| | 4276 | }); |
| | 4277 | |
| | 4278 | // Included from: src/javascript/file/FileReaderSync.js |
| | 4279 | |
| | 4280 | /** |
| | 4281 | * FileReaderSync.js |
| | 4282 | * |
| | 4283 | * Copyright 2013, Moxiecode Systems AB |
| | 4284 | * Released under GPL License. |
| | 4285 | * |
| | 4286 | * License: http://www.plupload.com/license |
| | 4287 | * Contributing: http://www.plupload.com/contributing |
| | 4288 | */ |
| | 4289 | |
| | 4290 | define('moxie/file/FileReaderSync', [ |
| | 4291 | 'moxie/core/utils/Basic', |
| | 4292 | 'moxie/runtime/RuntimeClient', |
| | 4293 | 'moxie/core/utils/Encode' |
| | 4294 | ], function(Basic, RuntimeClient, Encode) { |
| | 4295 | /** |
| | 4296 | Synchronous FileReader implementation. Something like this is available in WebWorkers environment, here |
| | 4297 | it can be used to read only preloaded blobs/files and only below certain size (not yet sure what that'd be, |
| | 4298 | but probably < 1mb). Not meant to be used directly by user. |
| | 4299 | |
| | 4300 | @class FileReaderSync |
| | 4301 | @private |
| | 4302 | @constructor |
| | 4303 | */ |
| | 4304 | return function() { |
| | 4305 | RuntimeClient.call(this); |
| | 4306 | |
| | 4307 | Basic.extend(this, { |
| | 4308 | uid: Basic.guid('uid_'), |
| | 4309 | |
| | 4310 | readAsBinaryString: function(blob) { |
| | 4311 | return _read.call(this, 'readAsBinaryString', blob); |
| | 4312 | }, |
| | 4313 | |
| | 4314 | readAsDataURL: function(blob) { |
| | 4315 | return _read.call(this, 'readAsDataURL', blob); |
| | 4316 | }, |
| | 4317 | |
| | 4318 | /*readAsArrayBuffer: function(blob) { |
| | 4319 | return _read.call(this, 'readAsArrayBuffer', blob); |
| | 4320 | },*/ |
| | 4321 | |
| | 4322 | readAsText: function(blob) { |
| | 4323 | return _read.call(this, 'readAsText', blob); |
| | 4324 | } |
| | 4325 | }); |
| | 4326 | |
| | 4327 | function _read(op, blob) { |
| | 4328 | if (blob.isDetached()) { |
| | 4329 | var src = blob.getSource(); |
| | 4330 | switch (op) { |
| | 4331 | case 'readAsBinaryString': |
| | 4332 | return src; |
| | 4333 | case 'readAsDataURL': |
| | 4334 | return 'data:' + blob.type + ';base64,' + Encode.btoa(src); |
| | 4335 | case 'readAsText': |
| | 4336 | var txt = ''; |
| | 4337 | for (var i = 0, length = src.length; i < length; i++) { |
| | 4338 | txt += String.fromCharCode(src[i]); |
| | 4339 | } |
| | 4340 | return txt; |
| | 4341 | } |
| | 4342 | } else { |
| | 4343 | var result = this.connectRuntime(blob.ruid).exec.call(this, 'FileReaderSync', 'read', op, blob); |
| | 4344 | this.disconnectRuntime(); |
| | 4345 | return result; |
| | 4346 | } |
| | 4347 | } |
| | 4348 | }; |
| | 4349 | }); |
| | 4350 | |
| | 4351 | // Included from: src/javascript/xhr/FormData.js |
| | 4352 | |
| | 4353 | /** |
| | 4354 | * FormData.js |
| | 4355 | * |
| | 4356 | * Copyright 2013, Moxiecode Systems AB |
| | 4357 | * Released under GPL License. |
| | 4358 | * |
| | 4359 | * License: http://www.plupload.com/license |
| | 4360 | * Contributing: http://www.plupload.com/contributing |
| | 4361 | */ |
| | 4362 | |
| | 4363 | define("moxie/xhr/FormData", [ |
| | 4364 | "moxie/core/Exceptions", |
| | 4365 | "moxie/core/utils/Basic", |
| | 4366 | "moxie/file/Blob" |
| | 4367 | ], function(x, Basic, Blob) { |
| | 4368 | /** |
| | 4369 | FormData |
| | 4370 | |
| | 4371 | @class FormData |
| | 4372 | @constructor |
| | 4373 | */ |
| | 4374 | function FormData() { |
| | 4375 | var _blob, _fields = []; |
| | 4376 | |
| | 4377 | Basic.extend(this, { |
| | 4378 | /** |
| | 4379 | Append another key-value pair to the FormData object |
| | 4380 | |
| | 4381 | @method append |
| | 4382 | @param {String} name Name for the new field |
| | 4383 | @param {String|Blob|Array|Object} value Value for the field |
| | 4384 | */ |
| | 4385 | append: function(name, value) { |
| | 4386 | var self = this, valueType = Basic.typeOf(value); |
| | 4387 | |
| | 4388 | // according to specs value might be either Blob or String |
| | 4389 | if (value instanceof Blob) { |
| | 4390 | _blob = { |
| | 4391 | name: name, |
| | 4392 | value: value // unfortunately we can only send single Blob in one FormData |
| | 4393 | }; |
| | 4394 | } else if ('array' === valueType) { |
| | 4395 | name += '[]'; |
| | 4396 | |
| | 4397 | Basic.each(value, function(value) { |
| | 4398 | self.append(name, value); |
| | 4399 | }); |
| | 4400 | } else if ('object' === valueType) { |
| | 4401 | Basic.each(value, function(value, key) { |
| | 4402 | self.append(name + '[' + key + ']', value); |
| | 4403 | }); |
| | 4404 | } else if ('null' === valueType || 'undefined' === valueType || 'number' === valueType && isNaN(value)) { |
| | 4405 | self.append(name, "false"); |
| | 4406 | } else { |
| | 4407 | _fields.push({ |
| | 4408 | name: name, |
| | 4409 | value: value.toString() |
| | 4410 | }); |
| | 4411 | } |
| | 4412 | }, |
| | 4413 | |
| | 4414 | /** |
| | 4415 | Checks if FormData contains Blob. |
| | 4416 | |
| | 4417 | @method hasBlob |
| | 4418 | @return {Boolean} |
| | 4419 | */ |
| | 4420 | hasBlob: function() { |
| | 4421 | return !!this.getBlob(); |
| | 4422 | }, |
| | 4423 | |
| | 4424 | /** |
| | 4425 | Retrieves blob. |
| | 4426 | |
| | 4427 | @method getBlob |
| | 4428 | @return {Object} Either Blob if found or null |
| | 4429 | */ |
| | 4430 | getBlob: function() { |
| | 4431 | return _blob && _blob.value || null; |
| | 4432 | }, |
| | 4433 | |
| | 4434 | /** |
| | 4435 | Retrieves blob field name. |
| | 4436 | |
| | 4437 | @method getBlobName |
| | 4438 | @return {String} Either Blob field name or null |
| | 4439 | */ |
| | 4440 | getBlobName: function() { |
| | 4441 | return _blob && _blob.name || null; |
| | 4442 | }, |
| | 4443 | |
| | 4444 | /** |
| | 4445 | Loop over the fields in FormData and invoke the callback for each of them. |
| | 4446 | |
| | 4447 | @method each |
| | 4448 | @param {Function} cb Callback to call for each field |
| | 4449 | */ |
| | 4450 | each: function(cb) { |
| | 4451 | Basic.each(_fields, function(field) { |
| | 4452 | cb(field.value, field.name); |
| | 4453 | }); |
| | 4454 | |
| | 4455 | if (_blob) { |
| | 4456 | cb(_blob.value, _blob.name); |
| | 4457 | } |
| | 4458 | }, |
| | 4459 | |
| | 4460 | destroy: function() { |
| | 4461 | _blob = null; |
| | 4462 | _fields = []; |
| | 4463 | } |
| | 4464 | }); |
| | 4465 | } |
| | 4466 | |
| | 4467 | return FormData; |
| | 4468 | }); |
| | 4469 | |
| | 4470 | // Included from: src/javascript/xhr/XMLHttpRequest.js |
| | 4471 | |
| | 4472 | /** |
| | 4473 | * XMLHttpRequest.js |
| | 4474 | * |
| | 4475 | * Copyright 2013, Moxiecode Systems AB |
| | 4476 | * Released under GPL License. |
| | 4477 | * |
| | 4478 | * License: http://www.plupload.com/license |
| | 4479 | * Contributing: http://www.plupload.com/contributing |
| | 4480 | */ |
| | 4481 | |
| | 4482 | define("moxie/xhr/XMLHttpRequest", [ |
| | 4483 | "moxie/core/utils/Basic", |
| | 4484 | "moxie/core/Exceptions", |
| | 4485 | "moxie/core/EventTarget", |
| | 4486 | "moxie/core/utils/Encode", |
| | 4487 | "moxie/core/utils/Url", |
| | 4488 | "moxie/runtime/Runtime", |
| | 4489 | "moxie/runtime/RuntimeTarget", |
| | 4490 | "moxie/file/Blob", |
| | 4491 | "moxie/file/FileReaderSync", |
| | 4492 | "moxie/xhr/FormData", |
| | 4493 | "moxie/core/utils/Env", |
| | 4494 | "moxie/core/utils/Mime" |
| | 4495 | ], function(Basic, x, EventTarget, Encode, Url, Runtime, RuntimeTarget, Blob, FileReaderSync, FormData, Env, Mime) { |
| | 4496 | |
| | 4497 | var httpCode = { |
| | 4498 | 100: 'Continue', |
| | 4499 | 101: 'Switching Protocols', |
| | 4500 | 102: 'Processing', |
| | 4501 | |
| | 4502 | 200: 'OK', |
| | 4503 | 201: 'Created', |
| | 4504 | 202: 'Accepted', |
| | 4505 | 203: 'Non-Authoritative Information', |
| | 4506 | 204: 'No Content', |
| | 4507 | 205: 'Reset Content', |
| | 4508 | 206: 'Partial Content', |
| | 4509 | 207: 'Multi-Status', |
| | 4510 | 226: 'IM Used', |
| | 4511 | |
| | 4512 | 300: 'Multiple Choices', |
| | 4513 | 301: 'Moved Permanently', |
| | 4514 | 302: 'Found', |
| | 4515 | 303: 'See Other', |
| | 4516 | 304: 'Not Modified', |
| | 4517 | 305: 'Use Proxy', |
| | 4518 | 306: 'Reserved', |
| | 4519 | 307: 'Temporary Redirect', |
| | 4520 | |
| | 4521 | 400: 'Bad Request', |
| | 4522 | 401: 'Unauthorized', |
| | 4523 | 402: 'Payment Required', |
| | 4524 | 403: 'Forbidden', |
| | 4525 | 404: 'Not Found', |
| | 4526 | 405: 'Method Not Allowed', |
| | 4527 | 406: 'Not Acceptable', |
| | 4528 | 407: 'Proxy Authentication Required', |
| | 4529 | 408: 'Request Timeout', |
| | 4530 | 409: 'Conflict', |
| | 4531 | 410: 'Gone', |
| | 4532 | 411: 'Length Required', |
| | 4533 | 412: 'Precondition Failed', |
| | 4534 | 413: 'Request Entity Too Large', |
| | 4535 | 414: 'Request-URI Too Long', |
| | 4536 | 415: 'Unsupported Media Type', |
| | 4537 | 416: 'Requested Range Not Satisfiable', |
| | 4538 | 417: 'Expectation Failed', |
| | 4539 | 422: 'Unprocessable Entity', |
| | 4540 | 423: 'Locked', |
| | 4541 | 424: 'Failed Dependency', |
| | 4542 | 426: 'Upgrade Required', |
| | 4543 | |
| | 4544 | 500: 'Internal Server Error', |
| | 4545 | 501: 'Not Implemented', |
| | 4546 | 502: 'Bad Gateway', |
| | 4547 | 503: 'Service Unavailable', |
| | 4548 | 504: 'Gateway Timeout', |
| | 4549 | 505: 'HTTP Version Not Supported', |
| | 4550 | 506: 'Variant Also Negotiates', |
| | 4551 | 507: 'Insufficient Storage', |
| | 4552 | 510: 'Not Extended' |
| | 4553 | }; |
| | 4554 | |
| | 4555 | function XMLHttpRequestUpload() { |
| | 4556 | this.uid = Basic.guid('uid_'); |
| | 4557 | } |
| | 4558 | |
| | 4559 | XMLHttpRequestUpload.prototype = EventTarget.instance; |
| | 4560 | |
| | 4561 | /** |
| | 4562 | Implementation of XMLHttpRequest |
| | 4563 | |
| | 4564 | @class XMLHttpRequest |
| | 4565 | @constructor |
| | 4566 | @uses RuntimeClient |
| | 4567 | @extends EventTarget |
| | 4568 | */ |
| | 4569 | var dispatches = [ |
| | 4570 | 'loadstart', |
| | 4571 | |
| | 4572 | 'progress', |
| | 4573 | |
| | 4574 | 'abort', |
| | 4575 | |
| | 4576 | 'error', |
| | 4577 | |
| | 4578 | 'load', |
| | 4579 | |
| | 4580 | 'timeout', |
| | 4581 | |
| | 4582 | 'loadend' |
| | 4583 | |
| | 4584 | // readystatechange (for historical reasons) |
| | 4585 | ]; |
| | 4586 | |
| | 4587 | var NATIVE = 1, RUNTIME = 2; |
| | 4588 | |
| | 4589 | function XMLHttpRequest() { |
| | 4590 | var self = this, |
| | 4591 | // this (together with _p() @see below) is here to gracefully upgrade to setter/getter syntax where possible |
| | 4592 | props = { |
| | 4593 | /** |
| | 4594 | The amount of milliseconds a request can take before being terminated. Initially zero. Zero means there is no timeout. |
| | 4595 | |
| | 4596 | @property timeout |
| | 4597 | @type Number |
| | 4598 | @default 0 |
| | 4599 | */ |
| | 4600 | timeout: 0, |
| | 4601 | |
| | 4602 | /** |
| | 4603 | Current state, can take following values: |
| | 4604 | UNSENT (numeric value 0) |
| | 4605 | The object has been constructed. |
| | 4606 | |
| | 4607 | OPENED (numeric value 1) |
| | 4608 | The open() method has been successfully invoked. During this state request headers can be set using setRequestHeader() and the request can be made using the send() method. |
| | 4609 | |
| | 4610 | HEADERS_RECEIVED (numeric value 2) |
| | 4611 | All redirects (if any) have been followed and all HTTP headers of the final response have been received. Several response members of the object are now available. |
| | 4612 | |
| | 4613 | LOADING (numeric value 3) |
| | 4614 | The response entity body is being received. |
| | 4615 | |
| | 4616 | DONE (numeric value 4) |
| | 4617 | |
| | 4618 | @property readyState |
| | 4619 | @type Number |
| | 4620 | @default 0 (UNSENT) |
| | 4621 | */ |
| | 4622 | readyState: XMLHttpRequest.UNSENT, |
| | 4623 | |
| | 4624 | /** |
| | 4625 | True when user credentials are to be included in a cross-origin request. False when they are to be excluded |
| | 4626 | in a cross-origin request and when cookies are to be ignored in its response. Initially false. |
| | 4627 | |
| | 4628 | @property withCredentials |
| | 4629 | @type Boolean |
| | 4630 | @default false |
| | 4631 | */ |
| | 4632 | withCredentials: false, |
| | 4633 | |
| | 4634 | /** |
| | 4635 | Returns the HTTP status code. |
| | 4636 | |
| | 4637 | @property status |
| | 4638 | @type Number |
| | 4639 | @default 0 |
| | 4640 | */ |
| | 4641 | status: 0, |
| | 4642 | |
| | 4643 | /** |
| | 4644 | Returns the HTTP status text. |
| | 4645 | |
| | 4646 | @property statusText |
| | 4647 | @type String |
| | 4648 | */ |
| | 4649 | statusText: "", |
| | 4650 | |
| | 4651 | /** |
| | 4652 | Returns the response type. Can be set to change the response type. Values are: |
| | 4653 | the empty string (default), "arraybuffer", "blob", "document", "json", and "text". |
| | 4654 | |
| | 4655 | @property responseType |
| | 4656 | @type String |
| | 4657 | */ |
| | 4658 | responseType: "", |
| | 4659 | |
| | 4660 | /** |
| | 4661 | Returns the document response entity body. |
| | 4662 | |
| | 4663 | Throws an "InvalidStateError" exception if responseType is not the empty string or "document". |
| | 4664 | |
| | 4665 | @property responseXML |
| | 4666 | @type Document |
| | 4667 | */ |
| | 4668 | responseXML: null, |
| | 4669 | |
| | 4670 | /** |
| | 4671 | Returns the text response entity body. |
| | 4672 | |
| | 4673 | Throws an "InvalidStateError" exception if responseType is not the empty string or "text". |
| | 4674 | |
| | 4675 | @property responseText |
| | 4676 | @type String |
| | 4677 | */ |
| | 4678 | responseText: null, |
| | 4679 | |
| | 4680 | /** |
| | 4681 | Returns the response entity body (http://www.w3.org/TR/XMLHttpRequest/#response-entity-body). |
| | 4682 | Can become: ArrayBuffer, Blob, Document, JSON, Text |
| | 4683 | |
| | 4684 | @property response |
| | 4685 | @type Mixed |
| | 4686 | */ |
| | 4687 | response: null |
| | 4688 | }, |
| | 4689 | |
| | 4690 | _async = true, |
| | 4691 | _url, |
| | 4692 | _method, |
| | 4693 | _headers = {}, |
| | 4694 | _user, |
| | 4695 | _password, |
| | 4696 | _encoding = null, |
| | 4697 | _mimeType = null, |
| | 4698 | |
| | 4699 | // flags |
| | 4700 | _sync_flag = false, |
| | 4701 | _send_flag = false, |
| | 4702 | _upload_events_flag = false, |
| | 4703 | _upload_complete_flag = false, |
| | 4704 | _error_flag = false, |
| | 4705 | _same_origin_flag = false, |
| | 4706 | |
| | 4707 | // times |
| | 4708 | _start_time, |
| | 4709 | _timeoutset_time, |
| | 4710 | |
| | 4711 | _finalMime = null, |
| | 4712 | _finalCharset = null, |
| | 4713 | |
| | 4714 | _options = {}, |
| | 4715 | _xhr, |
| | 4716 | _responseHeaders = '', |
| | 4717 | _responseHeadersBag |
| | 4718 | ; |
| | 4719 | |
| | 4720 | |
| | 4721 | Basic.extend(this, props, { |
| | 4722 | /** |
| | 4723 | Unique id of the component |
| | 4724 | |
| | 4725 | @property uid |
| | 4726 | @type String |
| | 4727 | */ |
| | 4728 | uid: Basic.guid('uid_'), |
| | 4729 | |
| | 4730 | /** |
| | 4731 | Target for Upload events |
| | 4732 | |
| | 4733 | @property upload |
| | 4734 | @type XMLHttpRequestUpload |
| | 4735 | */ |
| | 4736 | upload: new XMLHttpRequestUpload(), |
| | 4737 | |
| | 4738 | |
| | 4739 | /** |
| | 4740 | Sets the request method, request URL, synchronous flag, request username, and request password. |
| | 4741 | |
| | 4742 | Throws a "SyntaxError" exception if one of the following is true: |
| | 4743 | |
| | 4744 | method is not a valid HTTP method. |
| | 4745 | url cannot be resolved. |
| | 4746 | url contains the "user:password" format in the userinfo production. |
| | 4747 | Throws a "SecurityError" exception if method is a case-insensitive match for CONNECT, TRACE or TRACK. |
| | 4748 | |
| | 4749 | Throws an "InvalidAccessError" exception if one of the following is true: |
| | 4750 | |
| | 4751 | Either user or password is passed as argument and the origin of url does not match the XMLHttpRequest origin. |
| | 4752 | There is an associated XMLHttpRequest document and either the timeout attribute is not zero, |
| | 4753 | the withCredentials attribute is true, or the responseType attribute is not the empty string. |
| | 4754 | |
| | 4755 | |
| | 4756 | @method open |
| | 4757 | @param {String} method HTTP method to use on request |
| | 4758 | @param {String} url URL to request |
| | 4759 | @param {Boolean} [async=true] If false request will be done in synchronous manner. Asynchronous by default. |
| | 4760 | @param {String} [user] Username to use in HTTP authentication process on server-side |
| | 4761 | @param {String} [password] Password to use in HTTP authentication process on server-side |
| | 4762 | */ |
| | 4763 | open: function(method, url, async, user, password) { |
| | 4764 | var urlp; |
| | 4765 | |
| | 4766 | // first two arguments are required |
| | 4767 | if (!method || !url) { |
| | 4768 | throw new x.DOMException(x.DOMException.SYNTAX_ERR); |
| | 4769 | } |
| | 4770 | |
| | 4771 | // 2 - check if any code point in method is higher than U+00FF or after deflating method it does not match the method |
| | 4772 | if (/[\u0100-\uffff]/.test(method) || Encode.utf8_encode(method) !== method) { |
| | 4773 | throw new x.DOMException(x.DOMException.SYNTAX_ERR); |
| | 4774 | } |
| | 4775 | |
| | 4776 | // 3 |
| | 4777 | if (!!~Basic.inArray(method.toUpperCase(), ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'TRACE', 'TRACK'])) { |
| | 4778 | _method = method.toUpperCase(); |
| | 4779 | } |
| | 4780 | |
| | 4781 | |
| | 4782 | // 4 - allowing these methods poses a security risk |
| | 4783 | if (!!~Basic.inArray(_method, ['CONNECT', 'TRACE', 'TRACK'])) { |
| | 4784 | throw new x.DOMException(x.DOMException.SECURITY_ERR); |
| | 4785 | } |
| | 4786 | |
| | 4787 | // 5 |
| | 4788 | url = Encode.utf8_encode(url); |
| | 4789 | |
| | 4790 | // 6 - Resolve url relative to the XMLHttpRequest base URL. If the algorithm returns an error, throw a "SyntaxError". |
| | 4791 | urlp = Url.parseUrl(url); |
| | 4792 | |
| | 4793 | _same_origin_flag = Url.hasSameOrigin(urlp); |
| | 4794 | |
| | 4795 | // 7 - manually build up absolute url |
| | 4796 | _url = Url.resolveUrl(url); |
| | 4797 | |
| | 4798 | // 9-10, 12-13 |
| | 4799 | if ((user || password) && !_same_origin_flag) { |
| | 4800 | throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); |
| | 4801 | } |
| | 4802 | |
| | 4803 | _user = user || urlp.user; |
| | 4804 | _password = password || urlp.pass; |
| | 4805 | |
| | 4806 | // 11 |
| | 4807 | _async = async || true; |
| | 4808 | |
| | 4809 | if (_async === false && (_p('timeout') || _p('withCredentials') || _p('responseType') !== "")) { |
| | 4810 | throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); |
| | 4811 | } |
| | 4812 | |
| | 4813 | // 14 - terminate abort() |
| | 4814 | |
| | 4815 | // 15 - terminate send() |
| | 4816 | |
| | 4817 | // 18 |
| | 4818 | _sync_flag = !_async; |
| | 4819 | _send_flag = false; |
| | 4820 | _headers = {}; |
| | 4821 | _reset.call(this); |
| | 4822 | |
| | 4823 | // 19 |
| | 4824 | _p('readyState', XMLHttpRequest.OPENED); |
| | 4825 | |
| | 4826 | // 20 |
| | 4827 | this.dispatchEvent('readystatechange'); |
| | 4828 | }, |
| | 4829 | |
| | 4830 | /** |
| | 4831 | Appends an header to the list of author request headers, or if header is already |
| | 4832 | in the list of author request headers, combines its value with value. |
| | 4833 | |
| | 4834 | Throws an "InvalidStateError" exception if the state is not OPENED or if the send() flag is set. |
| | 4835 | Throws a "SyntaxError" exception if header is not a valid HTTP header field name or if value |
| | 4836 | is not a valid HTTP header field value. |
| | 4837 | |
| | 4838 | @method setRequestHeader |
| | 4839 | @param {String} header |
| | 4840 | @param {String|Number} value |
| | 4841 | */ |
| | 4842 | setRequestHeader: function(header, value) { |
| | 4843 | var uaHeaders = [ // these headers are controlled by the user agent |
| | 4844 | "accept-charset", |
| | 4845 | "accept-encoding", |
| | 4846 | "access-control-request-headers", |
| | 4847 | "access-control-request-method", |
| | 4848 | "connection", |
| | 4849 | "content-length", |
| | 4850 | "cookie", |
| | 4851 | "cookie2", |
| | 4852 | "content-transfer-encoding", |
| | 4853 | "date", |
| | 4854 | "expect", |
| | 4855 | "host", |
| | 4856 | "keep-alive", |
| | 4857 | "origin", |
| | 4858 | "referer", |
| | 4859 | "te", |
| | 4860 | "trailer", |
| | 4861 | "transfer-encoding", |
| | 4862 | "upgrade", |
| | 4863 | "user-agent", |
| | 4864 | "via" |
| | 4865 | ]; |
| | 4866 | |
| | 4867 | // 1-2 |
| | 4868 | if (_p('readyState') !== XMLHttpRequest.OPENED || _send_flag) { |
| | 4869 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 4870 | } |
| | 4871 | |
| | 4872 | // 3 |
| | 4873 | if (/[\u0100-\uffff]/.test(header) || Encode.utf8_encode(header) !== header) { |
| | 4874 | throw new x.DOMException(x.DOMException.SYNTAX_ERR); |
| | 4875 | } |
| | 4876 | |
| | 4877 | // 4 |
| | 4878 | /* this step is seemingly bypassed in browsers, probably to allow various unicode characters in header values |
| | 4879 | if (/[\u0100-\uffff]/.test(value) || Encode.utf8_encode(value) !== value) { |
| | 4880 | throw new x.DOMException(x.DOMException.SYNTAX_ERR); |
| | 4881 | }*/ |
| | 4882 | |
| | 4883 | header = Basic.trim(header).toLowerCase(); |
| | 4884 | |
| | 4885 | // setting of proxy-* and sec-* headers is prohibited by spec |
| | 4886 | if (!!~Basic.inArray(header, uaHeaders) || /^(proxy\-|sec\-)/.test(header)) { |
| | 4887 | return false; |
| | 4888 | } |
| | 4889 | |
| | 4890 | // camelize |
| | 4891 | // browsers lowercase header names (at least for custom ones) |
| | 4892 | // header = header.replace(/\b\w/g, function($1) { return $1.toUpperCase(); }); |
| | 4893 | |
| | 4894 | if (!_headers[header]) { |
| | 4895 | _headers[header] = value; |
| | 4896 | } else { |
| | 4897 | // http://tools.ietf.org/html/rfc2616#section-4.2 (last paragraph) |
| | 4898 | _headers[header] += ', ' + value; |
| | 4899 | } |
| | 4900 | return true; |
| | 4901 | }, |
| | 4902 | |
| | 4903 | /** |
| | 4904 | Returns all headers from the response, with the exception of those whose field name is Set-Cookie or Set-Cookie2. |
| | 4905 | |
| | 4906 | @method getAllResponseHeaders |
| | 4907 | @return {String} reponse headers or empty string |
| | 4908 | */ |
| | 4909 | getAllResponseHeaders: function() { |
| | 4910 | return _responseHeaders || ''; |
| | 4911 | }, |
| | 4912 | |
| | 4913 | /** |
| | 4914 | Returns the header field value from the response of which the field name matches header, |
| | 4915 | unless the field name is Set-Cookie or Set-Cookie2. |
| | 4916 | |
| | 4917 | @method getResponseHeader |
| | 4918 | @param {String} header |
| | 4919 | @return {String} value(s) for the specified header or null |
| | 4920 | */ |
| | 4921 | getResponseHeader: function(header) { |
| | 4922 | header = header.toLowerCase(); |
| | 4923 | |
| | 4924 | if (_error_flag || !!~Basic.inArray(header, ['set-cookie', 'set-cookie2'])) { |
| | 4925 | return null; |
| | 4926 | } |
| | 4927 | |
| | 4928 | if (_responseHeaders && _responseHeaders !== '') { |
| | 4929 | // if we didn't parse response headers until now, do it and keep for later |
| | 4930 | if (!_responseHeadersBag) { |
| | 4931 | _responseHeadersBag = {}; |
| | 4932 | Basic.each(_responseHeaders.split(/\r\n/), function(line) { |
| | 4933 | var pair = line.split(/:\s+/); |
| | 4934 | if (pair.length === 2) { // last line might be empty, omit |
| | 4935 | pair[0] = Basic.trim(pair[0]); // just in case |
| | 4936 | _responseHeadersBag[pair[0].toLowerCase()] = { // simply to retain header name in original form |
| | 4937 | header: pair[0], |
| | 4938 | value: Basic.trim(pair[1]) |
| | 4939 | }; |
| | 4940 | } |
| | 4941 | }); |
| | 4942 | } |
| | 4943 | if (_responseHeadersBag.hasOwnProperty(header)) { |
| | 4944 | return _responseHeadersBag[header].header + ': ' + _responseHeadersBag[header].value; |
| | 4945 | } |
| | 4946 | } |
| | 4947 | return null; |
| | 4948 | }, |
| | 4949 | |
| | 4950 | /** |
| | 4951 | Sets the Content-Type header for the response to mime. |
| | 4952 | Throws an "InvalidStateError" exception if the state is LOADING or DONE. |
| | 4953 | Throws a "SyntaxError" exception if mime is not a valid media type. |
| | 4954 | |
| | 4955 | @method overrideMimeType |
| | 4956 | @param String mime Mime type to set |
| | 4957 | */ |
| | 4958 | overrideMimeType: function(mime) { |
| | 4959 | var matches, charset; |
| | 4960 | |
| | 4961 | // 1 |
| | 4962 | if (!!~Basic.inArray(_p('readyState'), [XMLHttpRequest.LOADING, XMLHttpRequest.DONE])) { |
| | 4963 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 4964 | } |
| | 4965 | |
| | 4966 | // 2 |
| | 4967 | mime = Basic.trim(mime.toLowerCase()); |
| | 4968 | |
| | 4969 | if (/;/.test(mime) && (matches = mime.match(/^([^;]+)(?:;\scharset\=)?(.*)$/))) { |
| | 4970 | mime = matches[1]; |
| | 4971 | if (matches[2]) { |
| | 4972 | charset = matches[2]; |
| | 4973 | } |
| | 4974 | } |
| | 4975 | |
| | 4976 | if (!Mime.mimes[mime]) { |
| | 4977 | throw new x.DOMException(x.DOMException.SYNTAX_ERR); |
| | 4978 | } |
| | 4979 | |
| | 4980 | // 3-4 |
| | 4981 | _finalMime = mime; |
| | 4982 | _finalCharset = charset; |
| | 4983 | }, |
| | 4984 | |
| | 4985 | /** |
| | 4986 | Initiates the request. The optional argument provides the request entity body. |
| | 4987 | The argument is ignored if request method is GET or HEAD. |
| | 4988 | |
| | 4989 | Throws an "InvalidStateError" exception if the state is not OPENED or if the send() flag is set. |
| | 4990 | |
| | 4991 | @method send |
| | 4992 | @param {Blob|Document|String|FormData} [data] Request entity body |
| | 4993 | @param {Object} [options] Set of requirements and pre-requisities for runtime initialization |
| | 4994 | */ |
| | 4995 | send: function(data, options) { |
| | 4996 | if (Basic.typeOf(options) === 'string') { |
| | 4997 | _options = { ruid: options }; |
| | 4998 | } else if (!options) { |
| | 4999 | _options = {}; |
| | 5000 | } else { |
| | 5001 | _options = options; |
| | 5002 | } |
| | 5003 | |
| | 5004 | // 1-2 |
| | 5005 | if (this.readyState !== XMLHttpRequest.OPENED || _send_flag) { |
| | 5006 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 5007 | } |
| | 5008 | |
| | 5009 | // 3 |
| | 5010 | // sending Blob |
| | 5011 | if (data instanceof Blob) { |
| | 5012 | _options.ruid = data.ruid; |
| | 5013 | _mimeType = data.type || 'application/octet-stream'; |
| | 5014 | } |
| | 5015 | |
| | 5016 | // FormData |
| | 5017 | else if (data instanceof FormData) { |
| | 5018 | if (data.hasBlob()) { |
| | 5019 | var blob = data.getBlob(); |
| | 5020 | _options.ruid = blob.ruid; |
| | 5021 | _mimeType = blob.type || 'application/octet-stream'; |
| | 5022 | } |
| | 5023 | } |
| | 5024 | |
| | 5025 | // DOMString |
| | 5026 | else if (typeof data === 'string') { |
| | 5027 | _encoding = 'UTF-8'; |
| | 5028 | _mimeType = 'text/plain;charset=UTF-8'; |
| | 5029 | |
| | 5030 | // data should be converted to Unicode and encoded as UTF-8 |
| | 5031 | data = Encode.utf8_encode(data); |
| | 5032 | } |
| | 5033 | |
| | 5034 | // if withCredentials not set, but requested, set it automatically |
| | 5035 | if (!this.withCredentials) { |
| | 5036 | this.withCredentials = (_options.required_caps && _options.required_caps.send_browser_cookies) && !_same_origin_flag; |
| | 5037 | } |
| | 5038 | |
| | 5039 | // 4 - storage mutex |
| | 5040 | // 5 |
| | 5041 | _upload_events_flag = (!_sync_flag && this.upload.hasEventListener()); // DSAP |
| | 5042 | // 6 |
| | 5043 | _error_flag = false; |
| | 5044 | // 7 |
| | 5045 | _upload_complete_flag = !data; |
| | 5046 | // 8 - Asynchronous steps |
| | 5047 | if (!_sync_flag) { |
| | 5048 | // 8.1 |
| | 5049 | _send_flag = true; |
| | 5050 | // 8.2 |
| | 5051 | // this.dispatchEvent('loadstart'); // will be dispatched either by native or runtime xhr |
| | 5052 | // 8.3 |
| | 5053 | //if (!_upload_complete_flag) { |
| | 5054 | // this.upload.dispatchEvent('loadstart'); // will be dispatched either by native or runtime xhr |
| | 5055 | //} |
| | 5056 | } |
| | 5057 | // 8.5 - Return the send() method call, but continue running the steps in this algorithm. |
| | 5058 | _doXHR.call(this, data); |
| | 5059 | }, |
| | 5060 | |
| | 5061 | /** |
| | 5062 | Cancels any network activity. |
| | 5063 | |
| | 5064 | @method abort |
| | 5065 | */ |
| | 5066 | abort: function() { |
| | 5067 | _error_flag = true; |
| | 5068 | _sync_flag = false; |
| | 5069 | |
| | 5070 | if (!~Basic.inArray(_p('readyState'), [XMLHttpRequest.UNSENT, XMLHttpRequest.OPENED, XMLHttpRequest.DONE])) { |
| | 5071 | _p('readyState', XMLHttpRequest.DONE); |
| | 5072 | _send_flag = false; |
| | 5073 | |
| | 5074 | if (_xhr) { |
| | 5075 | _xhr.getRuntime().exec.call(_xhr, 'XMLHttpRequest', 'abort', _upload_complete_flag); |
| | 5076 | } else { |
| | 5077 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 5078 | } |
| | 5079 | |
| | 5080 | _upload_complete_flag = true; |
| | 5081 | } else { |
| | 5082 | _p('readyState', XMLHttpRequest.UNSENT); |
| | 5083 | } |
| | 5084 | }, |
| | 5085 | |
| | 5086 | destroy: function() { |
| | 5087 | if (_xhr) { |
| | 5088 | if (Basic.typeOf(_xhr.destroy) === 'function') { |
| | 5089 | _xhr.destroy(); |
| | 5090 | } |
| | 5091 | _xhr = null; |
| | 5092 | } |
| | 5093 | |
| | 5094 | this.unbindAll(); |
| | 5095 | |
| | 5096 | if (this.upload) { |
| | 5097 | this.upload.unbindAll(); |
| | 5098 | this.upload = null; |
| | 5099 | } |
| | 5100 | } |
| | 5101 | }); |
| | 5102 | |
| | 5103 | this.handleEventProps(dispatches.concat(['readystatechange'])); // for historical reasons |
| | 5104 | this.upload.handleEventProps(dispatches); |
| | 5105 | |
| | 5106 | /* this is nice, but maybe too lengthy |
| | 5107 | |
| | 5108 | // if supported by JS version, set getters/setters for specific properties |
| | 5109 | o.defineProperty(this, 'readyState', { |
| | 5110 | configurable: false, |
| | 5111 | |
| | 5112 | get: function() { |
| | 5113 | return _p('readyState'); |
| | 5114 | } |
| | 5115 | }); |
| | 5116 | |
| | 5117 | o.defineProperty(this, 'timeout', { |
| | 5118 | configurable: false, |
| | 5119 | |
| | 5120 | get: function() { |
| | 5121 | return _p('timeout'); |
| | 5122 | }, |
| | 5123 | |
| | 5124 | set: function(value) { |
| | 5125 | |
| | 5126 | if (_sync_flag) { |
| | 5127 | throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); |
| | 5128 | } |
| | 5129 | |
| | 5130 | // timeout still should be measured relative to the start time of request |
| | 5131 | _timeoutset_time = (new Date).getTime(); |
| | 5132 | |
| | 5133 | _p('timeout', value); |
| | 5134 | } |
| | 5135 | }); |
| | 5136 | |
| | 5137 | // the withCredentials attribute has no effect when fetching same-origin resources |
| | 5138 | o.defineProperty(this, 'withCredentials', { |
| | 5139 | configurable: false, |
| | 5140 | |
| | 5141 | get: function() { |
| | 5142 | return _p('withCredentials'); |
| | 5143 | }, |
| | 5144 | |
| | 5145 | set: function(value) { |
| | 5146 | // 1-2 |
| | 5147 | if (!~o.inArray(_p('readyState'), [XMLHttpRequest.UNSENT, XMLHttpRequest.OPENED]) || _send_flag) { |
| | 5148 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 5149 | } |
| | 5150 | |
| | 5151 | // 3-4 |
| | 5152 | if (_anonymous_flag || _sync_flag) { |
| | 5153 | throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); |
| | 5154 | } |
| | 5155 | |
| | 5156 | // 5 |
| | 5157 | _p('withCredentials', value); |
| | 5158 | } |
| | 5159 | }); |
| | 5160 | |
| | 5161 | o.defineProperty(this, 'status', { |
| | 5162 | configurable: false, |
| | 5163 | |
| | 5164 | get: function() { |
| | 5165 | return _p('status'); |
| | 5166 | } |
| | 5167 | }); |
| | 5168 | |
| | 5169 | o.defineProperty(this, 'statusText', { |
| | 5170 | configurable: false, |
| | 5171 | |
| | 5172 | get: function() { |
| | 5173 | return _p('statusText'); |
| | 5174 | } |
| | 5175 | }); |
| | 5176 | |
| | 5177 | o.defineProperty(this, 'responseType', { |
| | 5178 | configurable: false, |
| | 5179 | |
| | 5180 | get: function() { |
| | 5181 | return _p('responseType'); |
| | 5182 | }, |
| | 5183 | |
| | 5184 | set: function(value) { |
| | 5185 | // 1 |
| | 5186 | if (!!~o.inArray(_p('readyState'), [XMLHttpRequest.LOADING, XMLHttpRequest.DONE])) { |
| | 5187 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 5188 | } |
| | 5189 | |
| | 5190 | // 2 |
| | 5191 | if (_sync_flag) { |
| | 5192 | throw new x.DOMException(x.DOMException.INVALID_ACCESS_ERR); |
| | 5193 | } |
| | 5194 | |
| | 5195 | // 3 |
| | 5196 | _p('responseType', value.toLowerCase()); |
| | 5197 | } |
| | 5198 | }); |
| | 5199 | |
| | 5200 | o.defineProperty(this, 'responseText', { |
| | 5201 | configurable: false, |
| | 5202 | |
| | 5203 | get: function() { |
| | 5204 | // 1 |
| | 5205 | if (!~o.inArray(_p('responseType'), ['', 'text'])) { |
| | 5206 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 5207 | } |
| | 5208 | |
| | 5209 | // 2-3 |
| | 5210 | if (_p('readyState') !== XMLHttpRequest.DONE && _p('readyState') !== XMLHttpRequest.LOADING || _error_flag) { |
| | 5211 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 5212 | } |
| | 5213 | |
| | 5214 | return _p('responseText'); |
| | 5215 | } |
| | 5216 | }); |
| | 5217 | |
| | 5218 | o.defineProperty(this, 'responseXML', { |
| | 5219 | configurable: false, |
| | 5220 | |
| | 5221 | get: function() { |
| | 5222 | // 1 |
| | 5223 | if (!~o.inArray(_p('responseType'), ['', 'document'])) { |
| | 5224 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 5225 | } |
| | 5226 | |
| | 5227 | // 2-3 |
| | 5228 | if (_p('readyState') !== XMLHttpRequest.DONE || _error_flag) { |
| | 5229 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 5230 | } |
| | 5231 | |
| | 5232 | return _p('responseXML'); |
| | 5233 | } |
| | 5234 | }); |
| | 5235 | |
| | 5236 | o.defineProperty(this, 'response', { |
| | 5237 | configurable: false, |
| | 5238 | |
| | 5239 | get: function() { |
| | 5240 | if (!!~o.inArray(_p('responseType'), ['', 'text'])) { |
| | 5241 | if (_p('readyState') !== XMLHttpRequest.DONE && _p('readyState') !== XMLHttpRequest.LOADING || _error_flag) { |
| | 5242 | return ''; |
| | 5243 | } |
| | 5244 | } |
| | 5245 | |
| | 5246 | if (_p('readyState') !== XMLHttpRequest.DONE || _error_flag) { |
| | 5247 | return null; |
| | 5248 | } |
| | 5249 | |
| | 5250 | return _p('response'); |
| | 5251 | } |
| | 5252 | }); |
| | 5253 | |
| | 5254 | */ |
| | 5255 | |
| | 5256 | function _p(prop, value) { |
| | 5257 | if (!props.hasOwnProperty(prop)) { |
| | 5258 | return; |
| | 5259 | } |
| | 5260 | if (arguments.length === 1) { // get |
| | 5261 | return Env.can('define_property') ? props[prop] : self[prop]; |
| | 5262 | } else { // set |
| | 5263 | if (Env.can('define_property')) { |
| | 5264 | props[prop] = value; |
| | 5265 | } else { |
| | 5266 | self[prop] = value; |
| | 5267 | } |
| | 5268 | } |
| | 5269 | } |
| | 5270 | |
| | 5271 | /* |
| | 5272 | function _toASCII(str, AllowUnassigned, UseSTD3ASCIIRules) { |
| | 5273 | // TODO: http://tools.ietf.org/html/rfc3490#section-4.1 |
| | 5274 | return str.toLowerCase(); |
| | 5275 | } |
| | 5276 | */ |
| | 5277 | |
| | 5278 | |
| | 5279 | function _doXHR(data) { |
| | 5280 | var self = this; |
| | 5281 | |
| | 5282 | _start_time = new Date().getTime(); |
| | 5283 | |
| | 5284 | _xhr = new RuntimeTarget(); |
| | 5285 | |
| | 5286 | function loadEnd() { |
| | 5287 | if (_xhr) { // it could have been destroyed by now |
| | 5288 | _xhr.destroy(); |
| | 5289 | _xhr = null; |
| | 5290 | } |
| | 5291 | self.dispatchEvent('loadend'); |
| | 5292 | self = null; |
| | 5293 | } |
| | 5294 | |
| | 5295 | function exec(runtime) { |
| | 5296 | _xhr.bind('LoadStart', function(e) { |
| | 5297 | _p('readyState', XMLHttpRequest.LOADING); |
| | 5298 | self.dispatchEvent('readystatechange'); |
| | 5299 | |
| | 5300 | self.dispatchEvent(e); |
| | 5301 | |
| | 5302 | if (_upload_events_flag) { |
| | 5303 | self.upload.dispatchEvent(e); |
| | 5304 | } |
| | 5305 | }); |
| | 5306 | |
| | 5307 | _xhr.bind('Progress', function(e) { |
| | 5308 | if (_p('readyState') !== XMLHttpRequest.LOADING) { |
| | 5309 | _p('readyState', XMLHttpRequest.LOADING); // LoadStart unreliable (in Flash for example) |
| | 5310 | self.dispatchEvent('readystatechange'); |
| | 5311 | } |
| | 5312 | self.dispatchEvent(e); |
| | 5313 | }); |
| | 5314 | |
| | 5315 | _xhr.bind('UploadProgress', function(e) { |
| | 5316 | if (_upload_events_flag) { |
| | 5317 | self.upload.dispatchEvent({ |
| | 5318 | type: 'progress', |
| | 5319 | lengthComputable: false, |
| | 5320 | total: e.total, |
| | 5321 | loaded: e.loaded |
| | 5322 | }); |
| | 5323 | } |
| | 5324 | }); |
| | 5325 | |
| | 5326 | _xhr.bind('Load', function(e) { |
| | 5327 | _p('readyState', XMLHttpRequest.DONE); |
| | 5328 | _p('status', Number(runtime.exec.call(_xhr, 'XMLHttpRequest', 'getStatus') || 0)); |
| | 5329 | _p('statusText', httpCode[_p('status')] || ""); |
| | 5330 | |
| | 5331 | _p('response', runtime.exec.call(_xhr, 'XMLHttpRequest', 'getResponse', _p('responseType'))); |
| | 5332 | |
| | 5333 | if (!!~Basic.inArray(_p('responseType'), ['text', ''])) { |
| | 5334 | _p('responseText', _p('response')); |
| | 5335 | } else if (_p('responseType') === 'document') { |
| | 5336 | _p('responseXML', _p('response')); |
| | 5337 | } |
| | 5338 | |
| | 5339 | _responseHeaders = runtime.exec.call(_xhr, 'XMLHttpRequest', 'getAllResponseHeaders'); |
| | 5340 | |
| | 5341 | self.dispatchEvent('readystatechange'); |
| | 5342 | |
| | 5343 | if (_p('status') > 0) { // status 0 usually means that server is unreachable |
| | 5344 | if (_upload_events_flag) { |
| | 5345 | self.upload.dispatchEvent(e); |
| | 5346 | } |
| | 5347 | self.dispatchEvent(e); |
| | 5348 | } else { |
| | 5349 | _error_flag = true; |
| | 5350 | self.dispatchEvent('error'); |
| | 5351 | } |
| | 5352 | loadEnd(); |
| | 5353 | }); |
| | 5354 | |
| | 5355 | _xhr.bind('Abort', function(e) { |
| | 5356 | self.dispatchEvent(e); |
| | 5357 | loadEnd(); |
| | 5358 | }); |
| | 5359 | |
| | 5360 | _xhr.bind('Error', function(e) { |
| | 5361 | _error_flag = true; |
| | 5362 | _p('readyState', XMLHttpRequest.DONE); |
| | 5363 | self.dispatchEvent('readystatechange'); |
| | 5364 | _upload_complete_flag = true; |
| | 5365 | self.dispatchEvent(e); |
| | 5366 | loadEnd(); |
| | 5367 | }); |
| | 5368 | |
| | 5369 | runtime.exec.call(_xhr, 'XMLHttpRequest', 'send', { |
| | 5370 | url: _url, |
| | 5371 | method: _method, |
| | 5372 | async: _async, |
| | 5373 | user: _user, |
| | 5374 | password: _password, |
| | 5375 | headers: _headers, |
| | 5376 | mimeType: _mimeType, |
| | 5377 | encoding: _encoding, |
| | 5378 | responseType: self.responseType, |
| | 5379 | withCredentials: self.withCredentials, |
| | 5380 | options: _options |
| | 5381 | }, data); |
| | 5382 | } |
| | 5383 | |
| | 5384 | // clarify our requirements |
| | 5385 | if (typeof(_options.required_caps) === 'string') { |
| | 5386 | _options.required_caps = Runtime.parseCaps(_options.required_caps); |
| | 5387 | } |
| | 5388 | |
| | 5389 | _options.required_caps = Basic.extend({}, _options.required_caps, { |
| | 5390 | return_response_type: self.responseType |
| | 5391 | }); |
| | 5392 | |
| | 5393 | if (data instanceof FormData) { |
| | 5394 | _options.required_caps.send_multipart = true; |
| | 5395 | } |
| | 5396 | |
| | 5397 | if (!Basic.isEmptyObj(_headers)) { |
| | 5398 | _options.required_caps.send_custom_headers = true; |
| | 5399 | } |
| | 5400 | |
| | 5401 | if (!_same_origin_flag) { |
| | 5402 | _options.required_caps.do_cors = true; |
| | 5403 | } |
| | 5404 | |
| | 5405 | |
| | 5406 | if (_options.ruid) { // we do not need to wait if we can connect directly |
| | 5407 | exec(_xhr.connectRuntime(_options)); |
| | 5408 | } else { |
| | 5409 | _xhr.bind('RuntimeInit', function(e, runtime) { |
| | 5410 | exec(runtime); |
| | 5411 | }); |
| | 5412 | _xhr.bind('RuntimeError', function(e, err) { |
| | 5413 | self.dispatchEvent('RuntimeError', err); |
| | 5414 | }); |
| | 5415 | _xhr.connectRuntime(_options); |
| | 5416 | } |
| | 5417 | } |
| | 5418 | |
| | 5419 | |
| | 5420 | function _reset() { |
| | 5421 | _p('responseText', ""); |
| | 5422 | _p('responseXML', null); |
| | 5423 | _p('response', null); |
| | 5424 | _p('status', 0); |
| | 5425 | _p('statusText', ""); |
| | 5426 | _start_time = _timeoutset_time = null; |
| | 5427 | } |
| | 5428 | } |
| | 5429 | |
| | 5430 | XMLHttpRequest.UNSENT = 0; |
| | 5431 | XMLHttpRequest.OPENED = 1; |
| | 5432 | XMLHttpRequest.HEADERS_RECEIVED = 2; |
| | 5433 | XMLHttpRequest.LOADING = 3; |
| | 5434 | XMLHttpRequest.DONE = 4; |
| | 5435 | |
| | 5436 | XMLHttpRequest.prototype = EventTarget.instance; |
| | 5437 | |
| | 5438 | return XMLHttpRequest; |
| | 5439 | }); |
| | 5440 | |
| | 5441 | // Included from: src/javascript/runtime/Transporter.js |
| | 5442 | |
| | 5443 | /** |
| | 5444 | * Transporter.js |
| | 5445 | * |
| | 5446 | * Copyright 2013, Moxiecode Systems AB |
| | 5447 | * Released under GPL License. |
| | 5448 | * |
| | 5449 | * License: http://www.plupload.com/license |
| | 5450 | * Contributing: http://www.plupload.com/contributing |
| | 5451 | */ |
| | 5452 | |
| | 5453 | define("moxie/runtime/Transporter", [ |
| | 5454 | "moxie/core/utils/Basic", |
| | 5455 | "moxie/core/utils/Encode", |
| | 5456 | "moxie/runtime/RuntimeClient", |
| | 5457 | "moxie/core/EventTarget" |
| | 5458 | ], function(Basic, Encode, RuntimeClient, EventTarget) { |
| | 5459 | function Transporter() { |
| | 5460 | var mod, _runtime, _data, _size, _pos, _chunk_size; |
| | 5461 | |
| | 5462 | RuntimeClient.call(this); |
| | 5463 | |
| | 5464 | Basic.extend(this, { |
| | 5465 | uid: Basic.guid('uid_'), |
| | 5466 | |
| | 5467 | state: Transporter.IDLE, |
| | 5468 | |
| | 5469 | result: null, |
| | 5470 | |
| | 5471 | transport: function(data, type, options) { |
| | 5472 | var self = this; |
| | 5473 | |
| | 5474 | options = Basic.extend({ |
| | 5475 | chunk_size: 204798 |
| | 5476 | }, options); |
| | 5477 | |
| | 5478 | // should divide by three, base64 requires this |
| | 5479 | if ((mod = options.chunk_size % 3)) { |
| | 5480 | options.chunk_size += 3 - mod; |
| | 5481 | } |
| | 5482 | |
| | 5483 | _chunk_size = options.chunk_size; |
| | 5484 | |
| | 5485 | _reset.call(this); |
| | 5486 | _data = data; |
| | 5487 | _size = data.length; |
| | 5488 | |
| | 5489 | if (Basic.typeOf(options) === 'string' || options.ruid) { |
| | 5490 | _run.call(self, type, this.connectRuntime(options)); |
| | 5491 | } else { |
| | 5492 | // we require this to run only once |
| | 5493 | var cb = function(e, runtime) { |
| | 5494 | self.unbind("RuntimeInit", cb); |
| | 5495 | _run.call(self, type, runtime); |
| | 5496 | }; |
| | 5497 | this.bind("RuntimeInit", cb); |
| | 5498 | this.connectRuntime(options); |
| | 5499 | } |
| | 5500 | }, |
| | 5501 | |
| | 5502 | abort: function() { |
| | 5503 | var self = this; |
| | 5504 | |
| | 5505 | self.state = Transporter.IDLE; |
| | 5506 | if (_runtime) { |
| | 5507 | _runtime.exec.call(self, 'Transporter', 'clear'); |
| | 5508 | self.trigger("TransportingAborted"); |
| | 5509 | } |
| | 5510 | |
| | 5511 | _reset.call(self); |
| | 5512 | }, |
| | 5513 | |
| | 5514 | |
| | 5515 | destroy: function() { |
| | 5516 | this.unbindAll(); |
| | 5517 | _runtime = null; |
| | 5518 | this.disconnectRuntime(); |
| | 5519 | _reset.call(this); |
| | 5520 | } |
| | 5521 | }); |
| | 5522 | |
| | 5523 | function _reset() { |
| | 5524 | _size = _pos = 0; |
| | 5525 | _data = this.result = null; |
| | 5526 | } |
| | 5527 | |
| | 5528 | function _run(type, runtime) { |
| | 5529 | var self = this; |
| | 5530 | |
| | 5531 | _runtime = runtime; |
| | 5532 | |
| | 5533 | //self.unbind("RuntimeInit"); |
| | 5534 | |
| | 5535 | self.bind("TransportingProgress", function(e) { |
| | 5536 | _pos = e.loaded; |
| | 5537 | |
| | 5538 | if (_pos < _size && Basic.inArray(self.state, [Transporter.IDLE, Transporter.DONE]) === -1) { |
| | 5539 | _transport.call(self); |
| | 5540 | } |
| | 5541 | }, 999); |
| | 5542 | |
| | 5543 | self.bind("TransportingComplete", function() { |
| | 5544 | _pos = _size; |
| | 5545 | self.state = Transporter.DONE; |
| | 5546 | _data = null; // clean a bit |
| | 5547 | self.result = _runtime.exec.call(self, 'Transporter', 'getAsBlob', type || ''); |
| | 5548 | }, 999); |
| | 5549 | |
| | 5550 | self.state = Transporter.BUSY; |
| | 5551 | self.trigger("TransportingStarted"); |
| | 5552 | _transport.call(self); |
| | 5553 | } |
| | 5554 | |
| | 5555 | function _transport() { |
| | 5556 | var self = this, |
| | 5557 | chunk, |
| | 5558 | bytesLeft = _size - _pos; |
| | 5559 | |
| | 5560 | if (_chunk_size > bytesLeft) { |
| | 5561 | _chunk_size = bytesLeft; |
| | 5562 | } |
| | 5563 | |
| | 5564 | chunk = Encode.btoa(_data.substr(_pos, _chunk_size)); |
| | 5565 | _runtime.exec.call(self, 'Transporter', 'receive', chunk, _size); |
| | 5566 | } |
| | 5567 | } |
| | 5568 | |
| | 5569 | Transporter.IDLE = 0; |
| | 5570 | Transporter.BUSY = 1; |
| | 5571 | Transporter.DONE = 2; |
| | 5572 | |
| | 5573 | Transporter.prototype = EventTarget.instance; |
| | 5574 | |
| | 5575 | return Transporter; |
| | 5576 | }); |
| | 5577 | |
| | 5578 | // Included from: src/javascript/image/Image.js |
| | 5579 | |
| | 5580 | /** |
| | 5581 | * Image.js |
| | 5582 | * |
| | 5583 | * Copyright 2013, Moxiecode Systems AB |
| | 5584 | * Released under GPL License. |
| | 5585 | * |
| | 5586 | * License: http://www.plupload.com/license |
| | 5587 | * Contributing: http://www.plupload.com/contributing |
| | 5588 | */ |
| | 5589 | |
| | 5590 | define("moxie/image/Image", [ |
| | 5591 | "moxie/core/utils/Basic", |
| | 5592 | "moxie/core/utils/Dom", |
| | 5593 | "moxie/core/Exceptions", |
| | 5594 | "moxie/file/FileReaderSync", |
| | 5595 | "moxie/xhr/XMLHttpRequest", |
| | 5596 | "moxie/runtime/Runtime", |
| | 5597 | "moxie/runtime/RuntimeClient", |
| | 5598 | "moxie/runtime/Transporter", |
| | 5599 | "moxie/core/utils/Env", |
| | 5600 | "moxie/core/EventTarget", |
| | 5601 | "moxie/file/Blob", |
| | 5602 | "moxie/file/File", |
| | 5603 | "moxie/core/utils/Encode" |
| | 5604 | ], function(Basic, Dom, x, FileReaderSync, XMLHttpRequest, Runtime, RuntimeClient, Transporter, Env, EventTarget, Blob, File, Encode) { |
| | 5605 | /** |
| | 5606 | Image preloading and manipulation utility. Additionally it provides access to image meta info (Exif, GPS) and raw binary data. |
| | 5607 | |
| | 5608 | @class Image |
| | 5609 | @constructor |
| | 5610 | @extends EventTarget |
| | 5611 | */ |
| | 5612 | var dispatches = [ |
| | 5613 | 'progress', |
| | 5614 | |
| | 5615 | /** |
| | 5616 | Dispatched when loading is complete. |
| | 5617 | |
| | 5618 | @event load |
| | 5619 | @param {Object} event |
| | 5620 | */ |
| | 5621 | 'load', |
| | 5622 | |
| | 5623 | 'error', |
| | 5624 | |
| | 5625 | /** |
| | 5626 | Dispatched when resize operation is complete. |
| | 5627 | |
| | 5628 | @event resize |
| | 5629 | @param {Object} event |
| | 5630 | */ |
| | 5631 | 'resize', |
| | 5632 | |
| | 5633 | /** |
| | 5634 | Dispatched when visual representation of the image is successfully embedded |
| | 5635 | into the corresponsing container. |
| | 5636 | |
| | 5637 | @event embedded |
| | 5638 | @param {Object} event |
| | 5639 | */ |
| | 5640 | 'embedded' |
| | 5641 | ]; |
| | 5642 | |
| | 5643 | function Image() { |
| | 5644 | |
| | 5645 | RuntimeClient.call(this); |
| | 5646 | |
| | 5647 | Basic.extend(this, { |
| | 5648 | /** |
| | 5649 | Unique id of the component |
| | 5650 | |
| | 5651 | @property uid |
| | 5652 | @type {String} |
| | 5653 | */ |
| | 5654 | uid: Basic.guid('uid_'), |
| | 5655 | |
| | 5656 | /** |
| | 5657 | Unique id of the connected runtime, if any. |
| | 5658 | |
| | 5659 | @property ruid |
| | 5660 | @type {String} |
| | 5661 | */ |
| | 5662 | ruid: null, |
| | 5663 | |
| | 5664 | /** |
| | 5665 | Name of the file, that was used to create an image, if available. If not equals to empty string. |
| | 5666 | |
| | 5667 | @property name |
| | 5668 | @type {String} |
| | 5669 | @default "" |
| | 5670 | */ |
| | 5671 | name: "", |
| | 5672 | |
| | 5673 | /** |
| | 5674 | Size of the image in bytes. Actual value is set only after image is preloaded. |
| | 5675 | |
| | 5676 | @property size |
| | 5677 | @type {Number} |
| | 5678 | @default 0 |
| | 5679 | */ |
| | 5680 | size: 0, |
| | 5681 | |
| | 5682 | /** |
| | 5683 | Width of the image. Actual value is set only after image is preloaded. |
| | 5684 | |
| | 5685 | @property width |
| | 5686 | @type {Number} |
| | 5687 | @default 0 |
| | 5688 | */ |
| | 5689 | width: 0, |
| | 5690 | |
| | 5691 | /** |
| | 5692 | Height of the image. Actual value is set only after image is preloaded. |
| | 5693 | |
| | 5694 | @property height |
| | 5695 | @type {Number} |
| | 5696 | @default 0 |
| | 5697 | */ |
| | 5698 | height: 0, |
| | 5699 | |
| | 5700 | /** |
| | 5701 | Mime type of the image. Currently only image/jpeg and image/png are supported. Actual value is set only after image is preloaded. |
| | 5702 | |
| | 5703 | @property type |
| | 5704 | @type {String} |
| | 5705 | @default "" |
| | 5706 | */ |
| | 5707 | type: "", |
| | 5708 | |
| | 5709 | /** |
| | 5710 | Holds meta info (Exif, GPS). Is populated only for image/jpeg. Actual value is set only after image is preloaded. |
| | 5711 | |
| | 5712 | @property meta |
| | 5713 | @type {Object} |
| | 5714 | @default {} |
| | 5715 | */ |
| | 5716 | meta: {}, |
| | 5717 | |
| | 5718 | /** |
| | 5719 | Alias for load method, that takes another mOxie.Image object as a source (see load). |
| | 5720 | |
| | 5721 | @method clone |
| | 5722 | @param {Image} src Source for the image |
| | 5723 | @param {Boolean} [exact=false] Whether to activate in-depth clone mode |
| | 5724 | */ |
| | 5725 | clone: function() { |
| | 5726 | this.load.apply(this, arguments); |
| | 5727 | }, |
| | 5728 | |
| | 5729 | /** |
| | 5730 | Loads image from various sources. Currently the source for new image can be: mOxie.Image, mOxie.Blob/mOxie.File, |
| | 5731 | native Blob/File, dataUrl or URL. Depending on the type of the source, arguments - differ. When source is URL, |
| | 5732 | Image will be downloaded from remote destination and loaded in memory. |
| | 5733 | |
| | 5734 | @example |
| | 5735 | var img = new mOxie.Image(); |
| | 5736 | img.onload = function() { |
| | 5737 | var blob = img.getAsBlob(); |
| | 5738 | |
| | 5739 | var formData = new mOxie.FormData(); |
| | 5740 | formData.append('file', blob); |
| | 5741 | |
| | 5742 | var xhr = new mOxie.XMLHttpRequest(); |
| | 5743 | xhr.onload = function() { |
| | 5744 | // upload complete |
| | 5745 | }; |
| | 5746 | xhr.open('post', 'upload.php'); |
| | 5747 | xhr.send(formData); |
| | 5748 | }; |
| | 5749 | img.load("http://www.moxiecode.com/images/mox-logo.jpg"); // notice file extension (.jpg) |
| | 5750 | |
| | 5751 | |
| | 5752 | @method load |
| | 5753 | @param {Image|Blob|File|String} src Source for the image |
| | 5754 | @param {Boolean|Object} [mixed] |
| | 5755 | */ |
| | 5756 | load: function() { |
| | 5757 | _load.apply(this, arguments); |
| | 5758 | }, |
| | 5759 | |
| | 5760 | /** |
| | 5761 | Downsizes the image to fit the specified width/height. If crop is supplied, image will be cropped to exact dimensions. |
| | 5762 | |
| | 5763 | @method downsize |
| | 5764 | @param {Object} opts |
| | 5765 | @param {Number} opts.width Resulting width |
| | 5766 | @param {Number} [opts.height=width] Resulting height (optional, if not supplied will default to width) |
| | 5767 | @param {Boolean} [opts.crop=false] Whether to crop the image to exact dimensions |
| | 5768 | @param {Boolean} [opts.preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize) |
| | 5769 | @param {String} [opts.resample=false] Resampling algorithm to use for resizing |
| | 5770 | */ |
| | 5771 | downsize: function(opts) { |
| | 5772 | var defaults = { |
| | 5773 | width: this.width, |
| | 5774 | height: this.height, |
| | 5775 | type: this.type || 'image/jpeg', |
| | 5776 | quality: 90, |
| | 5777 | crop: false, |
| | 5778 | preserveHeaders: true, |
| | 5779 | resample: false |
| | 5780 | }; |
| | 5781 | |
| | 5782 | if (typeof(opts) === 'object') { |
| | 5783 | opts = Basic.extend(defaults, opts); |
| | 5784 | } else { |
| | 5785 | // for backward compatibility |
| | 5786 | opts = Basic.extend(defaults, { |
| | 5787 | width: arguments[0], |
| | 5788 | height: arguments[1], |
| | 5789 | crop: arguments[2], |
| | 5790 | preserveHeaders: arguments[3] |
| | 5791 | }); |
| | 5792 | } |
| | 5793 | |
| | 5794 | try { |
| | 5795 | if (!this.size) { // only preloaded image objects can be used as source |
| | 5796 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 5797 | } |
| | 5798 | |
| | 5799 | // no way to reliably intercept the crash due to high resolution, so we simply avoid it |
| | 5800 | if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) { |
| | 5801 | throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR); |
| | 5802 | } |
| | 5803 | |
| | 5804 | this.exec('Image', 'downsize', opts.width, opts.height, opts.crop, opts.preserveHeaders); |
| | 5805 | } catch(ex) { |
| | 5806 | // for now simply trigger error event |
| | 5807 | this.trigger('error', ex.code); |
| | 5808 | } |
| | 5809 | }, |
| | 5810 | |
| | 5811 | /** |
| | 5812 | Alias for downsize(width, height, true). (see downsize) |
| | 5813 | |
| | 5814 | @method crop |
| | 5815 | @param {Number} width Resulting width |
| | 5816 | @param {Number} [height=width] Resulting height (optional, if not supplied will default to width) |
| | 5817 | @param {Boolean} [preserveHeaders=true] Whether to preserve meta headers (on JPEGs after resize) |
| | 5818 | */ |
| | 5819 | crop: function(width, height, preserveHeaders) { |
| | 5820 | this.downsize(width, height, true, preserveHeaders); |
| | 5821 | }, |
| | 5822 | |
| | 5823 | getAsCanvas: function() { |
| | 5824 | if (!Env.can('create_canvas')) { |
| | 5825 | throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR); |
| | 5826 | } |
| | 5827 | |
| | 5828 | var runtime = this.connectRuntime(this.ruid); |
| | 5829 | return runtime.exec.call(this, 'Image', 'getAsCanvas'); |
| | 5830 | }, |
| | 5831 | |
| | 5832 | /** |
| | 5833 | Retrieves image in it's current state as mOxie.Blob object. Cannot be run on empty or image in progress (throws |
| | 5834 | DOMException.INVALID_STATE_ERR). |
| | 5835 | |
| | 5836 | @method getAsBlob |
| | 5837 | @param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png |
| | 5838 | @param {Number} [quality=90] Applicable only together with mime type image/jpeg |
| | 5839 | @return {Blob} Image as Blob |
| | 5840 | */ |
| | 5841 | getAsBlob: function(type, quality) { |
| | 5842 | if (!this.size) { |
| | 5843 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 5844 | } |
| | 5845 | return this.exec('Image', 'getAsBlob', type || 'image/jpeg', quality || 90); |
| | 5846 | }, |
| | 5847 | |
| | 5848 | /** |
| | 5849 | Retrieves image in it's current state as dataURL string. Cannot be run on empty or image in progress (throws |
| | 5850 | DOMException.INVALID_STATE_ERR). |
| | 5851 | |
| | 5852 | @method getAsDataURL |
| | 5853 | @param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png |
| | 5854 | @param {Number} [quality=90] Applicable only together with mime type image/jpeg |
| | 5855 | @return {String} Image as dataURL string |
| | 5856 | */ |
| | 5857 | getAsDataURL: function(type, quality) { |
| | 5858 | if (!this.size) { |
| | 5859 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 5860 | } |
| | 5861 | return this.exec('Image', 'getAsDataURL', type || 'image/jpeg', quality || 90); |
| | 5862 | }, |
| | 5863 | |
| | 5864 | /** |
| | 5865 | Retrieves image in it's current state as binary string. Cannot be run on empty or image in progress (throws |
| | 5866 | DOMException.INVALID_STATE_ERR). |
| | 5867 | |
| | 5868 | @method getAsBinaryString |
| | 5869 | @param {String} [type="image/jpeg"] Mime type of resulting blob. Can either be image/jpeg or image/png |
| | 5870 | @param {Number} [quality=90] Applicable only together with mime type image/jpeg |
| | 5871 | @return {String} Image as binary string |
| | 5872 | */ |
| | 5873 | getAsBinaryString: function(type, quality) { |
| | 5874 | var dataUrl = this.getAsDataURL(type, quality); |
| | 5875 | return Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7)); |
| | 5876 | }, |
| | 5877 | |
| | 5878 | /** |
| | 5879 | Embeds a visual representation of the image into the specified node. Depending on the runtime, |
| | 5880 | it might be a canvas, an img node or a thrid party shim object (Flash or SilverLight - very rare, |
| | 5881 | can be used in legacy browsers that do not have canvas or proper dataURI support). |
| | 5882 | |
| | 5883 | @method embed |
| | 5884 | @param {DOMElement} el DOM element to insert the image object into |
| | 5885 | @param {Object} [opts] |
| | 5886 | @param {Number} [opts.width] The width of an embed (defaults to the image width) |
| | 5887 | @param {Number} [opts.height] The height of an embed (defaults to the image height) |
| | 5888 | @param {String} [type="image/jpeg"] Mime type |
| | 5889 | @param {Number} [quality=90] Quality of an embed, if mime type is image/jpeg |
| | 5890 | @param {Boolean} [crop=false] Whether to crop an embed to the specified dimensions |
| | 5891 | */ |
| | 5892 | embed: function(el, opts) { |
| | 5893 | var self = this |
| | 5894 | , runtime // this has to be outside of all the closures to contain proper runtime |
| | 5895 | ; |
| | 5896 | |
| | 5897 | opts = Basic.extend({ |
| | 5898 | width: this.width, |
| | 5899 | height: this.height, |
| | 5900 | type: this.type || 'image/jpeg', |
| | 5901 | quality: 90 |
| | 5902 | }, opts || {}); |
| | 5903 | |
| | 5904 | |
| | 5905 | function render(type, quality) { |
| | 5906 | var img = this; |
| | 5907 | |
| | 5908 | // if possible, embed a canvas element directly |
| | 5909 | if (Env.can('create_canvas')) { |
| | 5910 | var canvas = img.getAsCanvas(); |
| | 5911 | if (canvas) { |
| | 5912 | el.appendChild(canvas); |
| | 5913 | canvas = null; |
| | 5914 | img.destroy(); |
| | 5915 | self.trigger('embedded'); |
| | 5916 | return; |
| | 5917 | } |
| | 5918 | } |
| | 5919 | |
| | 5920 | var dataUrl = img.getAsDataURL(type, quality); |
| | 5921 | if (!dataUrl) { |
| | 5922 | throw new x.ImageError(x.ImageError.WRONG_FORMAT); |
| | 5923 | } |
| | 5924 | |
| | 5925 | if (Env.can('use_data_uri_of', dataUrl.length)) { |
| | 5926 | el.innerHTML = '<img src="' + dataUrl + '" width="' + img.width + '" height="' + img.height + '" />'; |
| | 5927 | img.destroy(); |
| | 5928 | self.trigger('embedded'); |
| | 5929 | } else { |
| | 5930 | var tr = new Transporter(); |
| | 5931 | |
| | 5932 | tr.bind("TransportingComplete", function() { |
| | 5933 | runtime = self.connectRuntime(this.result.ruid); |
| | 5934 | |
| | 5935 | self.bind("Embedded", function() { |
| | 5936 | // position and size properly |
| | 5937 | Basic.extend(runtime.getShimContainer().style, { |
| | 5938 | //position: 'relative', |
| | 5939 | top: '0px', |
| | 5940 | left: '0px', |
| | 5941 | width: img.width + 'px', |
| | 5942 | height: img.height + 'px' |
| | 5943 | }); |
| | 5944 | |
| | 5945 | // some shims (Flash/SilverLight) reinitialize, if parent element is hidden, reordered or it's |
| | 5946 | // position type changes (in Gecko), but since we basically need this only in IEs 6/7 and |
| | 5947 | // sometimes 8 and they do not have this problem, we can comment this for now |
| | 5948 | /*tr.bind("RuntimeInit", function(e, runtime) { |
| | 5949 | tr.destroy(); |
| | 5950 | runtime.destroy(); |
| | 5951 | onResize.call(self); // re-feed our image data |
| | 5952 | });*/ |
| | 5953 | |
| | 5954 | runtime = null; // release |
| | 5955 | }, 999); |
| | 5956 | |
| | 5957 | runtime.exec.call(self, "ImageView", "display", this.result.uid, width, height); |
| | 5958 | img.destroy(); |
| | 5959 | }); |
| | 5960 | |
| | 5961 | tr.transport(Encode.atob(dataUrl.substring(dataUrl.indexOf('base64,') + 7)), type, { |
| | 5962 | required_caps: { |
| | 5963 | display_media: true |
| | 5964 | }, |
| | 5965 | runtime_order: 'flash,silverlight', |
| | 5966 | container: el |
| | 5967 | }); |
| | 5968 | } |
| | 5969 | } |
| | 5970 | |
| | 5971 | try { |
| | 5972 | if (!(el = Dom.get(el))) { |
| | 5973 | throw new x.DOMException(x.DOMException.INVALID_NODE_TYPE_ERR); |
| | 5974 | } |
| | 5975 | |
| | 5976 | if (!this.size) { // only preloaded image objects can be used as source |
| | 5977 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 5978 | } |
| | 5979 | |
| | 5980 | // high-resolution images cannot be consistently handled across the runtimes |
| | 5981 | if (this.width > Image.MAX_RESIZE_WIDTH || this.height > Image.MAX_RESIZE_HEIGHT) { |
| | 5982 | //throw new x.ImageError(x.ImageError.MAX_RESOLUTION_ERR); |
| | 5983 | } |
| | 5984 | |
| | 5985 | var imgCopy = new Image(); |
| | 5986 | |
| | 5987 | imgCopy.bind("Resize", function() { |
| | 5988 | render.call(this, opts.type, opts.quality); |
| | 5989 | }); |
| | 5990 | |
| | 5991 | imgCopy.bind("Load", function() { |
| | 5992 | imgCopy.downsize(opts); |
| | 5993 | }); |
| | 5994 | |
| | 5995 | // if embedded thumb data is available and dimensions are big enough, use it |
| | 5996 | if (this.meta.thumb && this.meta.thumb.width >= opts.width && this.meta.thumb.height >= opts.height) { |
| | 5997 | imgCopy.load(this.meta.thumb.data); |
| | 5998 | } else { |
| | 5999 | imgCopy.clone(this, false); |
| | 6000 | } |
| | 6001 | |
| | 6002 | return imgCopy; |
| | 6003 | } catch(ex) { |
| | 6004 | // for now simply trigger error event |
| | 6005 | this.trigger('error', ex.code); |
| | 6006 | } |
| | 6007 | }, |
| | 6008 | |
| | 6009 | /** |
| | 6010 | Properly destroys the image and frees resources in use. If any. Recommended way to dispose mOxie.Image object. |
| | 6011 | |
| | 6012 | @method destroy |
| | 6013 | */ |
| | 6014 | destroy: function() { |
| | 6015 | if (this.ruid) { |
| | 6016 | this.getRuntime().exec.call(this, 'Image', 'destroy'); |
| | 6017 | this.disconnectRuntime(); |
| | 6018 | } |
| | 6019 | this.unbindAll(); |
| | 6020 | } |
| | 6021 | }); |
| | 6022 | |
| | 6023 | |
| | 6024 | // this is here, because in order to bind properly, we need uid, which is created above |
| | 6025 | this.handleEventProps(dispatches); |
| | 6026 | |
| | 6027 | this.bind('Load Resize', function() { |
| | 6028 | _updateInfo.call(this); |
| | 6029 | }, 999); |
| | 6030 | |
| | 6031 | |
| | 6032 | function _updateInfo(info) { |
| | 6033 | if (!info) { |
| | 6034 | info = this.exec('Image', 'getInfo'); |
| | 6035 | } |
| | 6036 | |
| | 6037 | this.size = info.size; |
| | 6038 | this.width = info.width; |
| | 6039 | this.height = info.height; |
| | 6040 | this.type = info.type; |
| | 6041 | this.meta = info.meta; |
| | 6042 | |
| | 6043 | // update file name, only if empty |
| | 6044 | if (this.name === '') { |
| | 6045 | this.name = info.name; |
| | 6046 | } |
| | 6047 | } |
| | 6048 | |
| | 6049 | |
| | 6050 | function _load(src) { |
| | 6051 | var srcType = Basic.typeOf(src); |
| | 6052 | |
| | 6053 | try { |
| | 6054 | // if source is Image |
| | 6055 | if (src instanceof Image) { |
| | 6056 | if (!src.size) { // only preloaded image objects can be used as source |
| | 6057 | throw new x.DOMException(x.DOMException.INVALID_STATE_ERR); |
| | 6058 | } |
| | 6059 | _loadFromImage.apply(this, arguments); |
| | 6060 | } |
| | 6061 | // if source is o.Blob/o.File |
| | 6062 | else if (src instanceof Blob) { |
| | 6063 | if (!~Basic.inArray(src.type, ['image/jpeg', 'image/png'])) { |
| | 6064 | throw new x.ImageError(x.ImageError.WRONG_FORMAT); |
| | 6065 | } |
| | 6066 | _loadFromBlob.apply(this, arguments); |
| | 6067 | } |
| | 6068 | // if native blob/file |
| | 6069 | else if (Basic.inArray(srcType, ['blob', 'file']) !== -1) { |
| | 6070 | _load.call(this, new File(null, src), arguments[1]); |
| | 6071 | } |
| | 6072 | // if String |
| | 6073 | else if (srcType === 'string') { |
| | 6074 | // if dataUrl String |
| | 6075 | if (src.substr(0, 5) === 'data:') { |
| | 6076 | _load.call(this, new Blob(null, { data: src }), arguments[1]); |
| | 6077 | } |
| | 6078 | // else assume Url, either relative or absolute |
| | 6079 | else { |
| | 6080 | _loadFromUrl.apply(this, arguments); |
| | 6081 | } |
| | 6082 | } |
| | 6083 | // if source seems to be an img node |
| | 6084 | else if (srcType === 'node' && src.nodeName.toLowerCase() === 'img') { |
| | 6085 | _load.call(this, src.src, arguments[1]); |
| | 6086 | } |
| | 6087 | else { |
| | 6088 | throw new x.DOMException(x.DOMException.TYPE_MISMATCH_ERR); |
| | 6089 | } |
| | 6090 | } catch(ex) { |
| | 6091 | // for now simply trigger error event |
| | 6092 | this.trigger('error', ex.code); |
| | 6093 | } |
| | 6094 | } |
| | 6095 | |
| | 6096 | |
| | 6097 | function _loadFromImage(img, exact) { |
| | 6098 | var runtime = this.connectRuntime(img.ruid); |
| | 6099 | this.ruid = runtime.uid; |
| | 6100 | runtime.exec.call(this, 'Image', 'loadFromImage', img, (Basic.typeOf(exact) === 'undefined' ? true : exact)); |
| | 6101 | } |
| | 6102 | |
| | 6103 | |
| | 6104 | function _loadFromBlob(blob, options) { |
| | 6105 | var self = this; |
| | 6106 | |
| | 6107 | self.name = blob.name || ''; |
| | 6108 | |
| | 6109 | function exec(runtime) { |
| | 6110 | self.ruid = runtime.uid; |
| | 6111 | runtime.exec.call(self, 'Image', 'loadFromBlob', blob); |
| | 6112 | } |
| | 6113 | |
| | 6114 | if (blob.isDetached()) { |
| | 6115 | this.bind('RuntimeInit', function(e, runtime) { |
| | 6116 | exec(runtime); |
| | 6117 | }); |
| | 6118 | |
| | 6119 | // convert to object representation |
| | 6120 | if (options && typeof(options.required_caps) === 'string') { |
| | 6121 | options.required_caps = Runtime.parseCaps(options.required_caps); |
| | 6122 | } |
| | 6123 | |
| | 6124 | this.connectRuntime(Basic.extend({ |
| | 6125 | required_caps: { |
| | 6126 | access_image_binary: true, |
| | 6127 | resize_image: true |
| | 6128 | } |
| | 6129 | }, options)); |
| | 6130 | } else { |
| | 6131 | exec(this.connectRuntime(blob.ruid)); |
| | 6132 | } |
| | 6133 | } |
| | 6134 | |
| | 6135 | |
| | 6136 | function _loadFromUrl(url, options) { |
| | 6137 | var self = this, xhr; |
| | 6138 | |
| | 6139 | xhr = new XMLHttpRequest(); |
| | 6140 | |
| | 6141 | xhr.open('get', url); |
| | 6142 | xhr.responseType = 'blob'; |
| | 6143 | |
| | 6144 | xhr.onprogress = function(e) { |
| | 6145 | self.trigger(e); |
| | 6146 | }; |
| | 6147 | |
| | 6148 | xhr.onload = function() { |
| | 6149 | _loadFromBlob.call(self, xhr.response, true); |
| | 6150 | }; |
| | 6151 | |
| | 6152 | xhr.onerror = function(e) { |
| | 6153 | self.trigger(e); |
| | 6154 | }; |
| | 6155 | |
| | 6156 | xhr.onloadend = function() { |
| | 6157 | xhr.destroy(); |
| | 6158 | }; |
| | 6159 | |
| | 6160 | xhr.bind('RuntimeError', function(e, err) { |
| | 6161 | self.trigger('RuntimeError', err); |
| | 6162 | }); |
| | 6163 | |
| | 6164 | xhr.send(null, options); |
| | 6165 | } |
| | 6166 | } |
| | 6167 | |
| | 6168 | // virtual world will crash on you if image has a resolution higher than this: |
| | 6169 | Image.MAX_RESIZE_WIDTH = 8192; |
| | 6170 | Image.MAX_RESIZE_HEIGHT = 8192; |
| | 6171 | |
| | 6172 | Image.prototype = EventTarget.instance; |
| | 6173 | |
| | 6174 | return Image; |
| | 6175 | }); |
| | 6176 | |
| | 6177 | // Included from: src/javascript/runtime/html5/Runtime.js |
| | 6178 | |
| | 6179 | /** |
| | 6180 | * Runtime.js |
| | 6181 | * |
| | 6182 | * Copyright 2013, Moxiecode Systems AB |
| | 6183 | * Released under GPL License. |
| | 6184 | * |
| | 6185 | * License: http://www.plupload.com/license |
| | 6186 | * Contributing: http://www.plupload.com/contributing |
| | 6187 | */ |
| | 6188 | |
| | 6189 | /*global File:true */ |
| | 6190 | |
| | 6191 | /** |
| | 6192 | Defines constructor for HTML5 runtime. |
| | 6193 | |
| | 6194 | @class moxie/runtime/html5/Runtime |
| | 6195 | @private |
| | 6196 | */ |
| | 6197 | define("moxie/runtime/html5/Runtime", [ |
| | 6198 | "moxie/core/utils/Basic", |
| | 6199 | "moxie/core/Exceptions", |
| | 6200 | "moxie/runtime/Runtime", |
| | 6201 | "moxie/core/utils/Env" |
| | 6202 | ], function(Basic, x, Runtime, Env) { |
| | 6203 | |
| | 6204 | var type = "html5", extensions = {}; |
| | 6205 | |
| | 6206 | function Html5Runtime(options) { |
| | 6207 | var I = this |
| | 6208 | , Test = Runtime.capTest |
| | 6209 | , True = Runtime.capTrue |
| | 6210 | ; |
| | 6211 | |
| | 6212 | var caps = Basic.extend({ |
| | 6213 | access_binary: Test(window.FileReader || window.File && window.File.getAsDataURL), |
| | 6214 | access_image_binary: function() { |
| | 6215 | return I.can('access_binary') && !!extensions.Image; |
| | 6216 | }, |
| | 6217 | display_media: Test(Env.can('create_canvas') || Env.can('use_data_uri_over32kb')), |
| | 6218 | do_cors: Test(window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()), |
| | 6219 | drag_and_drop: Test(function() { |
| | 6220 | // this comes directly from Modernizr: http://www.modernizr.com/ |
| | 6221 | var div = document.createElement('div'); |
| | 6222 | // IE has support for drag and drop since version 5, but doesn't support dropping files from desktop |
| | 6223 | return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && |
| | 6224 | (Env.browser !== 'IE' || Env.verComp(Env.version, 9, '>')); |
| | 6225 | }()), |
| | 6226 | filter_by_extension: Test(function() { // if you know how to feature-detect this, please suggest |
| | 6227 | return (Env.browser === 'Chrome' && Env.verComp(Env.version, 28, '>=')) || |
| | 6228 | (Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) || |
| | 6229 | (Env.browser === 'Safari' && Env.verComp(Env.version, 7, '>=')); |
| | 6230 | }()), |
| | 6231 | return_response_headers: True, |
| | 6232 | return_response_type: function(responseType) { |
| | 6233 | if (responseType === 'json' && !!window.JSON) { // we can fake this one even if it's not supported |
| | 6234 | return true; |
| | 6235 | } |
| | 6236 | return Env.can('return_response_type', responseType); |
| | 6237 | }, |
| | 6238 | return_status_code: True, |
| | 6239 | report_upload_progress: Test(window.XMLHttpRequest && new XMLHttpRequest().upload), |
| | 6240 | resize_image: function() { |
| | 6241 | return I.can('access_binary') && Env.can('create_canvas'); |
| | 6242 | }, |
| | 6243 | select_file: function() { |
| | 6244 | return Env.can('use_fileinput') && window.File; |
| | 6245 | }, |
| | 6246 | select_folder: function() { |
| | 6247 | return I.can('select_file') && Env.browser === 'Chrome' && Env.verComp(Env.version, 21, '>='); |
| | 6248 | }, |
| | 6249 | select_multiple: function() { |
| | 6250 | // it is buggy on Safari Windows and iOS |
| | 6251 | return I.can('select_file') && |
| | 6252 | !(Env.browser === 'Safari' && Env.os === 'Windows') && |
| | 6253 | !(Env.os === 'iOS' && Env.verComp(Env.osVersion, "7.0.0", '>') && Env.verComp(Env.osVersion, "8.0.0", '<')); |
| | 6254 | }, |
| | 6255 | send_binary_string: Test(window.XMLHttpRequest && (new XMLHttpRequest().sendAsBinary || (window.Uint8Array && window.ArrayBuffer))), |
| | 6256 | send_custom_headers: Test(window.XMLHttpRequest), |
| | 6257 | send_multipart: function() { |
| | 6258 | return !!(window.XMLHttpRequest && new XMLHttpRequest().upload && window.FormData) || I.can('send_binary_string'); |
| | 6259 | }, |
| | 6260 | slice_blob: Test(window.File && (File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice)), |
| | 6261 | stream_upload: function(){ |
| | 6262 | return I.can('slice_blob') && I.can('send_multipart'); |
| | 6263 | }, |
| | 6264 | summon_file_dialog: function() { // yeah... some dirty sniffing here... |
| | 6265 | return I.can('select_file') && ( |
| | 6266 | (Env.browser === 'Firefox' && Env.verComp(Env.version, 4, '>=')) || |
| | 6267 | (Env.browser === 'Opera' && Env.verComp(Env.version, 12, '>=')) || |
| | 6268 | (Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) || |
| | 6269 | !!~Basic.inArray(Env.browser, ['Chrome', 'Safari']) |
| | 6270 | ); |
| | 6271 | }, |
| | 6272 | upload_filesize: True |
| | 6273 | }, |
| | 6274 | arguments[2] |
| | 6275 | ); |
| | 6276 | |
| | 6277 | Runtime.call(this, options, (arguments[1] || type), caps); |
| | 6278 | |
| | 6279 | |
| | 6280 | Basic.extend(this, { |
| | 6281 | |
| | 6282 | init : function() { |
| | 6283 | this.trigger("Init"); |
| | 6284 | }, |
| | 6285 | |
| | 6286 | destroy: (function(destroy) { // extend default destroy method |
| | 6287 | return function() { |
| | 6288 | destroy.call(I); |
| | 6289 | destroy = I = null; |
| | 6290 | }; |
| | 6291 | }(this.destroy)) |
| | 6292 | }); |
| | 6293 | |
| | 6294 | Basic.extend(this.getShim(), extensions); |
| | 6295 | } |
| | 6296 | |
| | 6297 | Runtime.addConstructor(type, Html5Runtime); |
| | 6298 | |
| | 6299 | return extensions; |
| | 6300 | }); |
| | 6301 | |
| | 6302 | // Included from: src/javascript/core/utils/Events.js |
| | 6303 | |
| | 6304 | /** |
| | 6305 | * Events.js |
| | 6306 | * |
| | 6307 | * Copyright 2013, Moxiecode Systems AB |
| | 6308 | * Released under GPL License. |
| | 6309 | * |
| | 6310 | * License: http://www.plupload.com/license |
| | 6311 | * Contributing: http://www.plupload.com/contributing |
| | 6312 | */ |
| | 6313 | |
| | 6314 | define('moxie/core/utils/Events', [ |
| | 6315 | 'moxie/core/utils/Basic' |
| | 6316 | ], function(Basic) { |
| | 6317 | var eventhash = {}, uid = 'moxie_' + Basic.guid(); |
| | 6318 | |
| | 6319 | // IE W3C like event funcs |
| | 6320 | function preventDefault() { |
| | 6321 | this.returnValue = false; |
| | 6322 | } |
| | 6323 | |
| | 6324 | function stopPropagation() { |
| | 6325 | this.cancelBubble = true; |
| | 6326 | } |
| | 6327 | |
| | 6328 | /** |
| | 6329 | Adds an event handler to the specified object and store reference to the handler |
| | 6330 | in objects internal Plupload registry (@see removeEvent). |
| | 6331 | |
| | 6332 | @method addEvent |
| | 6333 | @for Utils |
| | 6334 | @static |
| | 6335 | @param {Object} obj DOM element like object to add handler to. |
| | 6336 | @param {String} name Name to add event listener to. |
| | 6337 | @param {Function} callback Function to call when event occurs. |
| | 6338 | @param {String} [key] that might be used to add specifity to the event record. |
| | 6339 | */ |
| | 6340 | var addEvent = function(obj, name, callback, key) { |
| | 6341 | var func, events; |
| | 6342 | |
| | 6343 | name = name.toLowerCase(); |
| | 6344 | |
| | 6345 | // Add event listener |
| | 6346 | if (obj.addEventListener) { |
| | 6347 | func = callback; |
| | 6348 | |
| | 6349 | obj.addEventListener(name, func, false); |
| | 6350 | } else if (obj.attachEvent) { |
| | 6351 | func = function() { |
| | 6352 | var evt = window.event; |
| | 6353 | |
| | 6354 | if (!evt.target) { |
| | 6355 | evt.target = evt.srcElement; |
| | 6356 | } |
| | 6357 | |
| | 6358 | evt.preventDefault = preventDefault; |
| | 6359 | evt.stopPropagation = stopPropagation; |
| | 6360 | |
| | 6361 | callback(evt); |
| | 6362 | }; |
| | 6363 | |
| | 6364 | obj.attachEvent('on' + name, func); |
| | 6365 | } |
| | 6366 | |
| | 6367 | // Log event handler to objects internal mOxie registry |
| | 6368 | if (!obj[uid]) { |
| | 6369 | obj[uid] = Basic.guid(); |
| | 6370 | } |
| | 6371 | |
| | 6372 | if (!eventhash.hasOwnProperty(obj[uid])) { |
| | 6373 | eventhash[obj[uid]] = {}; |
| | 6374 | } |
| | 6375 | |
| | 6376 | events = eventhash[obj[uid]]; |
| | 6377 | |
| | 6378 | if (!events.hasOwnProperty(name)) { |
| | 6379 | events[name] = []; |
| | 6380 | } |
| | 6381 | |
| | 6382 | events[name].push({ |
| | 6383 | func: func, |
| | 6384 | orig: callback, // store original callback for IE |
| | 6385 | key: key |
| | 6386 | }); |
| | 6387 | }; |
| | 6388 | |
| | 6389 | |
| | 6390 | /** |
| | 6391 | Remove event handler from the specified object. If third argument (callback) |
| | 6392 | is not specified remove all events with the specified name. |
| | 6393 | |
| | 6394 | @method removeEvent |
| | 6395 | @static |
| | 6396 | @param {Object} obj DOM element to remove event listener(s) from. |
| | 6397 | @param {String} name Name of event listener to remove. |
| | 6398 | @param {Function|String} [callback] might be a callback or unique key to match. |
| | 6399 | */ |
| | 6400 | var removeEvent = function(obj, name, callback) { |
| | 6401 | var type, undef; |
| | 6402 | |
| | 6403 | name = name.toLowerCase(); |
| | 6404 | |
| | 6405 | if (obj[uid] && eventhash[obj[uid]] && eventhash[obj[uid]][name]) { |
| | 6406 | type = eventhash[obj[uid]][name]; |
| | 6407 | } else { |
| | 6408 | return; |
| | 6409 | } |
| | 6410 | |
| | 6411 | for (var i = type.length - 1; i >= 0; i--) { |
| | 6412 | // undefined or not, key should match |
| | 6413 | if (type[i].orig === callback || type[i].key === callback) { |
| | 6414 | if (obj.removeEventListener) { |
| | 6415 | obj.removeEventListener(name, type[i].func, false); |
| | 6416 | } else if (obj.detachEvent) { |
| | 6417 | obj.detachEvent('on'+name, type[i].func); |
| | 6418 | } |
| | 6419 | |
| | 6420 | type[i].orig = null; |
| | 6421 | type[i].func = null; |
| | 6422 | type.splice(i, 1); |
| | 6423 | |
| | 6424 | // If callback was passed we are done here, otherwise proceed |
| | 6425 | if (callback !== undef) { |
| | 6426 | break; |
| | 6427 | } |
| | 6428 | } |
| | 6429 | } |
| | 6430 | |
| | 6431 | // If event array got empty, remove it |
| | 6432 | if (!type.length) { |
| | 6433 | delete eventhash[obj[uid]][name]; |
| | 6434 | } |
| | 6435 | |
| | 6436 | // If mOxie registry has become empty, remove it |
| | 6437 | if (Basic.isEmptyObj(eventhash[obj[uid]])) { |
| | 6438 | delete eventhash[obj[uid]]; |
| | 6439 | |
| | 6440 | // IE doesn't let you remove DOM object property with - delete |
| | 6441 | try { |
| | 6442 | delete obj[uid]; |
| | 6443 | } catch(e) { |
| | 6444 | obj[uid] = undef; |
| | 6445 | } |
| | 6446 | } |
| | 6447 | }; |
| | 6448 | |
| | 6449 | |
| | 6450 | /** |
| | 6451 | Remove all kind of events from the specified object |
| | 6452 | |
| | 6453 | @method removeAllEvents |
| | 6454 | @static |
| | 6455 | @param {Object} obj DOM element to remove event listeners from. |
| | 6456 | @param {String} [key] unique key to match, when removing events. |
| | 6457 | */ |
| | 6458 | var removeAllEvents = function(obj, key) { |
| | 6459 | if (!obj || !obj[uid]) { |
| | 6460 | return; |
| | 6461 | } |
| | 6462 | |
| | 6463 | Basic.each(eventhash[obj[uid]], function(events, name) { |
| | 6464 | removeEvent(obj, name, key); |
| | 6465 | }); |
| | 6466 | }; |
| | 6467 | |
| | 6468 | return { |
| | 6469 | addEvent: addEvent, |
| | 6470 | removeEvent: removeEvent, |
| | 6471 | removeAllEvents: removeAllEvents |
| | 6472 | }; |
| | 6473 | }); |
| | 6474 | |
| | 6475 | // Included from: src/javascript/runtime/html5/file/FileInput.js |
| | 6476 | |
| | 6477 | /** |
| | 6478 | * FileInput.js |
| | 6479 | * |
| | 6480 | * Copyright 2013, Moxiecode Systems AB |
| | 6481 | * Released under GPL License. |
| | 6482 | * |
| | 6483 | * License: http://www.plupload.com/license |
| | 6484 | * Contributing: http://www.plupload.com/contributing |
| | 6485 | */ |
| | 6486 | |
| | 6487 | /** |
| | 6488 | @class moxie/runtime/html5/file/FileInput |
| | 6489 | @private |
| | 6490 | */ |
| | 6491 | define("moxie/runtime/html5/file/FileInput", [ |
| | 6492 | "moxie/runtime/html5/Runtime", |
| | 6493 | "moxie/file/File", |
| | 6494 | "moxie/core/utils/Basic", |
| | 6495 | "moxie/core/utils/Dom", |
| | 6496 | "moxie/core/utils/Events", |
| | 6497 | "moxie/core/utils/Mime", |
| | 6498 | "moxie/core/utils/Env" |
| | 6499 | ], function(extensions, File, Basic, Dom, Events, Mime, Env) { |
| | 6500 | |
| | 6501 | function FileInput() { |
| | 6502 | var _options; |
| | 6503 | |
| | 6504 | Basic.extend(this, { |
| | 6505 | init: function(options) { |
| | 6506 | var comp = this, I = comp.getRuntime(), input, shimContainer, mimes, browseButton, zIndex, top; |
| | 6507 | |
| | 6508 | _options = options; |
| | 6509 | |
| | 6510 | // figure out accept string |
| | 6511 | mimes = _options.accept.mimes || Mime.extList2mimes(_options.accept, I.can('filter_by_extension')); |
| | 6512 | |
| | 6513 | shimContainer = I.getShimContainer(); |
| | 6514 | |
| | 6515 | shimContainer.innerHTML = '<input id="' + I.uid +'" type="file" style="font-size:999px;opacity:0;"' + |
| | 6516 | (_options.multiple && I.can('select_multiple') ? 'multiple' : '') + |
| | 6517 | (_options.directory && I.can('select_folder') ? 'webkitdirectory directory' : '') + // Chrome 11+ |
| | 6518 | (mimes ? ' accept="' + mimes.join(',') + '"' : '') + ' />'; |
| | 6519 | |
| | 6520 | input = Dom.get(I.uid); |
| | 6521 | |
| | 6522 | // prepare file input to be placed underneath the browse_button element |
| | 6523 | Basic.extend(input.style, { |
| | 6524 | position: 'absolute', |
| | 6525 | top: 0, |
| | 6526 | left: 0, |
| | 6527 | width: '100%', |
| | 6528 | height: '100%' |
| | 6529 | }); |
| | 6530 | |
| | 6531 | |
| | 6532 | browseButton = Dom.get(_options.browse_button); |
| | 6533 | |
| | 6534 | // Route click event to the input[type=file] element for browsers that support such behavior |
| | 6535 | if (I.can('summon_file_dialog')) { |
| | 6536 | if (Dom.getStyle(browseButton, 'position') === 'static') { |
| | 6537 | browseButton.style.position = 'relative'; |
| | 6538 | } |
| | 6539 | |
| | 6540 | zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1; |
| | 6541 | |
| | 6542 | browseButton.style.zIndex = zIndex; |
| | 6543 | shimContainer.style.zIndex = zIndex - 1; |
| | 6544 | |
| | 6545 | Events.addEvent(browseButton, 'click', function(e) { |
| | 6546 | var input = Dom.get(I.uid); |
| | 6547 | if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file] |
| | 6548 | input.click(); |
| | 6549 | } |
| | 6550 | e.preventDefault(); |
| | 6551 | }, comp.uid); |
| | 6552 | } |
| | 6553 | |
| | 6554 | /* Since we have to place input[type=file] on top of the browse_button for some browsers, |
| | 6555 | browse_button loses interactivity, so we restore it here */ |
| | 6556 | top = I.can('summon_file_dialog') ? browseButton : shimContainer; |
| | 6557 | |
| | 6558 | Events.addEvent(top, 'mouseover', function() { |
| | 6559 | comp.trigger('mouseenter'); |
| | 6560 | }, comp.uid); |
| | 6561 | |
| | 6562 | Events.addEvent(top, 'mouseout', function() { |
| | 6563 | comp.trigger('mouseleave'); |
| | 6564 | }, comp.uid); |
| | 6565 | |
| | 6566 | Events.addEvent(top, 'mousedown', function() { |
| | 6567 | comp.trigger('mousedown'); |
| | 6568 | }, comp.uid); |
| | 6569 | |
| | 6570 | Events.addEvent(Dom.get(_options.container), 'mouseup', function() { |
| | 6571 | comp.trigger('mouseup'); |
| | 6572 | }, comp.uid); |
| | 6573 | |
| | 6574 | |
| | 6575 | input.onchange = function onChange(e) { // there should be only one handler for this |
| | 6576 | comp.files = []; |
| | 6577 | |
| | 6578 | Basic.each(this.files, function(file) { |
| | 6579 | var relativePath = ''; |
| | 6580 | |
| | 6581 | if (_options.directory) { |
| | 6582 | // folders are represented by dots, filter them out (Chrome 11+) |
| | 6583 | if (file.name == ".") { |
| | 6584 | // if it looks like a folder... |
| | 6585 | return true; |
| | 6586 | } |
| | 6587 | } |
| | 6588 | |
| | 6589 | if (file.webkitRelativePath) { |
| | 6590 | relativePath = '/' + file.webkitRelativePath.replace(/^\//, ''); |
| | 6591 | } |
| | 6592 | |
| | 6593 | file = new File(I.uid, file); |
| | 6594 | file.relativePath = relativePath; |
| | 6595 | |
| | 6596 | comp.files.push(file); |
| | 6597 | }); |
| | 6598 | |
| | 6599 | // clearing the value enables the user to select the same file again if they want to |
| | 6600 | if (Env.browser !== 'IE' && Env.browser !== 'IEMobile') { |
| | 6601 | this.value = ''; |
| | 6602 | } else { |
| | 6603 | // in IE input[type="file"] is read-only so the only way to reset it is to re-insert it |
| | 6604 | var clone = this.cloneNode(true); |
| | 6605 | this.parentNode.replaceChild(clone, this); |
| | 6606 | clone.onchange = onChange; |
| | 6607 | } |
| | 6608 | |
| | 6609 | if (comp.files.length) { |
| | 6610 | comp.trigger('change'); |
| | 6611 | } |
| | 6612 | }; |
| | 6613 | |
| | 6614 | // ready event is perfectly asynchronous |
| | 6615 | comp.trigger({ |
| | 6616 | type: 'ready', |
| | 6617 | async: true |
| | 6618 | }); |
| | 6619 | |
| | 6620 | shimContainer = null; |
| | 6621 | }, |
| | 6622 | |
| | 6623 | |
| | 6624 | disable: function(state) { |
| | 6625 | var I = this.getRuntime(), input; |
| | 6626 | |
| | 6627 | if ((input = Dom.get(I.uid))) { |
| | 6628 | input.disabled = !!state; |
| | 6629 | } |
| | 6630 | }, |
| | 6631 | |
| | 6632 | destroy: function() { |
| | 6633 | var I = this.getRuntime() |
| | 6634 | , shim = I.getShim() |
| | 6635 | , shimContainer = I.getShimContainer() |
| | 6636 | ; |
| | 6637 | |
| | 6638 | Events.removeAllEvents(shimContainer, this.uid); |
| | 6639 | Events.removeAllEvents(_options && Dom.get(_options.container), this.uid); |
| | 6640 | Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid); |
| | 6641 | |
| | 6642 | if (shimContainer) { |
| | 6643 | shimContainer.innerHTML = ''; |
| | 6644 | } |
| | 6645 | |
| | 6646 | shim.removeInstance(this.uid); |
| | 6647 | |
| | 6648 | _options = shimContainer = shim = null; |
| | 6649 | } |
| | 6650 | }); |
| | 6651 | } |
| | 6652 | |
| | 6653 | return (extensions.FileInput = FileInput); |
| | 6654 | }); |
| | 6655 | |
| | 6656 | // Included from: src/javascript/runtime/html5/file/Blob.js |
| | 6657 | |
| | 6658 | /** |
| | 6659 | * Blob.js |
| | 6660 | * |
| | 6661 | * Copyright 2013, Moxiecode Systems AB |
| | 6662 | * Released under GPL License. |
| | 6663 | * |
| | 6664 | * License: http://www.plupload.com/license |
| | 6665 | * Contributing: http://www.plupload.com/contributing |
| | 6666 | */ |
| | 6667 | |
| | 6668 | /** |
| | 6669 | @class moxie/runtime/html5/file/Blob |
| | 6670 | @private |
| | 6671 | */ |
| | 6672 | define("moxie/runtime/html5/file/Blob", [ |
| | 6673 | "moxie/runtime/html5/Runtime", |
| | 6674 | "moxie/file/Blob" |
| | 6675 | ], function(extensions, Blob) { |
| | 6676 | |
| | 6677 | function HTML5Blob() { |
| | 6678 | function w3cBlobSlice(blob, start, end) { |
| | 6679 | var blobSlice; |
| | 6680 | |
| | 6681 | if (window.File.prototype.slice) { |
| | 6682 | try { |
| | 6683 | blob.slice(); // depricated version will throw WRONG_ARGUMENTS_ERR exception |
| | 6684 | return blob.slice(start, end); |
| | 6685 | } catch (e) { |
| | 6686 | // depricated slice method |
| | 6687 | return blob.slice(start, end - start); |
| | 6688 | } |
| | 6689 | // slice method got prefixed: https://bugzilla.mozilla.org/show_bug.cgi?id=649672 |
| | 6690 | } else if ((blobSlice = window.File.prototype.webkitSlice || window.File.prototype.mozSlice)) { |
| | 6691 | return blobSlice.call(blob, start, end); |
| | 6692 | } else { |
| | 6693 | return null; // or throw some exception |
| | 6694 | } |
| | 6695 | } |
| | 6696 | |
| | 6697 | this.slice = function() { |
| | 6698 | return new Blob(this.getRuntime().uid, w3cBlobSlice.apply(this, arguments)); |
| | 6699 | }; |
| | 6700 | } |
| | 6701 | |
| | 6702 | return (extensions.Blob = HTML5Blob); |
| | 6703 | }); |
| | 6704 | |
| | 6705 | // Included from: src/javascript/runtime/html5/file/FileDrop.js |
| | 6706 | |
| | 6707 | /** |
| | 6708 | * FileDrop.js |
| | 6709 | * |
| | 6710 | * Copyright 2013, Moxiecode Systems AB |
| | 6711 | * Released under GPL License. |
| | 6712 | * |
| | 6713 | * License: http://www.plupload.com/license |
| | 6714 | * Contributing: http://www.plupload.com/contributing |
| | 6715 | */ |
| | 6716 | |
| | 6717 | /** |
| | 6718 | @class moxie/runtime/html5/file/FileDrop |
| | 6719 | @private |
| | 6720 | */ |
| | 6721 | define("moxie/runtime/html5/file/FileDrop", [ |
| | 6722 | "moxie/runtime/html5/Runtime", |
| | 6723 | 'moxie/file/File', |
| | 6724 | "moxie/core/utils/Basic", |
| | 6725 | "moxie/core/utils/Dom", |
| | 6726 | "moxie/core/utils/Events", |
| | 6727 | "moxie/core/utils/Mime" |
| | 6728 | ], function(extensions, File, Basic, Dom, Events, Mime) { |
| | 6729 | |
| | 6730 | function FileDrop() { |
| | 6731 | var _files = [], _allowedExts = [], _options, _ruid; |
| | 6732 | |
| | 6733 | Basic.extend(this, { |
| | 6734 | init: function(options) { |
| | 6735 | var comp = this, dropZone; |
| | 6736 | |
| | 6737 | _options = options; |
| | 6738 | _ruid = comp.ruid; // every dropped-in file should have a reference to the runtime |
| | 6739 | _allowedExts = _extractExts(_options.accept); |
| | 6740 | dropZone = _options.container; |
| | 6741 | |
| | 6742 | Events.addEvent(dropZone, 'dragover', function(e) { |
| | 6743 | if (!_hasFiles(e)) { |
| | 6744 | return; |
| | 6745 | } |
| | 6746 | e.preventDefault(); |
| | 6747 | e.dataTransfer.dropEffect = 'copy'; |
| | 6748 | }, comp.uid); |
| | 6749 | |
| | 6750 | Events.addEvent(dropZone, 'drop', function(e) { |
| | 6751 | if (!_hasFiles(e)) { |
| | 6752 | return; |
| | 6753 | } |
| | 6754 | e.preventDefault(); |
| | 6755 | |
| | 6756 | _files = []; |
| | 6757 | |
| | 6758 | // Chrome 21+ accepts folders via Drag'n'Drop |
| | 6759 | if (e.dataTransfer.items && e.dataTransfer.items[0].webkitGetAsEntry) { |
| | 6760 | _readItems(e.dataTransfer.items, function() { |
| | 6761 | comp.files = _files; |
| | 6762 | comp.trigger("drop"); |
| | 6763 | }); |
| | 6764 | } else { |
| | 6765 | Basic.each(e.dataTransfer.files, function(file) { |
| | 6766 | _addFile(file); |
| | 6767 | }); |
| | 6768 | comp.files = _files; |
| | 6769 | comp.trigger("drop"); |
| | 6770 | } |
| | 6771 | }, comp.uid); |
| | 6772 | |
| | 6773 | Events.addEvent(dropZone, 'dragenter', function(e) { |
| | 6774 | comp.trigger("dragenter"); |
| | 6775 | }, comp.uid); |
| | 6776 | |
| | 6777 | Events.addEvent(dropZone, 'dragleave', function(e) { |
| | 6778 | comp.trigger("dragleave"); |
| | 6779 | }, comp.uid); |
| | 6780 | }, |
| | 6781 | |
| | 6782 | destroy: function() { |
| | 6783 | Events.removeAllEvents(_options && Dom.get(_options.container), this.uid); |
| | 6784 | _ruid = _files = _allowedExts = _options = null; |
| | 6785 | } |
| | 6786 | }); |
| | 6787 | |
| | 6788 | |
| | 6789 | function _hasFiles(e) { |
| | 6790 | if (!e.dataTransfer || !e.dataTransfer.types) { // e.dataTransfer.files is not available in Gecko during dragover |
| | 6791 | return false; |
| | 6792 | } |
| | 6793 | |
| | 6794 | var types = Basic.toArray(e.dataTransfer.types || []); |
| | 6795 | |
| | 6796 | return Basic.inArray("Files", types) !== -1 || |
| | 6797 | Basic.inArray("public.file-url", types) !== -1 || // Safari < 5 |
| | 6798 | Basic.inArray("application/x-moz-file", types) !== -1 // Gecko < 1.9.2 (< Firefox 3.6) |
| | 6799 | ; |
| | 6800 | } |
| | 6801 | |
| | 6802 | |
| | 6803 | function _addFile(file, relativePath) { |
| | 6804 | if (_isAcceptable(file)) { |
| | 6805 | var fileObj = new File(_ruid, file); |
| | 6806 | fileObj.relativePath = relativePath || ''; |
| | 6807 | _files.push(fileObj); |
| | 6808 | } |
| | 6809 | } |
| | 6810 | |
| | 6811 | |
| | 6812 | function _extractExts(accept) { |
| | 6813 | var exts = []; |
| | 6814 | for (var i = 0; i < accept.length; i++) { |
| | 6815 | [].push.apply(exts, accept[i].extensions.split(/\s*,\s*/)); |
| | 6816 | } |
| | 6817 | return Basic.inArray('*', exts) === -1 ? exts : []; |
| | 6818 | } |
| | 6819 | |
| | 6820 | |
| | 6821 | function _isAcceptable(file) { |
| | 6822 | if (!_allowedExts.length) { |
| | 6823 | return true; |
| | 6824 | } |
| | 6825 | var ext = Mime.getFileExtension(file.name); |
| | 6826 | return !ext || Basic.inArray(ext, _allowedExts) !== -1; |
| | 6827 | } |
| | 6828 | |
| | 6829 | |
| | 6830 | function _readItems(items, cb) { |
| | 6831 | var entries = []; |
| | 6832 | Basic.each(items, function(item) { |
| | 6833 | var entry = item.webkitGetAsEntry(); |
| | 6834 | // Address #998 (https://code.google.com/p/chromium/issues/detail?id=332579) |
| | 6835 | if (entry) { |
| | 6836 | // file() fails on OSX when the filename contains a special character (e.g. umlaut): see #61 |
| | 6837 | if (entry.isFile) { |
| | 6838 | _addFile(item.getAsFile(), entry.fullPath); |
| | 6839 | } else { |
| | 6840 | entries.push(entry); |
| | 6841 | } |
| | 6842 | } |
| | 6843 | }); |
| | 6844 | |
| | 6845 | if (entries.length) { |
| | 6846 | _readEntries(entries, cb); |
| | 6847 | } else { |
| | 6848 | cb(); |
| | 6849 | } |
| | 6850 | } |
| | 6851 | |
| | 6852 | |
| | 6853 | function _readEntries(entries, cb) { |
| | 6854 | var queue = []; |
| | 6855 | Basic.each(entries, function(entry) { |
| | 6856 | queue.push(function(cbcb) { |
| | 6857 | _readEntry(entry, cbcb); |
| | 6858 | }); |
| | 6859 | }); |
| | 6860 | Basic.inSeries(queue, function() { |
| | 6861 | cb(); |
| | 6862 | }); |
| | 6863 | } |
| | 6864 | |
| | 6865 | |
| | 6866 | function _readEntry(entry, cb) { |
| | 6867 | if (entry.isFile) { |
| | 6868 | entry.file(function(file) { |
| | 6869 | _addFile(file, entry.fullPath); |
| | 6870 | cb(); |
| | 6871 | }, function() { |
| | 6872 | // fire an error event maybe |
| | 6873 | cb(); |
| | 6874 | }); |
| | 6875 | } else if (entry.isDirectory) { |
| | 6876 | _readDirEntry(entry, cb); |
| | 6877 | } else { |
| | 6878 | cb(); // not file, not directory? what then?.. |
| | 6879 | } |
| | 6880 | } |
| | 6881 | |
| | 6882 | |
| | 6883 | function _readDirEntry(dirEntry, cb) { |
| | 6884 | var entries = [], dirReader = dirEntry.createReader(); |
| | 6885 | |
| | 6886 | // keep quering recursively till no more entries |
| | 6887 | function getEntries(cbcb) { |
| | 6888 | dirReader.readEntries(function(moreEntries) { |
| | 6889 | if (moreEntries.length) { |
| | 6890 | [].push.apply(entries, moreEntries); |
| | 6891 | getEntries(cbcb); |
| | 6892 | } else { |
| | 6893 | cbcb(); |
| | 6894 | } |
| | 6895 | }, cbcb); |
| | 6896 | } |
| | 6897 | |
| | 6898 | // ...and you thought FileReader was crazy... |
| | 6899 | getEntries(function() { |
| | 6900 | _readEntries(entries, cb); |
| | 6901 | }); |
| | 6902 | } |
| | 6903 | } |
| | 6904 | |
| | 6905 | return (extensions.FileDrop = FileDrop); |
| | 6906 | }); |
| | 6907 | |
| | 6908 | // Included from: src/javascript/runtime/html5/file/FileReader.js |
| | 6909 | |
| | 6910 | /** |
| | 6911 | * FileReader.js |
| | 6912 | * |
| | 6913 | * Copyright 2013, Moxiecode Systems AB |
| | 6914 | * Released under GPL License. |
| | 6915 | * |
| | 6916 | * License: http://www.plupload.com/license |
| | 6917 | * Contributing: http://www.plupload.com/contributing |
| | 6918 | */ |
| | 6919 | |
| | 6920 | /** |
| | 6921 | @class moxie/runtime/html5/file/FileReader |
| | 6922 | @private |
| | 6923 | */ |
| | 6924 | define("moxie/runtime/html5/file/FileReader", [ |
| | 6925 | "moxie/runtime/html5/Runtime", |
| | 6926 | "moxie/core/utils/Encode", |
| | 6927 | "moxie/core/utils/Basic" |
| | 6928 | ], function(extensions, Encode, Basic) { |
| | 6929 | |
| | 6930 | function FileReader() { |
| | 6931 | var _fr, _convertToBinary = false; |
| | 6932 | |
| | 6933 | Basic.extend(this, { |
| | 6934 | |
| | 6935 | read: function(op, blob) { |
| | 6936 | var comp = this; |
| | 6937 | |
| | 6938 | comp.result = ''; |
| | 6939 | |
| | 6940 | _fr = new window.FileReader(); |
| | 6941 | |
| | 6942 | _fr.addEventListener('progress', function(e) { |
| | 6943 | comp.trigger(e); |
| | 6944 | }); |
| | 6945 | |
| | 6946 | _fr.addEventListener('load', function(e) { |
| | 6947 | comp.result = _convertToBinary ? _toBinary(_fr.result) : _fr.result; |
| | 6948 | comp.trigger(e); |
| | 6949 | }); |
| | 6950 | |
| | 6951 | _fr.addEventListener('error', function(e) { |
| | 6952 | comp.trigger(e, _fr.error); |
| | 6953 | }); |
| | 6954 | |
| | 6955 | _fr.addEventListener('loadend', function(e) { |
| | 6956 | _fr = null; |
| | 6957 | comp.trigger(e); |
| | 6958 | }); |
| | 6959 | |
| | 6960 | if (Basic.typeOf(_fr[op]) === 'function') { |
| | 6961 | _convertToBinary = false; |
| | 6962 | _fr[op](blob.getSource()); |
| | 6963 | } else if (op === 'readAsBinaryString') { // readAsBinaryString is depricated in general and never existed in IE10+ |
| | 6964 | _convertToBinary = true; |
| | 6965 | _fr.readAsDataURL(blob.getSource()); |
| | 6966 | } |
| | 6967 | }, |
| | 6968 | |
| | 6969 | abort: function() { |
| | 6970 | if (_fr) { |
| | 6971 | _fr.abort(); |
| | 6972 | } |
| | 6973 | }, |
| | 6974 | |
| | 6975 | destroy: function() { |
| | 6976 | _fr = null; |
| | 6977 | } |
| | 6978 | }); |
| | 6979 | |
| | 6980 | function _toBinary(str) { |
| | 6981 | return Encode.atob(str.substring(str.indexOf('base64,') + 7)); |
| | 6982 | } |
| | 6983 | } |
| | 6984 | |
| | 6985 | return (extensions.FileReader = FileReader); |
| | 6986 | }); |
| | 6987 | |
| | 6988 | // Included from: src/javascript/runtime/html5/xhr/XMLHttpRequest.js |
| | 6989 | |
| | 6990 | /** |
| | 6991 | * XMLHttpRequest.js |
| | 6992 | * |
| | 6993 | * Copyright 2013, Moxiecode Systems AB |
| | 6994 | * Released under GPL License. |
| | 6995 | * |
| | 6996 | * License: http://www.plupload.com/license |
| | 6997 | * Contributing: http://www.plupload.com/contributing |
| | 6998 | */ |
| | 6999 | |
| | 7000 | /*global ActiveXObject:true */ |
| | 7001 | |
| | 7002 | /** |
| | 7003 | @class moxie/runtime/html5/xhr/XMLHttpRequest |
| | 7004 | @private |
| | 7005 | */ |
| | 7006 | define("moxie/runtime/html5/xhr/XMLHttpRequest", [ |
| | 7007 | "moxie/runtime/html5/Runtime", |
| | 7008 | "moxie/core/utils/Basic", |
| | 7009 | "moxie/core/utils/Mime", |
| | 7010 | "moxie/core/utils/Url", |
| | 7011 | "moxie/file/File", |
| | 7012 | "moxie/file/Blob", |
| | 7013 | "moxie/xhr/FormData", |
| | 7014 | "moxie/core/Exceptions", |
| | 7015 | "moxie/core/utils/Env" |
| | 7016 | ], function(extensions, Basic, Mime, Url, File, Blob, FormData, x, Env) { |
| | 7017 | |
| | 7018 | function XMLHttpRequest() { |
| | 7019 | var self = this |
| | 7020 | , _xhr |
| | 7021 | , _filename |
| | 7022 | ; |
| | 7023 | |
| | 7024 | Basic.extend(this, { |
| | 7025 | send: function(meta, data) { |
| | 7026 | var target = this |
| | 7027 | , isGecko2_5_6 = (Env.browser === 'Mozilla' && Env.verComp(Env.version, 4, '>=') && Env.verComp(Env.version, 7, '<')) |
| | 7028 | , isAndroidBrowser = Env.browser === 'Android Browser' |
| | 7029 | , mustSendAsBinary = false |
| | 7030 | ; |
| | 7031 | |
| | 7032 | // extract file name |
| | 7033 | _filename = meta.url.replace(/^.+?\/([\w\-\.]+)$/, '$1').toLowerCase(); |
| | 7034 | |
| | 7035 | _xhr = _getNativeXHR(); |
| | 7036 | _xhr.open(meta.method, meta.url, meta.async, meta.user, meta.password); |
| | 7037 | |
| | 7038 | |
| | 7039 | // prepare data to be sent |
| | 7040 | if (data instanceof Blob) { |
| | 7041 | if (data.isDetached()) { |
| | 7042 | mustSendAsBinary = true; |
| | 7043 | } |
| | 7044 | data = data.getSource(); |
| | 7045 | } else if (data instanceof FormData) { |
| | 7046 | |
| | 7047 | if (data.hasBlob()) { |
| | 7048 | if (data.getBlob().isDetached()) { |
| | 7049 | data = _prepareMultipart.call(target, data); // _xhr must be instantiated and be in OPENED state |
| | 7050 | mustSendAsBinary = true; |
| | 7051 | } else if ((isGecko2_5_6 || isAndroidBrowser) && Basic.typeOf(data.getBlob().getSource()) === 'blob' && window.FileReader) { |
| | 7052 | // Gecko 2/5/6 can't send blob in FormData: https://bugzilla.mozilla.org/show_bug.cgi?id=649150 |
| | 7053 | // Android browsers (default one and Dolphin) seem to have the same issue, see: #613 |
| | 7054 | _preloadAndSend.call(target, meta, data); |
| | 7055 | return; // _preloadAndSend will reinvoke send() with transmutated FormData =%D |
| | 7056 | } |
| | 7057 | } |
| | 7058 | |
| | 7059 | // transfer fields to real FormData |
| | 7060 | if (data instanceof FormData) { // if still a FormData, e.g. not mangled by _prepareMultipart() |
| | 7061 | var fd = new window.FormData(); |
| | 7062 | data.each(function(value, name) { |
| | 7063 | if (value instanceof Blob) { |
| | 7064 | fd.append(name, value.getSource()); |
| | 7065 | } else { |
| | 7066 | fd.append(name, value); |
| | 7067 | } |
| | 7068 | }); |
| | 7069 | data = fd; |
| | 7070 | } |
| | 7071 | } |
| | 7072 | |
| | 7073 | |
| | 7074 | // if XHR L2 |
| | 7075 | if (_xhr.upload) { |
| | 7076 | if (meta.withCredentials) { |
| | 7077 | _xhr.withCredentials = true; |
| | 7078 | } |
| | 7079 | |
| | 7080 | _xhr.addEventListener('load', function(e) { |
| | 7081 | target.trigger(e); |
| | 7082 | }); |
| | 7083 | |
| | 7084 | _xhr.addEventListener('error', function(e) { |
| | 7085 | target.trigger(e); |
| | 7086 | }); |
| | 7087 | |
| | 7088 | // additionally listen to progress events |
| | 7089 | _xhr.addEventListener('progress', function(e) { |
| | 7090 | target.trigger(e); |
| | 7091 | }); |
| | 7092 | |
| | 7093 | _xhr.upload.addEventListener('progress', function(e) { |
| | 7094 | target.trigger({ |
| | 7095 | type: 'UploadProgress', |
| | 7096 | loaded: e.loaded, |
| | 7097 | total: e.total |
| | 7098 | }); |
| | 7099 | }); |
| | 7100 | // ... otherwise simulate XHR L2 |
| | 7101 | } else { |
| | 7102 | _xhr.onreadystatechange = function onReadyStateChange() { |
| | 7103 | |
| | 7104 | // fake Level 2 events |
| | 7105 | switch (_xhr.readyState) { |
| | 7106 | |
| | 7107 | case 1: // XMLHttpRequest.OPENED |
| | 7108 | // readystatechanged is fired twice for OPENED state (in IE and Mozilla) - neu |
| | 7109 | break; |
| | 7110 | |
| | 7111 | // looks like HEADERS_RECEIVED (state 2) is not reported in Opera (or it's old versions) - neu |
| | 7112 | case 2: // XMLHttpRequest.HEADERS_RECEIVED |
| | 7113 | break; |
| | 7114 | |
| | 7115 | case 3: // XMLHttpRequest.LOADING |
| | 7116 | // try to fire progress event for not XHR L2 |
| | 7117 | var total, loaded; |
| | 7118 | |
| | 7119 | try { |
| | 7120 | if (Url.hasSameOrigin(meta.url)) { // Content-Length not accessible for cross-domain on some browsers |
| | 7121 | total = _xhr.getResponseHeader('Content-Length') || 0; // old Safari throws an exception here |
| | 7122 | } |
| | 7123 | |
| | 7124 | if (_xhr.responseText) { // responseText was introduced in IE7 |
| | 7125 | loaded = _xhr.responseText.length; |
| | 7126 | } |
| | 7127 | } catch(ex) { |
| | 7128 | total = loaded = 0; |
| | 7129 | } |
| | 7130 | |
| | 7131 | target.trigger({ |
| | 7132 | type: 'progress', |
| | 7133 | lengthComputable: !!total, |
| | 7134 | total: parseInt(total, 10), |
| | 7135 | loaded: loaded |
| | 7136 | }); |
| | 7137 | break; |
| | 7138 | |
| | 7139 | case 4: // XMLHttpRequest.DONE |
| | 7140 | // release readystatechange handler (mostly for IE) |
| | 7141 | _xhr.onreadystatechange = function() {}; |
| | 7142 | |
| | 7143 | // usually status 0 is returned when server is unreachable, but FF also fails to status 0 for 408 timeout |
| | 7144 | if (_xhr.status === 0) { |
| | 7145 | target.trigger('error'); |
| | 7146 | } else { |
| | 7147 | target.trigger('load'); |
| | 7148 | } |
| | 7149 | break; |
| | 7150 | } |
| | 7151 | }; |
| | 7152 | } |
| | 7153 | |
| | 7154 | |
| | 7155 | // set request headers |
| | 7156 | if (!Basic.isEmptyObj(meta.headers)) { |
| | 7157 | Basic.each(meta.headers, function(value, header) { |
| | 7158 | _xhr.setRequestHeader(header, value); |
| | 7159 | }); |
| | 7160 | } |
| | 7161 | |
| | 7162 | // request response type |
| | 7163 | if ("" !== meta.responseType && 'responseType' in _xhr) { |
| | 7164 | if ('json' === meta.responseType && !Env.can('return_response_type', 'json')) { // we can fake this one |
| | 7165 | _xhr.responseType = 'text'; |
| | 7166 | } else { |
| | 7167 | _xhr.responseType = meta.responseType; |
| | 7168 | } |
| | 7169 | } |
| | 7170 | |
| | 7171 | // send ... |
| | 7172 | if (!mustSendAsBinary) { |
| | 7173 | _xhr.send(data); |
| | 7174 | } else { |
| | 7175 | if (_xhr.sendAsBinary) { // Gecko |
| | 7176 | _xhr.sendAsBinary(data); |
| | 7177 | } else { // other browsers having support for typed arrays |
| | 7178 | (function() { |
| | 7179 | // mimic Gecko's sendAsBinary |
| | 7180 | var ui8a = new Uint8Array(data.length); |
| | 7181 | for (var i = 0; i < data.length; i++) { |
| | 7182 | ui8a[i] = (data.charCodeAt(i) & 0xff); |
| | 7183 | } |
| | 7184 | _xhr.send(ui8a.buffer); |
| | 7185 | }()); |
| | 7186 | } |
| | 7187 | } |
| | 7188 | |
| | 7189 | target.trigger('loadstart'); |
| | 7190 | }, |
| | 7191 | |
| | 7192 | getStatus: function() { |
| | 7193 | // according to W3C spec it should return 0 for readyState < 3, but instead it throws an exception |
| | 7194 | try { |
| | 7195 | if (_xhr) { |
| | 7196 | return _xhr.status; |
| | 7197 | } |
| | 7198 | } catch(ex) {} |
| | 7199 | return 0; |
| | 7200 | }, |
| | 7201 | |
| | 7202 | getResponse: function(responseType) { |
| | 7203 | var I = this.getRuntime(); |
| | 7204 | |
| | 7205 | try { |
| | 7206 | switch (responseType) { |
| | 7207 | case 'blob': |
| | 7208 | var file = new File(I.uid, _xhr.response); |
| | 7209 | |
| | 7210 | // try to extract file name from content-disposition if possible (might be - not, if CORS for example) |
| | 7211 | var disposition = _xhr.getResponseHeader('Content-Disposition'); |
| | 7212 | if (disposition) { |
| | 7213 | // extract filename from response header if available |
| | 7214 | var match = disposition.match(/filename=([\'\"'])([^\1]+)\1/); |
| | 7215 | if (match) { |
| | 7216 | _filename = match[2]; |
| | 7217 | } |
| | 7218 | } |
| | 7219 | file.name = _filename; |
| | 7220 | |
| | 7221 | // pre-webkit Opera doesn't set type property on the blob response |
| | 7222 | if (!file.type) { |
| | 7223 | file.type = Mime.getFileMime(_filename); |
| | 7224 | } |
| | 7225 | return file; |
| | 7226 | |
| | 7227 | case 'json': |
| | 7228 | if (!Env.can('return_response_type', 'json')) { |
| | 7229 | return _xhr.status === 200 && !!window.JSON ? JSON.parse(_xhr.responseText) : null; |
| | 7230 | } |
| | 7231 | return _xhr.response; |
| | 7232 | |
| | 7233 | case 'document': |
| | 7234 | return _getDocument(_xhr); |
| | 7235 | |
| | 7236 | default: |
| | 7237 | return _xhr.responseText !== '' ? _xhr.responseText : null; // against the specs, but for consistency across the runtimes |
| | 7238 | } |
| | 7239 | } catch(ex) { |
| | 7240 | return null; |
| | 7241 | } |
| | 7242 | }, |
| | 7243 | |
| | 7244 | getAllResponseHeaders: function() { |
| | 7245 | try { |
| | 7246 | return _xhr.getAllResponseHeaders(); |
| | 7247 | } catch(ex) {} |
| | 7248 | return ''; |
| | 7249 | }, |
| | 7250 | |
| | 7251 | abort: function() { |
| | 7252 | if (_xhr) { |
| | 7253 | _xhr.abort(); |
| | 7254 | } |
| | 7255 | }, |
| | 7256 | |
| | 7257 | destroy: function() { |
| | 7258 | self = _filename = null; |
| | 7259 | } |
| | 7260 | }); |
| | 7261 | |
| | 7262 | |
| | 7263 | // here we go... ugly fix for ugly bug |
| | 7264 | function _preloadAndSend(meta, data) { |
| | 7265 | var target = this, blob, fr; |
| | 7266 | |
| | 7267 | // get original blob |
| | 7268 | blob = data.getBlob().getSource(); |
| | 7269 | |
| | 7270 | // preload blob in memory to be sent as binary string |
| | 7271 | fr = new window.FileReader(); |
| | 7272 | fr.onload = function() { |
| | 7273 | // overwrite original blob |
| | 7274 | data.append(data.getBlobName(), new Blob(null, { |
| | 7275 | type: blob.type, |
| | 7276 | data: fr.result |
| | 7277 | })); |
| | 7278 | // invoke send operation again |
| | 7279 | self.send.call(target, meta, data); |
| | 7280 | }; |
| | 7281 | fr.readAsBinaryString(blob); |
| | 7282 | } |
| | 7283 | |
| | 7284 | |
| | 7285 | function _getNativeXHR() { |
| | 7286 | if (window.XMLHttpRequest && !(Env.browser === 'IE' && Env.verComp(Env.version, 8, '<'))) { // IE7 has native XHR but it's buggy |
| | 7287 | return new window.XMLHttpRequest(); |
| | 7288 | } else { |
| | 7289 | return (function() { |
| | 7290 | var progIDs = ['Msxml2.XMLHTTP.6.0', 'Microsoft.XMLHTTP']; // if 6.0 available, use it, otherwise failback to default 3.0 |
| | 7291 | for (var i = 0; i < progIDs.length; i++) { |
| | 7292 | try { |
| | 7293 | return new ActiveXObject(progIDs[i]); |
| | 7294 | } catch (ex) {} |
| | 7295 | } |
| | 7296 | })(); |
| | 7297 | } |
| | 7298 | } |
| | 7299 | |
| | 7300 | // @credits Sergey Ilinsky (http://www.ilinsky.com/) |
| | 7301 | function _getDocument(xhr) { |
| | 7302 | var rXML = xhr.responseXML; |
| | 7303 | var rText = xhr.responseText; |
| | 7304 | |
| | 7305 | // Try parsing responseText (@see: http://www.ilinsky.com/articles/XMLHttpRequest/#bugs-ie-responseXML-content-type) |
| | 7306 | if (Env.browser === 'IE' && rText && rXML && !rXML.documentElement && /[^\/]+\/[^\+]+\+xml/.test(xhr.getResponseHeader("Content-Type"))) { |
| | 7307 | rXML = new window.ActiveXObject("Microsoft.XMLDOM"); |
| | 7308 | rXML.async = false; |
| | 7309 | rXML.validateOnParse = false; |
| | 7310 | rXML.loadXML(rText); |
| | 7311 | } |
| | 7312 | |
| | 7313 | // Check if there is no error in document |
| | 7314 | if (rXML) { |
| | 7315 | if ((Env.browser === 'IE' && rXML.parseError !== 0) || !rXML.documentElement || rXML.documentElement.tagName === "parsererror") { |
| | 7316 | return null; |
| | 7317 | } |
| | 7318 | } |
| | 7319 | return rXML; |
| | 7320 | } |
| | 7321 | |
| | 7322 | |
| | 7323 | function _prepareMultipart(fd) { |
| | 7324 | var boundary = '----moxieboundary' + new Date().getTime() |
| | 7325 | , dashdash = '--' |
| | 7326 | , crlf = '\r\n' |
| | 7327 | , multipart = '' |
| | 7328 | , I = this.getRuntime() |
| | 7329 | ; |
| | 7330 | |
| | 7331 | if (!I.can('send_binary_string')) { |
| | 7332 | throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR); |
| | 7333 | } |
| | 7334 | |
| | 7335 | _xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); |
| | 7336 | |
| | 7337 | // append multipart parameters |
| | 7338 | fd.each(function(value, name) { |
| | 7339 | // Firefox 3.6 failed to convert multibyte characters to UTF-8 in sendAsBinary(), |
| | 7340 | // so we try it here ourselves with: unescape(encodeURIComponent(value)) |
| | 7341 | if (value instanceof Blob) { |
| | 7342 | // Build RFC2388 blob |
| | 7343 | multipart += dashdash + boundary + crlf + |
| | 7344 | 'Content-Disposition: form-data; name="' + name + '"; filename="' + unescape(encodeURIComponent(value.name || 'blob')) + '"' + crlf + |
| | 7345 | 'Content-Type: ' + (value.type || 'application/octet-stream') + crlf + crlf + |
| | 7346 | value.getSource() + crlf; |
| | 7347 | } else { |
| | 7348 | multipart += dashdash + boundary + crlf + |
| | 7349 | 'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf + |
| | 7350 | unescape(encodeURIComponent(value)) + crlf; |
| | 7351 | } |
| | 7352 | }); |
| | 7353 | |
| | 7354 | multipart += dashdash + boundary + dashdash + crlf; |
| | 7355 | |
| | 7356 | return multipart; |
| | 7357 | } |
| | 7358 | } |
| | 7359 | |
| | 7360 | return (extensions.XMLHttpRequest = XMLHttpRequest); |
| | 7361 | }); |
| | 7362 | |
| | 7363 | // Included from: src/javascript/runtime/html5/utils/BinaryReader.js |
| | 7364 | |
| | 7365 | /** |
| | 7366 | * BinaryReader.js |
| | 7367 | * |
| | 7368 | * Copyright 2013, Moxiecode Systems AB |
| | 7369 | * Released under GPL License. |
| | 7370 | * |
| | 7371 | * License: http://www.plupload.com/license |
| | 7372 | * Contributing: http://www.plupload.com/contributing |
| | 7373 | */ |
| | 7374 | |
| | 7375 | /** |
| | 7376 | @class moxie/runtime/html5/utils/BinaryReader |
| | 7377 | @private |
| | 7378 | */ |
| | 7379 | define("moxie/runtime/html5/utils/BinaryReader", [ |
| | 7380 | "moxie/core/utils/Basic" |
| | 7381 | ], function(Basic) { |
| | 7382 | |
| | 7383 | |
| | 7384 | function BinaryReader(data) { |
| | 7385 | if (data instanceof ArrayBuffer) { |
| | 7386 | ArrayBufferReader.apply(this, arguments); |
| | 7387 | } else { |
| | 7388 | UTF16StringReader.apply(this, arguments); |
| | 7389 | } |
| | 7390 | } |
| | 7391 | |
| | 7392 | |
| | 7393 | Basic.extend(BinaryReader.prototype, { |
| | 7394 | |
| | 7395 | littleEndian: false, |
| | 7396 | |
| | 7397 | |
| | 7398 | read: function(idx, size) { |
| | 7399 | var sum, mv, i; |
| | 7400 | |
| | 7401 | if (idx + size > this.length()) { |
| | 7402 | throw new Error("You are trying to read outside the source boundaries."); |
| | 7403 | } |
| | 7404 | |
| | 7405 | mv = this.littleEndian |
| | 7406 | ? 0 |
| | 7407 | : -8 * (size - 1) |
| | 7408 | ; |
| | 7409 | |
| | 7410 | for (i = 0, sum = 0; i < size; i++) { |
| | 7411 | sum |= (this.readByteAt(idx + i) << Math.abs(mv + i*8)); |
| | 7412 | } |
| | 7413 | return sum; |
| | 7414 | }, |
| | 7415 | |
| | 7416 | |
| | 7417 | write: function(idx, num, size) { |
| | 7418 | var mv, i, str = ''; |
| | 7419 | |
| | 7420 | if (idx > this.length()) { |
| | 7421 | throw new Error("You are trying to write outside the source boundaries."); |
| | 7422 | } |
| | 7423 | |
| | 7424 | mv = this.littleEndian |
| | 7425 | ? 0 |
| | 7426 | : -8 * (size - 1) |
| | 7427 | ; |
| | 7428 | |
| | 7429 | for (i = 0; i < size; i++) { |
| | 7430 | this.writeByteAt(idx + i, (num >> Math.abs(mv + i*8)) & 255); |
| | 7431 | } |
| | 7432 | }, |
| | 7433 | |
| | 7434 | |
| | 7435 | BYTE: function(idx) { |
| | 7436 | return this.read(idx, 1); |
| | 7437 | }, |
| | 7438 | |
| | 7439 | |
| | 7440 | SHORT: function(idx) { |
| | 7441 | return this.read(idx, 2); |
| | 7442 | }, |
| | 7443 | |
| | 7444 | |
| | 7445 | LONG: function(idx) { |
| | 7446 | return this.read(idx, 4); |
| | 7447 | }, |
| | 7448 | |
| | 7449 | |
| | 7450 | SLONG: function(idx) { // 2's complement notation |
| | 7451 | var num = this.read(idx, 4); |
| | 7452 | return (num > 2147483647 ? num - 4294967296 : num); |
| | 7453 | }, |
| | 7454 | |
| | 7455 | |
| | 7456 | CHAR: function(idx) { |
| | 7457 | return String.fromCharCode(this.read(idx, 1)); |
| | 7458 | }, |
| | 7459 | |
| | 7460 | |
| | 7461 | STRING: function(idx, count) { |
| | 7462 | return this.asArray('CHAR', idx, count).join(''); |
| | 7463 | }, |
| | 7464 | |
| | 7465 | |
| | 7466 | asArray: function(type, idx, count) { |
| | 7467 | var values = []; |
| | 7468 | |
| | 7469 | for (var i = 0; i < count; i++) { |
| | 7470 | values[i] = this[type](idx + i); |
| | 7471 | } |
| | 7472 | return values; |
| | 7473 | } |
| | 7474 | }); |
| | 7475 | |
| | 7476 | |
| | 7477 | function ArrayBufferReader(data) { |
| | 7478 | var _dv = new DataView(data); |
| | 7479 | |
| | 7480 | Basic.extend(this, { |
| | 7481 | |
| | 7482 | readByteAt: function(idx) { |
| | 7483 | return _dv.getUint8(idx); |
| | 7484 | }, |
| | 7485 | |
| | 7486 | |
| | 7487 | writeByteAt: function(idx, value) { |
| | 7488 | _dv.setUint8(idx, value); |
| | 7489 | }, |
| | 7490 | |
| | 7491 | |
| | 7492 | SEGMENT: function(idx, size, value) { |
| | 7493 | switch (arguments.length) { |
| | 7494 | case 2: |
| | 7495 | return data.slice(idx, idx + size); |
| | 7496 | |
| | 7497 | case 1: |
| | 7498 | return data.slice(idx); |
| | 7499 | |
| | 7500 | case 3: |
| | 7501 | if (value === null) { |
| | 7502 | value = new ArrayBuffer(); |
| | 7503 | } |
| | 7504 | |
| | 7505 | if (value instanceof ArrayBuffer) { |
| | 7506 | var arr = new Uint8Array(this.length() - size + value.byteLength); |
| | 7507 | if (idx > 0) { |
| | 7508 | arr.set(new Uint8Array(data.slice(0, idx)), 0); |
| | 7509 | } |
| | 7510 | arr.set(new Uint8Array(value), idx); |
| | 7511 | arr.set(new Uint8Array(data.slice(idx + size)), idx + value.byteLength); |
| | 7512 | |
| | 7513 | this.clear(); |
| | 7514 | data = arr.buffer; |
| | 7515 | _dv = new DataView(data); |
| | 7516 | break; |
| | 7517 | } |
| | 7518 | |
| | 7519 | default: return data; |
| | 7520 | } |
| | 7521 | }, |
| | 7522 | |
| | 7523 | |
| | 7524 | length: function() { |
| | 7525 | return data ? data.byteLength : 0; |
| | 7526 | }, |
| | 7527 | |
| | 7528 | |
| | 7529 | clear: function() { |
| | 7530 | _dv = data = null; |
| | 7531 | } |
| | 7532 | }); |
| | 7533 | } |
| | 7534 | |
| | 7535 | |
| | 7536 | function UTF16StringReader(data) { |
| | 7537 | Basic.extend(this, { |
| | 7538 | |
| | 7539 | readByteAt: function(idx) { |
| | 7540 | return data.charCodeAt(idx); |
| | 7541 | }, |
| | 7542 | |
| | 7543 | |
| | 7544 | writeByteAt: function(idx, value) { |
| | 7545 | putstr(String.fromCharCode(value), idx, 1); |
| | 7546 | }, |
| | 7547 | |
| | 7548 | |
| | 7549 | SEGMENT: function(idx, length, segment) { |
| | 7550 | switch (arguments.length) { |
| | 7551 | case 1: |
| | 7552 | return data.substr(idx); |
| | 7553 | case 2: |
| | 7554 | return data.substr(idx, length); |
| | 7555 | case 3: |
| | 7556 | putstr(segment !== null ? segment : '', idx, length); |
| | 7557 | break; |
| | 7558 | default: return data; |
| | 7559 | } |
| | 7560 | }, |
| | 7561 | |
| | 7562 | |
| | 7563 | length: function() { |
| | 7564 | return data ? data.length : 0; |
| | 7565 | }, |
| | 7566 | |
| | 7567 | clear: function() { |
| | 7568 | data = null; |
| | 7569 | } |
| | 7570 | }); |
| | 7571 | |
| | 7572 | |
| | 7573 | function putstr(segment, idx, length) { |
| | 7574 | length = arguments.length === 3 ? length : data.length - idx - 1; |
| | 7575 | data = data.substr(0, idx) + segment + data.substr(length + idx); |
| | 7576 | } |
| | 7577 | } |
| | 7578 | |
| | 7579 | |
| | 7580 | return BinaryReader; |
| | 7581 | }); |
| | 7582 | |
| | 7583 | // Included from: src/javascript/runtime/html5/image/JPEGHeaders.js |
| | 7584 | |
| | 7585 | /** |
| | 7586 | * JPEGHeaders.js |
| | 7587 | * |
| | 7588 | * Copyright 2013, Moxiecode Systems AB |
| | 7589 | * Released under GPL License. |
| | 7590 | * |
| | 7591 | * License: http://www.plupload.com/license |
| | 7592 | * Contributing: http://www.plupload.com/contributing |
| | 7593 | */ |
| | 7594 | |
| | 7595 | /** |
| | 7596 | @class moxie/runtime/html5/image/JPEGHeaders |
| | 7597 | @private |
| | 7598 | */ |
| | 7599 | define("moxie/runtime/html5/image/JPEGHeaders", [ |
| | 7600 | "moxie/runtime/html5/utils/BinaryReader", |
| | 7601 | "moxie/core/Exceptions" |
| | 7602 | ], function(BinaryReader, x) { |
| | 7603 | |
| | 7604 | return function JPEGHeaders(data) { |
| | 7605 | var headers = [], _br, idx, marker, length = 0; |
| | 7606 | |
| | 7607 | _br = new BinaryReader(data); |
| | 7608 | |
| | 7609 | // Check if data is jpeg |
| | 7610 | if (_br.SHORT(0) !== 0xFFD8) { |
| | 7611 | _br.clear(); |
| | 7612 | throw new x.ImageError(x.ImageError.WRONG_FORMAT); |
| | 7613 | } |
| | 7614 | |
| | 7615 | idx = 2; |
| | 7616 | |
| | 7617 | while (idx <= _br.length()) { |
| | 7618 | marker = _br.SHORT(idx); |
| | 7619 | |
| | 7620 | // omit RST (restart) markers |
| | 7621 | if (marker >= 0xFFD0 && marker <= 0xFFD7) { |
| | 7622 | idx += 2; |
| | 7623 | continue; |
| | 7624 | } |
| | 7625 | |
| | 7626 | // no headers allowed after SOS marker |
| | 7627 | if (marker === 0xFFDA || marker === 0xFFD9) { |
| | 7628 | break; |
| | 7629 | } |
| | 7630 | |
| | 7631 | length = _br.SHORT(idx + 2) + 2; |
| | 7632 | |
| | 7633 | // APPn marker detected |
| | 7634 | if (marker >= 0xFFE1 && marker <= 0xFFEF) { |
| | 7635 | headers.push({ |
| | 7636 | hex: marker, |
| | 7637 | name: 'APP' + (marker & 0x000F), |
| | 7638 | start: idx, |
| | 7639 | length: length, |
| | 7640 | segment: _br.SEGMENT(idx, length) |
| | 7641 | }); |
| | 7642 | } |
| | 7643 | |
| | 7644 | idx += length; |
| | 7645 | } |
| | 7646 | |
| | 7647 | _br.clear(); |
| | 7648 | |
| | 7649 | return { |
| | 7650 | headers: headers, |
| | 7651 | |
| | 7652 | restore: function(data) { |
| | 7653 | var max, i, br; |
| | 7654 | |
| | 7655 | br = new BinaryReader(data); |
| | 7656 | |
| | 7657 | idx = br.SHORT(2) == 0xFFE0 ? 4 + br.SHORT(4) : 2; |
| | 7658 | |
| | 7659 | for (i = 0, max = headers.length; i < max; i++) { |
| | 7660 | br.SEGMENT(idx, 0, headers[i].segment); |
| | 7661 | idx += headers[i].length; |
| | 7662 | } |
| | 7663 | |
| | 7664 | data = br.SEGMENT(); |
| | 7665 | br.clear(); |
| | 7666 | return data; |
| | 7667 | }, |
| | 7668 | |
| | 7669 | strip: function(data) { |
| | 7670 | var br, headers, jpegHeaders, i; |
| | 7671 | |
| | 7672 | jpegHeaders = new JPEGHeaders(data); |
| | 7673 | headers = jpegHeaders.headers; |
| | 7674 | jpegHeaders.purge(); |
| | 7675 | |
| | 7676 | br = new BinaryReader(data); |
| | 7677 | |
| | 7678 | i = headers.length; |
| | 7679 | while (i--) { |
| | 7680 | br.SEGMENT(headers[i].start, headers[i].length, ''); |
| | 7681 | } |
| | 7682 | |
| | 7683 | data = br.SEGMENT(); |
| | 7684 | br.clear(); |
| | 7685 | return data; |
| | 7686 | }, |
| | 7687 | |
| | 7688 | get: function(name) { |
| | 7689 | var array = []; |
| | 7690 | |
| | 7691 | for (var i = 0, max = headers.length; i < max; i++) { |
| | 7692 | if (headers[i].name === name.toUpperCase()) { |
| | 7693 | array.push(headers[i].segment); |
| | 7694 | } |
| | 7695 | } |
| | 7696 | return array; |
| | 7697 | }, |
| | 7698 | |
| | 7699 | set: function(name, segment) { |
| | 7700 | var array = [], i, ii, max; |
| | 7701 | |
| | 7702 | if (typeof(segment) === 'string') { |
| | 7703 | array.push(segment); |
| | 7704 | } else { |
| | 7705 | array = segment; |
| | 7706 | } |
| | 7707 | |
| | 7708 | for (i = ii = 0, max = headers.length; i < max; i++) { |
| | 7709 | if (headers[i].name === name.toUpperCase()) { |
| | 7710 | headers[i].segment = array[ii]; |
| | 7711 | headers[i].length = array[ii].length; |
| | 7712 | ii++; |
| | 7713 | } |
| | 7714 | if (ii >= array.length) { |
| | 7715 | break; |
| | 7716 | } |
| | 7717 | } |
| | 7718 | }, |
| | 7719 | |
| | 7720 | purge: function() { |
| | 7721 | this.headers = headers = []; |
| | 7722 | } |
| | 7723 | }; |
| | 7724 | }; |
| | 7725 | }); |
| | 7726 | |
| | 7727 | // Included from: src/javascript/runtime/html5/image/ExifParser.js |
| | 7728 | |
| | 7729 | /** |
| | 7730 | * ExifParser.js |
| | 7731 | * |
| | 7732 | * Copyright 2013, Moxiecode Systems AB |
| | 7733 | * Released under GPL License. |
| | 7734 | * |
| | 7735 | * License: http://www.plupload.com/license |
| | 7736 | * Contributing: http://www.plupload.com/contributing |
| | 7737 | */ |
| | 7738 | |
| | 7739 | /** |
| | 7740 | @class moxie/runtime/html5/image/ExifParser |
| | 7741 | @private |
| | 7742 | */ |
| | 7743 | define("moxie/runtime/html5/image/ExifParser", [ |
| | 7744 | "moxie/core/utils/Basic", |
| | 7745 | "moxie/runtime/html5/utils/BinaryReader", |
| | 7746 | "moxie/core/Exceptions" |
| | 7747 | ], function(Basic, BinaryReader, x) { |
| | 7748 | |
| | 7749 | function ExifParser(data) { |
| | 7750 | var __super__, tags, tagDescs, offsets, idx, Tiff; |
| | 7751 | |
| | 7752 | BinaryReader.call(this, data); |
| | 7753 | |
| | 7754 | tags = { |
| | 7755 | tiff: { |
| | 7756 | /* |
| | 7757 | The image orientation viewed in terms of rows and columns. |
| | 7758 | |
| | 7759 | 1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side. |
| | 7760 | 2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side. |
| | 7761 | 3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side. |
| | 7762 | 4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side. |
| | 7763 | 5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top. |
| | 7764 | 6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top. |
| | 7765 | 7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom. |
| | 7766 | 8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom. |
| | 7767 | */ |
| | 7768 | 0x0112: 'Orientation', |
| | 7769 | 0x010E: 'ImageDescription', |
| | 7770 | 0x010F: 'Make', |
| | 7771 | 0x0110: 'Model', |
| | 7772 | 0x0131: 'Software', |
| | 7773 | 0x8769: 'ExifIFDPointer', |
| | 7774 | 0x8825: 'GPSInfoIFDPointer' |
| | 7775 | }, |
| | 7776 | exif: { |
| | 7777 | 0x9000: 'ExifVersion', |
| | 7778 | 0xA001: 'ColorSpace', |
| | 7779 | 0xA002: 'PixelXDimension', |
| | 7780 | 0xA003: 'PixelYDimension', |
| | 7781 | 0x9003: 'DateTimeOriginal', |
| | 7782 | 0x829A: 'ExposureTime', |
| | 7783 | 0x829D: 'FNumber', |
| | 7784 | 0x8827: 'ISOSpeedRatings', |
| | 7785 | 0x9201: 'ShutterSpeedValue', |
| | 7786 | 0x9202: 'ApertureValue' , |
| | 7787 | 0x9207: 'MeteringMode', |
| | 7788 | 0x9208: 'LightSource', |
| | 7789 | 0x9209: 'Flash', |
| | 7790 | 0x920A: 'FocalLength', |
| | 7791 | 0xA402: 'ExposureMode', |
| | 7792 | 0xA403: 'WhiteBalance', |
| | 7793 | 0xA406: 'SceneCaptureType', |
| | 7794 | 0xA404: 'DigitalZoomRatio', |
| | 7795 | 0xA408: 'Contrast', |
| | 7796 | 0xA409: 'Saturation', |
| | 7797 | 0xA40A: 'Sharpness' |
| | 7798 | }, |
| | 7799 | gps: { |
| | 7800 | 0x0000: 'GPSVersionID', |
| | 7801 | 0x0001: 'GPSLatitudeRef', |
| | 7802 | 0x0002: 'GPSLatitude', |
| | 7803 | 0x0003: 'GPSLongitudeRef', |
| | 7804 | 0x0004: 'GPSLongitude' |
| | 7805 | }, |
| | 7806 | |
| | 7807 | thumb: { |
| | 7808 | 0x0201: 'JPEGInterchangeFormat', |
| | 7809 | 0x0202: 'JPEGInterchangeFormatLength' |
| | 7810 | } |
| | 7811 | }; |
| | 7812 | |
| | 7813 | tagDescs = { |
| | 7814 | 'ColorSpace': { |
| | 7815 | 1: 'sRGB', |
| | 7816 | 0: 'Uncalibrated' |
| | 7817 | }, |
| | 7818 | |
| | 7819 | 'MeteringMode': { |
| | 7820 | 0: 'Unknown', |
| | 7821 | 1: 'Average', |
| | 7822 | 2: 'CenterWeightedAverage', |
| | 7823 | 3: 'Spot', |
| | 7824 | 4: 'MultiSpot', |
| | 7825 | 5: 'Pattern', |
| | 7826 | 6: 'Partial', |
| | 7827 | 255: 'Other' |
| | 7828 | }, |
| | 7829 | |
| | 7830 | 'LightSource': { |
| | 7831 | 1: 'Daylight', |
| | 7832 | 2: 'Fliorescent', |
| | 7833 | 3: 'Tungsten', |
| | 7834 | 4: 'Flash', |
| | 7835 | 9: 'Fine weather', |
| | 7836 | 10: 'Cloudy weather', |
| | 7837 | 11: 'Shade', |
| | 7838 | 12: 'Daylight fluorescent (D 5700 - 7100K)', |
| | 7839 | 13: 'Day white fluorescent (N 4600 -5400K)', |
| | 7840 | 14: 'Cool white fluorescent (W 3900 - 4500K)', |
| | 7841 | 15: 'White fluorescent (WW 3200 - 3700K)', |
| | 7842 | 17: 'Standard light A', |
| | 7843 | 18: 'Standard light B', |
| | 7844 | 19: 'Standard light C', |
| | 7845 | 20: 'D55', |
| | 7846 | 21: 'D65', |
| | 7847 | 22: 'D75', |
| | 7848 | 23: 'D50', |
| | 7849 | 24: 'ISO studio tungsten', |
| | 7850 | 255: 'Other' |
| | 7851 | }, |
| | 7852 | |
| | 7853 | 'Flash': { |
| | 7854 | 0x0000: 'Flash did not fire', |
| | 7855 | 0x0001: 'Flash fired', |
| | 7856 | 0x0005: 'Strobe return light not detected', |
| | 7857 | 0x0007: 'Strobe return light detected', |
| | 7858 | 0x0009: 'Flash fired, compulsory flash mode', |
| | 7859 | 0x000D: 'Flash fired, compulsory flash mode, return light not detected', |
| | 7860 | 0x000F: 'Flash fired, compulsory flash mode, return light detected', |
| | 7861 | 0x0010: 'Flash did not fire, compulsory flash mode', |
| | 7862 | 0x0018: 'Flash did not fire, auto mode', |
| | 7863 | 0x0019: 'Flash fired, auto mode', |
| | 7864 | 0x001D: 'Flash fired, auto mode, return light not detected', |
| | 7865 | 0x001F: 'Flash fired, auto mode, return light detected', |
| | 7866 | 0x0020: 'No flash function', |
| | 7867 | 0x0041: 'Flash fired, red-eye reduction mode', |
| | 7868 | 0x0045: 'Flash fired, red-eye reduction mode, return light not detected', |
| | 7869 | 0x0047: 'Flash fired, red-eye reduction mode, return light detected', |
| | 7870 | 0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode', |
| | 7871 | 0x004D: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected', |
| | 7872 | 0x004F: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected', |
| | 7873 | 0x0059: 'Flash fired, auto mode, red-eye reduction mode', |
| | 7874 | 0x005D: 'Flash fired, auto mode, return light not detected, red-eye reduction mode', |
| | 7875 | 0x005F: 'Flash fired, auto mode, return light detected, red-eye reduction mode' |
| | 7876 | }, |
| | 7877 | |
| | 7878 | 'ExposureMode': { |
| | 7879 | 0: 'Auto exposure', |
| | 7880 | 1: 'Manual exposure', |
| | 7881 | 2: 'Auto bracket' |
| | 7882 | }, |
| | 7883 | |
| | 7884 | 'WhiteBalance': { |
| | 7885 | 0: 'Auto white balance', |
| | 7886 | 1: 'Manual white balance' |
| | 7887 | }, |
| | 7888 | |
| | 7889 | 'SceneCaptureType': { |
| | 7890 | 0: 'Standard', |
| | 7891 | 1: 'Landscape', |
| | 7892 | 2: 'Portrait', |
| | 7893 | 3: 'Night scene' |
| | 7894 | }, |
| | 7895 | |
| | 7896 | 'Contrast': { |
| | 7897 | 0: 'Normal', |
| | 7898 | 1: 'Soft', |
| | 7899 | 2: 'Hard' |
| | 7900 | }, |
| | 7901 | |
| | 7902 | 'Saturation': { |
| | 7903 | 0: 'Normal', |
| | 7904 | 1: 'Low saturation', |
| | 7905 | 2: 'High saturation' |
| | 7906 | }, |
| | 7907 | |
| | 7908 | 'Sharpness': { |
| | 7909 | 0: 'Normal', |
| | 7910 | 1: 'Soft', |
| | 7911 | 2: 'Hard' |
| | 7912 | }, |
| | 7913 | |
| | 7914 | // GPS related |
| | 7915 | 'GPSLatitudeRef': { |
| | 7916 | N: 'North latitude', |
| | 7917 | S: 'South latitude' |
| | 7918 | }, |
| | 7919 | |
| | 7920 | 'GPSLongitudeRef': { |
| | 7921 | E: 'East longitude', |
| | 7922 | W: 'West longitude' |
| | 7923 | } |
| | 7924 | }; |
| | 7925 | |
| | 7926 | offsets = { |
| | 7927 | tiffHeader: 10 |
| | 7928 | }; |
| | 7929 | |
| | 7930 | idx = offsets.tiffHeader; |
| | 7931 | |
| | 7932 | __super__ = { |
| | 7933 | clear: this.clear |
| | 7934 | }; |
| | 7935 | |
| | 7936 | // Public functions |
| | 7937 | Basic.extend(this, { |
| | 7938 | |
| | 7939 | read: function() { |
| | 7940 | try { |
| | 7941 | return ExifParser.prototype.read.apply(this, arguments); |
| | 7942 | } catch (ex) { |
| | 7943 | throw new x.ImageError(x.ImageError.INVALID_META_ERR); |
| | 7944 | } |
| | 7945 | }, |
| | 7946 | |
| | 7947 | |
| | 7948 | write: function() { |
| | 7949 | try { |
| | 7950 | return ExifParser.prototype.write.apply(this, arguments); |
| | 7951 | } catch (ex) { |
| | 7952 | throw new x.ImageError(x.ImageError.INVALID_META_ERR); |
| | 7953 | } |
| | 7954 | }, |
| | 7955 | |
| | 7956 | |
| | 7957 | UNDEFINED: function() { |
| | 7958 | return this.BYTE.apply(this, arguments); |
| | 7959 | }, |
| | 7960 | |
| | 7961 | |
| | 7962 | RATIONAL: function(idx) { |
| | 7963 | return this.LONG(idx) / this.LONG(idx + 4) |
| | 7964 | }, |
| | 7965 | |
| | 7966 | |
| | 7967 | SRATIONAL: function(idx) { |
| | 7968 | return this.SLONG(idx) / this.SLONG(idx + 4) |
| | 7969 | }, |
| | 7970 | |
| | 7971 | ASCII: function(idx) { |
| | 7972 | return this.CHAR(idx); |
| | 7973 | }, |
| | 7974 | |
| | 7975 | TIFF: function() { |
| | 7976 | return Tiff || null; |
| | 7977 | }, |
| | 7978 | |
| | 7979 | |
| | 7980 | EXIF: function() { |
| | 7981 | var Exif = null; |
| | 7982 | |
| | 7983 | if (offsets.exifIFD) { |
| | 7984 | try { |
| | 7985 | Exif = extractTags.call(this, offsets.exifIFD, tags.exif); |
| | 7986 | } catch(ex) { |
| | 7987 | return null; |
| | 7988 | } |
| | 7989 | |
| | 7990 | // Fix formatting of some tags |
| | 7991 | if (Exif.ExifVersion && Basic.typeOf(Exif.ExifVersion) === 'array') { |
| | 7992 | for (var i = 0, exifVersion = ''; i < Exif.ExifVersion.length; i++) { |
| | 7993 | exifVersion += String.fromCharCode(Exif.ExifVersion[i]); |
| | 7994 | } |
| | 7995 | Exif.ExifVersion = exifVersion; |
| | 7996 | } |
| | 7997 | } |
| | 7998 | |
| | 7999 | return Exif; |
| | 8000 | }, |
| | 8001 | |
| | 8002 | |
| | 8003 | GPS: function() { |
| | 8004 | var GPS = null; |
| | 8005 | |
| | 8006 | if (offsets.gpsIFD) { |
| | 8007 | try { |
| | 8008 | GPS = extractTags.call(this, offsets.gpsIFD, tags.gps); |
| | 8009 | } catch (ex) { |
| | 8010 | return null; |
| | 8011 | } |
| | 8012 | |
| | 8013 | // iOS devices (and probably some others) do not put in GPSVersionID tag (why?..) |
| | 8014 | if (GPS.GPSVersionID && Basic.typeOf(GPS.GPSVersionID) === 'array') { |
| | 8015 | GPS.GPSVersionID = GPS.GPSVersionID.join('.'); |
| | 8016 | } |
| | 8017 | } |
| | 8018 | |
| | 8019 | return GPS; |
| | 8020 | }, |
| | 8021 | |
| | 8022 | |
| | 8023 | thumb: function() { |
| | 8024 | if (offsets.IFD1) { |
| | 8025 | try { |
| | 8026 | var IFD1Tags = extractTags.call(this, offsets.IFD1, tags.thumb); |
| | 8027 | |
| | 8028 | if ('JPEGInterchangeFormat' in IFD1Tags) { |
| | 8029 | return this.SEGMENT(offsets.tiffHeader + IFD1Tags.JPEGInterchangeFormat, IFD1Tags.JPEGInterchangeFormatLength); |
| | 8030 | } |
| | 8031 | } catch (ex) {} |
| | 8032 | } |
| | 8033 | return null; |
| | 8034 | }, |
| | 8035 | |
| | 8036 | |
| | 8037 | setExif: function(tag, value) { |
| | 8038 | // Right now only setting of width/height is possible |
| | 8039 | if (tag !== 'PixelXDimension' && tag !== 'PixelYDimension') { return false; } |
| | 8040 | |
| | 8041 | return setTag.call(this, 'exif', tag, value); |
| | 8042 | }, |
| | 8043 | |
| | 8044 | |
| | 8045 | clear: function() { |
| | 8046 | __super__.clear(); |
| | 8047 | data = tags = tagDescs = Tiff = offsets = __super__ = null; |
| | 8048 | } |
| | 8049 | }); |
| | 8050 | |
| | 8051 | |
| | 8052 | // Check if that's APP1 and that it has EXIF |
| | 8053 | if (this.SHORT(0) !== 0xFFE1 || this.STRING(4, 5).toUpperCase() !== "EXIF\0") { |
| | 8054 | throw new x.ImageError(x.ImageError.INVALID_META_ERR); |
| | 8055 | } |
| | 8056 | |
| | 8057 | // Set read order of multi-byte data |
| | 8058 | this.littleEndian = (this.SHORT(idx) == 0x4949); |
| | 8059 | |
| | 8060 | // Check if always present bytes are indeed present |
| | 8061 | if (this.SHORT(idx+=2) !== 0x002A) { |
| | 8062 | throw new x.ImageError(x.ImageError.INVALID_META_ERR); |
| | 8063 | } |
| | 8064 | |
| | 8065 | offsets.IFD0 = offsets.tiffHeader + this.LONG(idx += 2); |
| | 8066 | Tiff = extractTags.call(this, offsets.IFD0, tags.tiff); |
| | 8067 | |
| | 8068 | if ('ExifIFDPointer' in Tiff) { |
| | 8069 | offsets.exifIFD = offsets.tiffHeader + Tiff.ExifIFDPointer; |
| | 8070 | delete Tiff.ExifIFDPointer; |
| | 8071 | } |
| | 8072 | |
| | 8073 | if ('GPSInfoIFDPointer' in Tiff) { |
| | 8074 | offsets.gpsIFD = offsets.tiffHeader + Tiff.GPSInfoIFDPointer; |
| | 8075 | delete Tiff.GPSInfoIFDPointer; |
| | 8076 | } |
| | 8077 | |
| | 8078 | if (Basic.isEmptyObj(Tiff)) { |
| | 8079 | Tiff = null; |
| | 8080 | } |
| | 8081 | |
| | 8082 | // check if we have a thumb as well |
| | 8083 | var IFD1Offset = this.LONG(offsets.IFD0 + this.SHORT(offsets.IFD0) * 12 + 2); |
| | 8084 | if (IFD1Offset) { |
| | 8085 | offsets.IFD1 = offsets.tiffHeader + IFD1Offset; |
| | 8086 | } |
| | 8087 | |
| | 8088 | |
| | 8089 | function extractTags(IFD_offset, tags2extract) { |
| | 8090 | var data = this; |
| | 8091 | var length, i, tag, type, count, size, offset, value, values = [], hash = {}; |
| | 8092 | |
| | 8093 | var types = { |
| | 8094 | 1 : 'BYTE', |
| | 8095 | 7 : 'UNDEFINED', |
| | 8096 | 2 : 'ASCII', |
| | 8097 | 3 : 'SHORT', |
| | 8098 | 4 : 'LONG', |
| | 8099 | 5 : 'RATIONAL', |
| | 8100 | 9 : 'SLONG', |
| | 8101 | 10: 'SRATIONAL' |
| | 8102 | }; |
| | 8103 | |
| | 8104 | var sizes = { |
| | 8105 | 'BYTE' : 1, |
| | 8106 | 'UNDEFINED' : 1, |
| | 8107 | 'ASCII' : 1, |
| | 8108 | 'SHORT' : 2, |
| | 8109 | 'LONG' : 4, |
| | 8110 | 'RATIONAL' : 8, |
| | 8111 | 'SLONG' : 4, |
| | 8112 | 'SRATIONAL' : 8 |
| | 8113 | }; |
| | 8114 | |
| | 8115 | length = data.SHORT(IFD_offset); |
| | 8116 | |
| | 8117 | // The size of APP1 including all these elements shall not exceed the 64 Kbytes specified in the JPEG standard. |
| | 8118 | |
| | 8119 | for (i = 0; i < length; i++) { |
| | 8120 | values = []; |
| | 8121 | |
| | 8122 | // Set binary reader pointer to beginning of the next tag |
| | 8123 | offset = IFD_offset + 2 + i*12; |
| | 8124 | |
| | 8125 | tag = tags2extract[data.SHORT(offset)]; |
| | 8126 | |
| | 8127 | if (tag === undefined) { |
| | 8128 | continue; // Not the tag we requested |
| | 8129 | } |
| | 8130 | |
| | 8131 | type = types[data.SHORT(offset+=2)]; |
| | 8132 | count = data.LONG(offset+=2); |
| | 8133 | size = sizes[type]; |
| | 8134 | |
| | 8135 | if (!size) { |
| | 8136 | throw new x.ImageError(x.ImageError.INVALID_META_ERR); |
| | 8137 | } |
| | 8138 | |
| | 8139 | offset += 4; |
| | 8140 | |
| | 8141 | // tag can only fit 4 bytes of data, if data is larger we should look outside |
| | 8142 | if (size * count > 4) { |
| | 8143 | // instead of data tag contains an offset of the data |
| | 8144 | offset = data.LONG(offset) + offsets.tiffHeader; |
| | 8145 | } |
| | 8146 | |
| | 8147 | // in case we left the boundaries of data throw an early exception |
| | 8148 | if (offset + size * count >= this.length()) { |
| | 8149 | throw new x.ImageError(x.ImageError.INVALID_META_ERR); |
| | 8150 | } |
| | 8151 | |
| | 8152 | // special care for the string |
| | 8153 | if (type === 'ASCII') { |
| | 8154 | hash[tag] = Basic.trim(data.STRING(offset, count).replace(/\0$/, '')); // strip trailing NULL |
| | 8155 | continue; |
| | 8156 | } else { |
| | 8157 | values = data.asArray(type, offset, count); |
| | 8158 | value = (count == 1 ? values[0] : values); |
| | 8159 | |
| | 8160 | if (tagDescs.hasOwnProperty(tag) && typeof value != 'object') { |
| | 8161 | hash[tag] = tagDescs[tag][value]; |
| | 8162 | } else { |
| | 8163 | hash[tag] = value; |
| | 8164 | } |
| | 8165 | } |
| | 8166 | } |
| | 8167 | |
| | 8168 | return hash; |
| | 8169 | } |
| | 8170 | |
| | 8171 | // At the moment only setting of simple (LONG) values, that do not require offset recalculation, is supported |
| | 8172 | function setTag(ifd, tag, value) { |
| | 8173 | var offset, length, tagOffset, valueOffset = 0; |
| | 8174 | |
| | 8175 | // If tag name passed translate into hex key |
| | 8176 | if (typeof(tag) === 'string') { |
| | 8177 | var tmpTags = tags[ifd.toLowerCase()]; |
| | 8178 | for (var hex in tmpTags) { |
| | 8179 | if (tmpTags[hex] === tag) { |
| | 8180 | tag = hex; |
| | 8181 | break; |
| | 8182 | } |
| | 8183 | } |
| | 8184 | } |
| | 8185 | offset = offsets[ifd.toLowerCase() + 'IFD']; |
| | 8186 | length = this.SHORT(offset); |
| | 8187 | |
| | 8188 | for (var i = 0; i < length; i++) { |
| | 8189 | tagOffset = offset + 12 * i + 2; |
| | 8190 | |
| | 8191 | if (this.SHORT(tagOffset) == tag) { |
| | 8192 | valueOffset = tagOffset + 8; |
| | 8193 | break; |
| | 8194 | } |
| | 8195 | } |
| | 8196 | |
| | 8197 | if (!valueOffset) { |
| | 8198 | return false; |
| | 8199 | } |
| | 8200 | |
| | 8201 | try { |
| | 8202 | this.write(valueOffset, value, 4); |
| | 8203 | } catch(ex) { |
| | 8204 | return false; |
| | 8205 | } |
| | 8206 | |
| | 8207 | return true; |
| | 8208 | } |
| | 8209 | } |
| | 8210 | |
| | 8211 | ExifParser.prototype = BinaryReader.prototype; |
| | 8212 | |
| | 8213 | return ExifParser; |
| | 8214 | }); |
| | 8215 | |
| | 8216 | // Included from: src/javascript/runtime/html5/image/JPEG.js |
| | 8217 | |
| | 8218 | /** |
| | 8219 | * JPEG.js |
| | 8220 | * |
| | 8221 | * Copyright 2013, Moxiecode Systems AB |
| | 8222 | * Released under GPL License. |
| | 8223 | * |
| | 8224 | * License: http://www.plupload.com/license |
| | 8225 | * Contributing: http://www.plupload.com/contributing |
| | 8226 | */ |
| | 8227 | |
| | 8228 | /** |
| | 8229 | @class moxie/runtime/html5/image/JPEG |
| | 8230 | @private |
| | 8231 | */ |
| | 8232 | define("moxie/runtime/html5/image/JPEG", [ |
| | 8233 | "moxie/core/utils/Basic", |
| | 8234 | "moxie/core/Exceptions", |
| | 8235 | "moxie/runtime/html5/image/JPEGHeaders", |
| | 8236 | "moxie/runtime/html5/utils/BinaryReader", |
| | 8237 | "moxie/runtime/html5/image/ExifParser" |
| | 8238 | ], function(Basic, x, JPEGHeaders, BinaryReader, ExifParser) { |
| | 8239 | |
| | 8240 | function JPEG(data) { |
| | 8241 | var _br, _hm, _ep, _info; |
| | 8242 | |
| | 8243 | _br = new BinaryReader(data); |
| | 8244 | |
| | 8245 | // check if it is jpeg |
| | 8246 | if (_br.SHORT(0) !== 0xFFD8) { |
| | 8247 | throw new x.ImageError(x.ImageError.WRONG_FORMAT); |
| | 8248 | } |
| | 8249 | |
| | 8250 | // backup headers |
| | 8251 | _hm = new JPEGHeaders(data); |
| | 8252 | |
| | 8253 | // extract exif info |
| | 8254 | try { |
| | 8255 | _ep = new ExifParser(_hm.get('app1')[0]); |
| | 8256 | } catch(ex) {} |
| | 8257 | |
| | 8258 | // get dimensions |
| | 8259 | _info = _getDimensions.call(this); |
| | 8260 | |
| | 8261 | Basic.extend(this, { |
| | 8262 | type: 'image/jpeg', |
| | 8263 | |
| | 8264 | size: _br.length(), |
| | 8265 | |
| | 8266 | width: _info && _info.width || 0, |
| | 8267 | |
| | 8268 | height: _info && _info.height || 0, |
| | 8269 | |
| | 8270 | setExif: function(tag, value) { |
| | 8271 | if (!_ep) { |
| | 8272 | return false; // or throw an exception |
| | 8273 | } |
| | 8274 | |
| | 8275 | if (Basic.typeOf(tag) === 'object') { |
| | 8276 | Basic.each(tag, function(value, tag) { |
| | 8277 | _ep.setExif(tag, value); |
| | 8278 | }); |
| | 8279 | } else { |
| | 8280 | _ep.setExif(tag, value); |
| | 8281 | } |
| | 8282 | |
| | 8283 | // update internal headers |
| | 8284 | _hm.set('app1', _ep.SEGMENT()); |
| | 8285 | }, |
| | 8286 | |
| | 8287 | writeHeaders: function() { |
| | 8288 | if (!arguments.length) { |
| | 8289 | // if no arguments passed, update headers internally |
| | 8290 | return _hm.restore(data); |
| | 8291 | } |
| | 8292 | return _hm.restore(arguments[0]); |
| | 8293 | }, |
| | 8294 | |
| | 8295 | stripHeaders: function(data) { |
| | 8296 | return _hm.strip(data); |
| | 8297 | }, |
| | 8298 | |
| | 8299 | purge: function() { |
| | 8300 | _purge.call(this); |
| | 8301 | } |
| | 8302 | }); |
| | 8303 | |
| | 8304 | if (_ep) { |
| | 8305 | this.meta = { |
| | 8306 | tiff: _ep.TIFF(), |
| | 8307 | exif: _ep.EXIF(), |
| | 8308 | gps: _ep.GPS(), |
| | 8309 | thumb: _getThumb() |
| | 8310 | }; |
| | 8311 | } |
| | 8312 | |
| | 8313 | |
| | 8314 | function _getDimensions(br) { |
| | 8315 | var idx = 0 |
| | 8316 | , marker |
| | 8317 | , length |
| | 8318 | ; |
| | 8319 | |
| | 8320 | if (!br) { |
| | 8321 | br = _br; |
| | 8322 | } |
| | 8323 | |
| | 8324 | // examine all through the end, since some images might have very large APP segments |
| | 8325 | while (idx <= br.length()) { |
| | 8326 | marker = br.SHORT(idx += 2); |
| | 8327 | |
| | 8328 | if (marker >= 0xFFC0 && marker <= 0xFFC3) { // SOFn |
| | 8329 | idx += 5; // marker (2 bytes) + length (2 bytes) + Sample precision (1 byte) |
| | 8330 | return { |
| | 8331 | height: br.SHORT(idx), |
| | 8332 | width: br.SHORT(idx += 2) |
| | 8333 | }; |
| | 8334 | } |
| | 8335 | length = br.SHORT(idx += 2); |
| | 8336 | idx += length - 2; |
| | 8337 | } |
| | 8338 | return null; |
| | 8339 | } |
| | 8340 | |
| | 8341 | |
| | 8342 | function _getThumb() { |
| | 8343 | var data = _ep.thumb() |
| | 8344 | , br |
| | 8345 | , info |
| | 8346 | ; |
| | 8347 | |
| | 8348 | if (data) { |
| | 8349 | br = new BinaryReader(data); |
| | 8350 | info = _getDimensions(br); |
| | 8351 | br.clear(); |
| | 8352 | |
| | 8353 | if (info) { |
| | 8354 | info.data = data; |
| | 8355 | return info; |
| | 8356 | } |
| | 8357 | } |
| | 8358 | return null; |
| | 8359 | } |
| | 8360 | |
| | 8361 | |
| | 8362 | function _purge() { |
| | 8363 | if (!_ep || !_hm || !_br) { |
| | 8364 | return; // ignore any repeating purge requests |
| | 8365 | } |
| | 8366 | _ep.clear(); |
| | 8367 | _hm.purge(); |
| | 8368 | _br.clear(); |
| | 8369 | _info = _hm = _ep = _br = null; |
| | 8370 | } |
| | 8371 | } |
| | 8372 | |
| | 8373 | return JPEG; |
| | 8374 | }); |
| | 8375 | |
| | 8376 | // Included from: src/javascript/runtime/html5/image/PNG.js |
| | 8377 | |
| | 8378 | /** |
| | 8379 | * PNG.js |
| | 8380 | * |
| | 8381 | * Copyright 2013, Moxiecode Systems AB |
| | 8382 | * Released under GPL License. |
| | 8383 | * |
| | 8384 | * License: http://www.plupload.com/license |
| | 8385 | * Contributing: http://www.plupload.com/contributing |
| | 8386 | */ |
| | 8387 | |
| | 8388 | /** |
| | 8389 | @class moxie/runtime/html5/image/PNG |
| | 8390 | @private |
| | 8391 | */ |
| | 8392 | define("moxie/runtime/html5/image/PNG", [ |
| | 8393 | "moxie/core/Exceptions", |
| | 8394 | "moxie/core/utils/Basic", |
| | 8395 | "moxie/runtime/html5/utils/BinaryReader" |
| | 8396 | ], function(x, Basic, BinaryReader) { |
| | 8397 | |
| | 8398 | function PNG(data) { |
| | 8399 | var _br, _hm, _ep, _info; |
| | 8400 | |
| | 8401 | _br = new BinaryReader(data); |
| | 8402 | |
| | 8403 | // check if it's png |
| | 8404 | (function() { |
| | 8405 | var idx = 0, i = 0 |
| | 8406 | , signature = [0x8950, 0x4E47, 0x0D0A, 0x1A0A] |
| | 8407 | ; |
| | 8408 | |
| | 8409 | for (i = 0; i < signature.length; i++, idx += 2) { |
| | 8410 | if (signature[i] != _br.SHORT(idx)) { |
| | 8411 | throw new x.ImageError(x.ImageError.WRONG_FORMAT); |
| | 8412 | } |
| | 8413 | } |
| | 8414 | }()); |
| | 8415 | |
| | 8416 | function _getDimensions() { |
| | 8417 | var chunk, idx; |
| | 8418 | |
| | 8419 | chunk = _getChunkAt.call(this, 8); |
| | 8420 | |
| | 8421 | if (chunk.type == 'IHDR') { |
| | 8422 | idx = chunk.start; |
| | 8423 | return { |
| | 8424 | width: _br.LONG(idx), |
| | 8425 | height: _br.LONG(idx += 4) |
| | 8426 | }; |
| | 8427 | } |
| | 8428 | return null; |
| | 8429 | } |
| | 8430 | |
| | 8431 | function _purge() { |
| | 8432 | if (!_br) { |
| | 8433 | return; // ignore any repeating purge requests |
| | 8434 | } |
| | 8435 | _br.clear(); |
| | 8436 | data = _info = _hm = _ep = _br = null; |
| | 8437 | } |
| | 8438 | |
| | 8439 | _info = _getDimensions.call(this); |
| | 8440 | |
| | 8441 | Basic.extend(this, { |
| | 8442 | type: 'image/png', |
| | 8443 | |
| | 8444 | size: _br.length(), |
| | 8445 | |
| | 8446 | width: _info.width, |
| | 8447 | |
| | 8448 | height: _info.height, |
| | 8449 | |
| | 8450 | purge: function() { |
| | 8451 | _purge.call(this); |
| | 8452 | } |
| | 8453 | }); |
| | 8454 | |
| | 8455 | // for PNG we can safely trigger purge automatically, as we do not keep any data for later |
| | 8456 | _purge.call(this); |
| | 8457 | |
| | 8458 | function _getChunkAt(idx) { |
| | 8459 | var length, type, start, CRC; |
| | 8460 | |
| | 8461 | length = _br.LONG(idx); |
| | 8462 | type = _br.STRING(idx += 4, 4); |
| | 8463 | start = idx += 4; |
| | 8464 | CRC = _br.LONG(idx + length); |
| | 8465 | |
| | 8466 | return { |
| | 8467 | length: length, |
| | 8468 | type: type, |
| | 8469 | start: start, |
| | 8470 | CRC: CRC |
| | 8471 | }; |
| | 8472 | } |
| | 8473 | } |
| | 8474 | |
| | 8475 | return PNG; |
| | 8476 | }); |
| | 8477 | |
| | 8478 | // Included from: src/javascript/runtime/html5/image/ImageInfo.js |
| | 8479 | |
| | 8480 | /** |
| | 8481 | * ImageInfo.js |
| | 8482 | * |
| | 8483 | * Copyright 2013, Moxiecode Systems AB |
| | 8484 | * Released under GPL License. |
| | 8485 | * |
| | 8486 | * License: http://www.plupload.com/license |
| | 8487 | * Contributing: http://www.plupload.com/contributing |
| | 8488 | */ |
| | 8489 | |
| | 8490 | /** |
| | 8491 | @class moxie/runtime/html5/image/ImageInfo |
| | 8492 | @private |
| | 8493 | */ |
| | 8494 | define("moxie/runtime/html5/image/ImageInfo", [ |
| | 8495 | "moxie/core/utils/Basic", |
| | 8496 | "moxie/core/Exceptions", |
| | 8497 | "moxie/runtime/html5/image/JPEG", |
| | 8498 | "moxie/runtime/html5/image/PNG" |
| | 8499 | ], function(Basic, x, JPEG, PNG) { |
| | 8500 | /** |
| | 8501 | Optional image investigation tool for HTML5 runtime. Provides the following features: |
| | 8502 | - ability to distinguish image type (JPEG or PNG) by signature |
| | 8503 | - ability to extract image width/height directly from it's internals, without preloading in memory (fast) |
| | 8504 | - ability to extract APP headers from JPEGs (Exif, GPS, etc) |
| | 8505 | - ability to replace width/height tags in extracted JPEG headers |
| | 8506 | - ability to restore APP headers, that were for example stripped during image manipulation |
| | 8507 | |
| | 8508 | @class ImageInfo |
| | 8509 | @constructor |
| | 8510 | @param {String} data Image source as binary string |
| | 8511 | */ |
| | 8512 | return function(data) { |
| | 8513 | var _cs = [JPEG, PNG], _img; |
| | 8514 | |
| | 8515 | // figure out the format, throw: ImageError.WRONG_FORMAT if not supported |
| | 8516 | _img = (function() { |
| | 8517 | for (var i = 0; i < _cs.length; i++) { |
| | 8518 | try { |
| | 8519 | return new _cs[i](data); |
| | 8520 | } catch (ex) { |
| | 8521 | // console.info(ex); |
| | 8522 | } |
| | 8523 | } |
| | 8524 | throw new x.ImageError(x.ImageError.WRONG_FORMAT); |
| | 8525 | }()); |
| | 8526 | |
| | 8527 | Basic.extend(this, { |
| | 8528 | /** |
| | 8529 | Image Mime Type extracted from it's depths |
| | 8530 | |
| | 8531 | @property type |
| | 8532 | @type {String} |
| | 8533 | @default '' |
| | 8534 | */ |
| | 8535 | type: '', |
| | 8536 | |
| | 8537 | /** |
| | 8538 | Image size in bytes |
| | 8539 | |
| | 8540 | @property size |
| | 8541 | @type {Number} |
| | 8542 | @default 0 |
| | 8543 | */ |
| | 8544 | size: 0, |
| | 8545 | |
| | 8546 | /** |
| | 8547 | Image width extracted from image source |
| | 8548 | |
| | 8549 | @property width |
| | 8550 | @type {Number} |
| | 8551 | @default 0 |
| | 8552 | */ |
| | 8553 | width: 0, |
| | 8554 | |
| | 8555 | /** |
| | 8556 | Image height extracted from image source |
| | 8557 | |
| | 8558 | @property height |
| | 8559 | @type {Number} |
| | 8560 | @default 0 |
| | 8561 | */ |
| | 8562 | height: 0, |
| | 8563 | |
| | 8564 | /** |
| | 8565 | Sets Exif tag. Currently applicable only for width and height tags. Obviously works only with JPEGs. |
| | 8566 | |
| | 8567 | @method setExif |
| | 8568 | @param {String} tag Tag to set |
| | 8569 | @param {Mixed} value Value to assign to the tag |
| | 8570 | */ |
| | 8571 | setExif: function() {}, |
| | 8572 | |
| | 8573 | /** |
| | 8574 | Restores headers to the source. |
| | 8575 | |
| | 8576 | @method writeHeaders |
| | 8577 | @param {String} data Image source as binary string |
| | 8578 | @return {String} Updated binary string |
| | 8579 | */ |
| | 8580 | writeHeaders: function(data) { |
| | 8581 | return data; |
| | 8582 | }, |
| | 8583 | |
| | 8584 | /** |
| | 8585 | Strip all headers from the source. |
| | 8586 | |
| | 8587 | @method stripHeaders |
| | 8588 | @param {String} data Image source as binary string |
| | 8589 | @return {String} Updated binary string |
| | 8590 | */ |
| | 8591 | stripHeaders: function(data) { |
| | 8592 | return data; |
| | 8593 | }, |
| | 8594 | |
| | 8595 | /** |
| | 8596 | Dispose resources. |
| | 8597 | |
| | 8598 | @method purge |
| | 8599 | */ |
| | 8600 | purge: function() { |
| | 8601 | data = null; |
| | 8602 | } |
| | 8603 | }); |
| | 8604 | |
| | 8605 | Basic.extend(this, _img); |
| | 8606 | |
| | 8607 | this.purge = function() { |
| | 8608 | _img.purge(); |
| | 8609 | _img = null; |
| | 8610 | }; |
| | 8611 | }; |
| | 8612 | }); |
| | 8613 | |
| | 8614 | // Included from: src/javascript/runtime/html5/image/MegaPixel.js |
| | 8615 | |
| | 8616 | /** |
| | 8617 | (The MIT License) |
| | 8618 | |
| | 8619 | Copyright (c) 2012 Shinichi Tomita <shinichi.tomita@gmail.com>; |
| | 8620 | |
| | 8621 | Permission is hereby granted, free of charge, to any person obtaining |
| | 8622 | a copy of this software and associated documentation files (the |
| | 8623 | 'Software'), to deal in the Software without restriction, including |
| | 8624 | without limitation the rights to use, copy, modify, merge, publish, |
| | 8625 | distribute, sublicense, and/or sell copies of the Software, and to |
| | 8626 | permit persons to whom the Software is furnished to do so, subject to |
| | 8627 | the following conditions: |
| | 8628 | |
| | 8629 | The above copyright notice and this permission notice shall be |
| | 8630 | included in all copies or substantial portions of the Software. |
| | 8631 | |
| | 8632 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, |
| | 8633 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| | 8634 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| | 8635 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| | 8636 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| | 8637 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| | 8638 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| | 8639 | */ |
| | 8640 | |
| | 8641 | /** |
| | 8642 | * Mega pixel image rendering library for iOS6 Safari |
| | 8643 | * |
| | 8644 | * Fixes iOS6 Safari's image file rendering issue for large size image (over mega-pixel), |
| | 8645 | * which causes unexpected subsampling when drawing it in canvas. |
| | 8646 | * By using this library, you can safely render the image with proper stretching. |
| | 8647 | * |
| | 8648 | * Copyright (c) 2012 Shinichi Tomita <shinichi.tomita@gmail.com> |
| | 8649 | * Released under the MIT license |
| | 8650 | */ |
| | 8651 | |
| | 8652 | /** |
| | 8653 | @class moxie/runtime/html5/image/MegaPixel |
| | 8654 | @private |
| | 8655 | */ |
| | 8656 | define("moxie/runtime/html5/image/MegaPixel", [], function() { |
| | 8657 | |
| | 8658 | /** |
| | 8659 | * Rendering image element (with resizing) into the canvas element |
| | 8660 | */ |
| | 8661 | function renderImageToCanvas(img, canvas, options) { |
| | 8662 | var iw = img.naturalWidth, ih = img.naturalHeight; |
| | 8663 | var width = options.width, height = options.height; |
| | 8664 | var x = options.x || 0, y = options.y || 0; |
| | 8665 | var ctx = canvas.getContext('2d'); |
| | 8666 | if (detectSubsampling(img)) { |
| | 8667 | iw /= 2; |
| | 8668 | ih /= 2; |
| | 8669 | } |
| | 8670 | var d = 1024; // size of tiling canvas |
| | 8671 | var tmpCanvas = document.createElement('canvas'); |
| | 8672 | tmpCanvas.width = tmpCanvas.height = d; |
| | 8673 | var tmpCtx = tmpCanvas.getContext('2d'); |
| | 8674 | var vertSquashRatio = detectVerticalSquash(img, iw, ih); |
| | 8675 | var sy = 0; |
| | 8676 | while (sy < ih) { |
| | 8677 | var sh = sy + d > ih ? ih - sy : d; |
| | 8678 | var sx = 0; |
| | 8679 | while (sx < iw) { |
| | 8680 | var sw = sx + d > iw ? iw - sx : d; |
| | 8681 | tmpCtx.clearRect(0, 0, d, d); |
| | 8682 | tmpCtx.drawImage(img, -sx, -sy); |
| | 8683 | var dx = (sx * width / iw + x) << 0; |
| | 8684 | var dw = Math.ceil(sw * width / iw); |
| | 8685 | var dy = (sy * height / ih / vertSquashRatio + y) << 0; |
| | 8686 | var dh = Math.ceil(sh * height / ih / vertSquashRatio); |
| | 8687 | ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh); |
| | 8688 | sx += d; |
| | 8689 | } |
| | 8690 | sy += d; |
| | 8691 | } |
| | 8692 | tmpCanvas = tmpCtx = null; |
| | 8693 | } |
| | 8694 | |
| | 8695 | /** |
| | 8696 | * Detect subsampling in loaded image. |
| | 8697 | * In iOS, larger images than 2M pixels may be subsampled in rendering. |
| | 8698 | */ |
| | 8699 | function detectSubsampling(img) { |
| | 8700 | var iw = img.naturalWidth, ih = img.naturalHeight; |
| | 8701 | if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image |
| | 8702 | var canvas = document.createElement('canvas'); |
| | 8703 | canvas.width = canvas.height = 1; |
| | 8704 | var ctx = canvas.getContext('2d'); |
| | 8705 | ctx.drawImage(img, -iw + 1, 0); |
| | 8706 | // subsampled image becomes half smaller in rendering size. |
| | 8707 | // check alpha channel value to confirm image is covering edge pixel or not. |
| | 8708 | // if alpha value is 0 image is not covering, hence subsampled. |
| | 8709 | return ctx.getImageData(0, 0, 1, 1).data[3] === 0; |
| | 8710 | } else { |
| | 8711 | return false; |
| | 8712 | } |
| | 8713 | } |
| | 8714 | |
| | 8715 | |
| | 8716 | /** |
| | 8717 | * Detecting vertical squash in loaded image. |
| | 8718 | * Fixes a bug which squash image vertically while drawing into canvas for some images. |
| | 8719 | */ |
| | 8720 | function detectVerticalSquash(img, iw, ih) { |
| | 8721 | var canvas = document.createElement('canvas'); |
| | 8722 | canvas.width = 1; |
| | 8723 | canvas.height = ih; |
| | 8724 | var ctx = canvas.getContext('2d'); |
| | 8725 | ctx.drawImage(img, 0, 0); |
| | 8726 | var data = ctx.getImageData(0, 0, 1, ih).data; |
| | 8727 | // search image edge pixel position in case it is squashed vertically. |
| | 8728 | var sy = 0; |
| | 8729 | var ey = ih; |
| | 8730 | var py = ih; |
| | 8731 | while (py > sy) { |
| | 8732 | var alpha = data[(py - 1) * 4 + 3]; |
| | 8733 | if (alpha === 0) { |
| | 8734 | ey = py; |
| | 8735 | } else { |
| | 8736 | sy = py; |
| | 8737 | } |
| | 8738 | py = (ey + sy) >> 1; |
| | 8739 | } |
| | 8740 | canvas = null; |
| | 8741 | var ratio = (py / ih); |
| | 8742 | return (ratio === 0) ? 1 : ratio; |
| | 8743 | } |
| | 8744 | |
| | 8745 | return { |
| | 8746 | isSubsampled: detectSubsampling, |
| | 8747 | renderTo: renderImageToCanvas |
| | 8748 | }; |
| | 8749 | }); |
| | 8750 | |
| | 8751 | // Included from: src/javascript/runtime/html5/image/Image.js |
| | 8752 | |
| | 8753 | /** |
| | 8754 | * Image.js |
| | 8755 | * |
| | 8756 | * Copyright 2013, Moxiecode Systems AB |
| | 8757 | * Released under GPL License. |
| | 8758 | * |
| | 8759 | * License: http://www.plupload.com/license |
| | 8760 | * Contributing: http://www.plupload.com/contributing |
| | 8761 | */ |
| | 8762 | |
| | 8763 | /** |
| | 8764 | @class moxie/runtime/html5/image/Image |
| | 8765 | @private |
| | 8766 | */ |
| | 8767 | define("moxie/runtime/html5/image/Image", [ |
| | 8768 | "moxie/runtime/html5/Runtime", |
| | 8769 | "moxie/core/utils/Basic", |
| | 8770 | "moxie/core/Exceptions", |
| | 8771 | "moxie/core/utils/Encode", |
| | 8772 | "moxie/file/Blob", |
| | 8773 | "moxie/file/File", |
| | 8774 | "moxie/runtime/html5/image/ImageInfo", |
| | 8775 | "moxie/runtime/html5/image/MegaPixel", |
| | 8776 | "moxie/core/utils/Mime", |
| | 8777 | "moxie/core/utils/Env" |
| | 8778 | ], function(extensions, Basic, x, Encode, Blob, File, ImageInfo, MegaPixel, Mime, Env) { |
| | 8779 | |
| | 8780 | function HTML5Image() { |
| | 8781 | var me = this |
| | 8782 | , _img, _imgInfo, _canvas, _binStr, _blob |
| | 8783 | , _modified = false // is set true whenever image is modified |
| | 8784 | , _preserveHeaders = true |
| | 8785 | ; |
| | 8786 | |
| | 8787 | Basic.extend(this, { |
| | 8788 | loadFromBlob: function(blob) { |
| | 8789 | var comp = this, I = comp.getRuntime() |
| | 8790 | , asBinary = arguments.length > 1 ? arguments[1] : true |
| | 8791 | ; |
| | 8792 | |
| | 8793 | if (!I.can('access_binary')) { |
| | 8794 | throw new x.RuntimeError(x.RuntimeError.NOT_SUPPORTED_ERR); |
| | 8795 | } |
| | 8796 | |
| | 8797 | _blob = blob; |
| | 8798 | |
| | 8799 | if (blob.isDetached()) { |
| | 8800 | _binStr = blob.getSource(); |
| | 8801 | _preload.call(this, _binStr); |
| | 8802 | return; |
| | 8803 | } else { |
| | 8804 | _readAsDataUrl.call(this, blob.getSource(), function(dataUrl) { |
| | 8805 | if (asBinary) { |
| | 8806 | _binStr = _toBinary(dataUrl); |
| | 8807 | } |
| | 8808 | _preload.call(comp, dataUrl); |
| | 8809 | }); |
| | 8810 | } |
| | 8811 | }, |
| | 8812 | |
| | 8813 | loadFromImage: function(img, exact) { |
| | 8814 | this.meta = img.meta; |
| | 8815 | |
| | 8816 | _blob = new File(null, { |
| | 8817 | name: img.name, |
| | 8818 | size: img.size, |
| | 8819 | type: img.type |
| | 8820 | }); |
| | 8821 | |
| | 8822 | _preload.call(this, exact ? (_binStr = img.getAsBinaryString()) : img.getAsDataURL()); |
| | 8823 | }, |
| | 8824 | |
| | 8825 | getInfo: function() { |
| | 8826 | var I = this.getRuntime(), info; |
| | 8827 | |
| | 8828 | if (!_imgInfo && _binStr && I.can('access_image_binary')) { |
| | 8829 | _imgInfo = new ImageInfo(_binStr); |
| | 8830 | } |
| | 8831 | |
| | 8832 | info = { |
| | 8833 | width: _getImg().width || 0, |
| | 8834 | height: _getImg().height || 0, |
| | 8835 | type: _blob.type || Mime.getFileMime(_blob.name), |
| | 8836 | size: _binStr && _binStr.length || _blob.size || 0, |
| | 8837 | name: _blob.name || '', |
| | 8838 | meta: _imgInfo && _imgInfo.meta || this.meta || {} |
| | 8839 | }; |
| | 8840 | |
| | 8841 | // store thumbnail data as blob |
| | 8842 | if (info.meta && info.meta.thumb && !(info.meta.thumb.data instanceof Blob)) { |
| | 8843 | info.meta.thumb.data = new Blob(null, { |
| | 8844 | type: 'image/jpeg', |
| | 8845 | data: info.meta.thumb.data |
| | 8846 | }); |
| | 8847 | } |
| | 8848 | |
| | 8849 | return info; |
| | 8850 | }, |
| | 8851 | |
| | 8852 | downsize: function() { |
| | 8853 | _downsize.apply(this, arguments); |
| | 8854 | }, |
| | 8855 | |
| | 8856 | getAsCanvas: function() { |
| | 8857 | if (_canvas) { |
| | 8858 | _canvas.id = this.uid + '_canvas'; |
| | 8859 | } |
| | 8860 | return _canvas; |
| | 8861 | }, |
| | 8862 | |
| | 8863 | getAsBlob: function(type, quality) { |
| | 8864 | if (type !== this.type) { |
| | 8865 | // if different mime type requested prepare image for conversion |
| | 8866 | _downsize.call(this, this.width, this.height, false); |
| | 8867 | } |
| | 8868 | return new File(null, { |
| | 8869 | name: _blob.name || '', |
| | 8870 | type: type, |
| | 8871 | data: me.getAsBinaryString.call(this, type, quality) |
| | 8872 | }); |
| | 8873 | }, |
| | 8874 | |
| | 8875 | getAsDataURL: function(type) { |
| | 8876 | var quality = arguments[1] || 90; |
| | 8877 | |
| | 8878 | // if image has not been modified, return the source right away |
| | 8879 | if (!_modified) { |
| | 8880 | return _img.src; |
| | 8881 | } |
| | 8882 | |
| | 8883 | if ('image/jpeg' !== type) { |
| | 8884 | return _canvas.toDataURL('image/png'); |
| | 8885 | } else { |
| | 8886 | try { |
| | 8887 | // older Geckos used to result in an exception on quality argument |
| | 8888 | return _canvas.toDataURL('image/jpeg', quality/100); |
| | 8889 | } catch (ex) { |
| | 8890 | return _canvas.toDataURL('image/jpeg'); |
| | 8891 | } |
| | 8892 | } |
| | 8893 | }, |
| | 8894 | |
| | 8895 | getAsBinaryString: function(type, quality) { |
| | 8896 | // if image has not been modified, return the source right away |
| | 8897 | if (!_modified) { |
| | 8898 | // if image was not loaded from binary string |
| | 8899 | if (!_binStr) { |
| | 8900 | _binStr = _toBinary(me.getAsDataURL(type, quality)); |
| | 8901 | } |
| | 8902 | return _binStr; |
| | 8903 | } |
| | 8904 | |
| | 8905 | if ('image/jpeg' !== type) { |
| | 8906 | _binStr = _toBinary(me.getAsDataURL(type, quality)); |
| | 8907 | } else { |
| | 8908 | var dataUrl; |
| | 8909 | |
| | 8910 | // if jpeg |
| | 8911 | if (!quality) { |
| | 8912 | quality = 90; |
| | 8913 | } |
| | 8914 | |
| | 8915 | try { |
| | 8916 | // older Geckos used to result in an exception on quality argument |
| | 8917 | dataUrl = _canvas.toDataURL('image/jpeg', quality/100); |
| | 8918 | } catch (ex) { |
| | 8919 | dataUrl = _canvas.toDataURL('image/jpeg'); |
| | 8920 | } |
| | 8921 | |
| | 8922 | _binStr = _toBinary(dataUrl); |
| | 8923 | |
| | 8924 | if (_imgInfo) { |
| | 8925 | _binStr = _imgInfo.stripHeaders(_binStr); |
| | 8926 | |
| | 8927 | if (_preserveHeaders) { |
| | 8928 | // update dimensions info in exif |
| | 8929 | if (_imgInfo.meta && _imgInfo.meta.exif) { |
| | 8930 | _imgInfo.setExif({ |
| | 8931 | PixelXDimension: this.width, |
| | 8932 | PixelYDimension: this.height |
| | 8933 | }); |
| | 8934 | } |
| | 8935 | |
| | 8936 | // re-inject the headers |
| | 8937 | _binStr = _imgInfo.writeHeaders(_binStr); |
| | 8938 | } |
| | 8939 | |
| | 8940 | // will be re-created from fresh on next getInfo call |
| | 8941 | _imgInfo.purge(); |
| | 8942 | _imgInfo = null; |
| | 8943 | } |
| | 8944 | } |
| | 8945 | |
| | 8946 | _modified = false; |
| | 8947 | |
| | 8948 | return _binStr; |
| | 8949 | }, |
| | 8950 | |
| | 8951 | destroy: function() { |
| | 8952 | me = null; |
| | 8953 | _purge.call(this); |
| | 8954 | this.getRuntime().getShim().removeInstance(this.uid); |
| | 8955 | } |
| | 8956 | }); |
| | 8957 | |
| | 8958 | |
| | 8959 | function _getImg() { |
| | 8960 | if (!_canvas && !_img) { |
| | 8961 | throw new x.ImageError(x.DOMException.INVALID_STATE_ERR); |
| | 8962 | } |
| | 8963 | return _canvas || _img; |
| | 8964 | } |
| | 8965 | |
| | 8966 | |
| | 8967 | function _toBinary(str) { |
| | 8968 | return Encode.atob(str.substring(str.indexOf('base64,') + 7)); |
| | 8969 | } |
| | 8970 | |
| | 8971 | |
| | 8972 | function _toDataUrl(str, type) { |
| | 8973 | return 'data:' + (type || '') + ';base64,' + Encode.btoa(str); |
| | 8974 | } |
| | 8975 | |
| | 8976 | |
| | 8977 | function _preload(str) { |
| | 8978 | var comp = this; |
| | 8979 | |
| | 8980 | _img = new Image(); |
| | 8981 | _img.onerror = function() { |
| | 8982 | _purge.call(this); |
| | 8983 | comp.trigger('error', x.ImageError.WRONG_FORMAT); |
| | 8984 | }; |
| | 8985 | _img.onload = function() { |
| | 8986 | comp.trigger('load'); |
| | 8987 | }; |
| | 8988 | |
| | 8989 | _img.src = str.substr(0, 5) == 'data:' ? str : _toDataUrl(str, _blob.type); |
| | 8990 | } |
| | 8991 | |
| | 8992 | |
| | 8993 | function _readAsDataUrl(file, callback) { |
| | 8994 | var comp = this, fr; |
| | 8995 | |
| | 8996 | // use FileReader if it's available |
| | 8997 | if (window.FileReader) { |
| | 8998 | fr = new FileReader(); |
| | 8999 | fr.onload = function() { |
| | 9000 | callback(this.result); |
| | 9001 | }; |
| | 9002 | fr.onerror = function() { |
| | 9003 | comp.trigger('error', x.ImageError.WRONG_FORMAT); |
| | 9004 | }; |
| | 9005 | fr.readAsDataURL(file); |
| | 9006 | } else { |
| | 9007 | return callback(file.getAsDataURL()); |
| | 9008 | } |
| | 9009 | } |
| | 9010 | |
| | 9011 | function _downsize(width, height, crop, preserveHeaders) { |
| | 9012 | var self = this |
| | 9013 | , scale |
| | 9014 | , mathFn |
| | 9015 | , x = 0 |
| | 9016 | , y = 0 |
| | 9017 | , img |
| | 9018 | , destWidth |
| | 9019 | , destHeight |
| | 9020 | , orientation |
| | 9021 | ; |
| | 9022 | |
| | 9023 | _preserveHeaders = preserveHeaders; // we will need to check this on export (see getAsBinaryString()) |
| | 9024 | |
| | 9025 | // take into account orientation tag |
| | 9026 | orientation = (this.meta && this.meta.tiff && this.meta.tiff.Orientation) || 1; |
| | 9027 | |
| | 9028 | if (Basic.inArray(orientation, [5,6,7,8]) !== -1) { // values that require 90 degree rotation |
| | 9029 | // swap dimensions |
| | 9030 | var tmp = width; |
| | 9031 | width = height; |
| | 9032 | height = tmp; |
| | 9033 | } |
| | 9034 | |
| | 9035 | img = _getImg(); |
| | 9036 | |
| | 9037 | // unify dimensions |
| | 9038 | if (!crop) { |
| | 9039 | scale = Math.min(width/img.width, height/img.height); |
| | 9040 | } else { |
| | 9041 | // one of the dimensions may exceed the actual image dimensions - we need to take the smallest value |
| | 9042 | width = Math.min(width, img.width); |
| | 9043 | height = Math.min(height, img.height); |
| | 9044 | |
| | 9045 | scale = Math.max(width/img.width, height/img.height); |
| | 9046 | } |
| | 9047 | |
| | 9048 | // we only downsize here |
| | 9049 | if (scale > 1 && !crop && preserveHeaders) { |
| | 9050 | this.trigger('Resize'); |
| | 9051 | return; |
| | 9052 | } |
| | 9053 | |
| | 9054 | // prepare canvas if necessary |
| | 9055 | if (!_canvas) { |
| | 9056 | _canvas = document.createElement("canvas"); |
| | 9057 | } |
| | 9058 | |
| | 9059 | // calculate dimensions of proportionally resized image |
| | 9060 | destWidth = Math.round(img.width * scale); |
| | 9061 | destHeight = Math.round(img.height * scale); |
| | 9062 | |
| | 9063 | // scale image and canvas |
| | 9064 | if (crop) { |
| | 9065 | _canvas.width = width; |
| | 9066 | _canvas.height = height; |
| | 9067 | |
| | 9068 | // if dimensions of the resulting image still larger than canvas, center it |
| | 9069 | if (destWidth > width) { |
| | 9070 | x = Math.round((destWidth - width) / 2); |
| | 9071 | } |
| | 9072 | |
| | 9073 | if (destHeight > height) { |
| | 9074 | y = Math.round((destHeight - height) / 2); |
| | 9075 | } |
| | 9076 | } else { |
| | 9077 | _canvas.width = destWidth; |
| | 9078 | _canvas.height = destHeight; |
| | 9079 | } |
| | 9080 | |
| | 9081 | // rotate if required, according to orientation tag |
| | 9082 | if (!_preserveHeaders) { |
| | 9083 | _rotateToOrientaion(_canvas.width, _canvas.height, orientation); |
| | 9084 | } |
| | 9085 | |
| | 9086 | _drawToCanvas.call(this, img, _canvas, -x, -y, destWidth, destHeight); |
| | 9087 | |
| | 9088 | this.width = _canvas.width; |
| | 9089 | this.height = _canvas.height; |
| | 9090 | |
| | 9091 | _modified = true; |
| | 9092 | self.trigger('Resize'); |
| | 9093 | } |
| | 9094 | |
| | 9095 | |
| | 9096 | function _drawToCanvas(img, canvas, x, y, w, h) { |
| | 9097 | if (Env.OS === 'iOS') { |
| | 9098 | // avoid squish bug in iOS6 |
| | 9099 | MegaPixel.renderTo(img, canvas, { width: w, height: h, x: x, y: y }); |
| | 9100 | } else { |
| | 9101 | var ctx = canvas.getContext('2d'); |
| | 9102 | ctx.drawImage(img, x, y, w, h); |
| | 9103 | } |
| | 9104 | } |
| | 9105 | |
| | 9106 | |
| | 9107 | /** |
| | 9108 | * Transform canvas coordination according to specified frame size and orientation |
| | 9109 | * Orientation value is from EXIF tag |
| | 9110 | * @author Shinichi Tomita <shinichi.tomita@gmail.com> |
| | 9111 | */ |
| | 9112 | function _rotateToOrientaion(width, height, orientation) { |
| | 9113 | switch (orientation) { |
| | 9114 | case 5: |
| | 9115 | case 6: |
| | 9116 | case 7: |
| | 9117 | case 8: |
| | 9118 | _canvas.width = height; |
| | 9119 | _canvas.height = width; |
| | 9120 | break; |
| | 9121 | default: |
| | 9122 | _canvas.width = width; |
| | 9123 | _canvas.height = height; |
| | 9124 | } |
| | 9125 | |
| | 9126 | /** |
| | 9127 | 1 = The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side. |
| | 9128 | 2 = The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side. |
| | 9129 | 3 = The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side. |
| | 9130 | 4 = The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side. |
| | 9131 | 5 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual top. |
| | 9132 | 6 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual top. |
| | 9133 | 7 = The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom. |
| | 9134 | 8 = The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom. |
| | 9135 | */ |
| | 9136 | |
| | 9137 | var ctx = _canvas.getContext('2d'); |
| | 9138 | switch (orientation) { |
| | 9139 | case 2: |
| | 9140 | // horizontal flip |
| | 9141 | ctx.translate(width, 0); |
| | 9142 | ctx.scale(-1, 1); |
| | 9143 | break; |
| | 9144 | case 3: |
| | 9145 | // 180 rotate left |
| | 9146 | ctx.translate(width, height); |
| | 9147 | ctx.rotate(Math.PI); |
| | 9148 | break; |
| | 9149 | case 4: |
| | 9150 | // vertical flip |
| | 9151 | ctx.translate(0, height); |
| | 9152 | ctx.scale(1, -1); |
| | 9153 | break; |
| | 9154 | case 5: |
| | 9155 | // vertical flip + 90 rotate right |
| | 9156 | ctx.rotate(0.5 * Math.PI); |
| | 9157 | ctx.scale(1, -1); |
| | 9158 | break; |
| | 9159 | case 6: |
| | 9160 | // 90 rotate right |
| | 9161 | ctx.rotate(0.5 * Math.PI); |
| | 9162 | ctx.translate(0, -height); |
| | 9163 | break; |
| | 9164 | case 7: |
| | 9165 | // horizontal flip + 90 rotate right |
| | 9166 | ctx.rotate(0.5 * Math.PI); |
| | 9167 | ctx.translate(width, -height); |
| | 9168 | ctx.scale(-1, 1); |
| | 9169 | break; |
| | 9170 | case 8: |
| | 9171 | // 90 rotate left |
| | 9172 | ctx.rotate(-0.5 * Math.PI); |
| | 9173 | ctx.translate(-width, 0); |
| | 9174 | break; |
| | 9175 | } |
| | 9176 | } |
| | 9177 | |
| | 9178 | |
| | 9179 | function _purge() { |
| | 9180 | if (_imgInfo) { |
| | 9181 | _imgInfo.purge(); |
| | 9182 | _imgInfo = null; |
| | 9183 | } |
| | 9184 | _binStr = _img = _canvas = _blob = null; |
| | 9185 | _modified = false; |
| | 9186 | } |
| | 9187 | } |
| | 9188 | |
| | 9189 | return (extensions.Image = HTML5Image); |
| | 9190 | }); |
| | 9191 | |
| | 9192 | /** |
| | 9193 | * Stub for moxie/runtime/flash/Runtime |
| | 9194 | * @private |
| | 9195 | */ |
| | 9196 | define("moxie/runtime/flash/Runtime", [ |
| | 9197 | ], function() { |
| | 9198 | return {}; |
| | 9199 | }); |
| | 9200 | |
| | 9201 | /** |
| | 9202 | * Stub for moxie/runtime/silverlight/Runtime |
| | 9203 | * @private |
| | 9204 | */ |
| | 9205 | define("moxie/runtime/silverlight/Runtime", [ |
| | 9206 | ], function() { |
| | 9207 | return {}; |
| | 9208 | }); |
| | 9209 | |
| | 9210 | // Included from: src/javascript/runtime/html4/Runtime.js |
| | 9211 | |
| | 9212 | /** |
| | 9213 | * Runtime.js |
| | 9214 | * |
| | 9215 | * Copyright 2013, Moxiecode Systems AB |
| | 9216 | * Released under GPL License. |
| | 9217 | * |
| | 9218 | * License: http://www.plupload.com/license |
| | 9219 | * Contributing: http://www.plupload.com/contributing |
| | 9220 | */ |
| | 9221 | |
| | 9222 | /*global File:true */ |
| | 9223 | |
| | 9224 | /** |
| | 9225 | Defines constructor for HTML4 runtime. |
| | 9226 | |
| | 9227 | @class moxie/runtime/html4/Runtime |
| | 9228 | @private |
| | 9229 | */ |
| | 9230 | define("moxie/runtime/html4/Runtime", [ |
| | 9231 | "moxie/core/utils/Basic", |
| | 9232 | "moxie/core/Exceptions", |
| | 9233 | "moxie/runtime/Runtime", |
| | 9234 | "moxie/core/utils/Env" |
| | 9235 | ], function(Basic, x, Runtime, Env) { |
| | 9236 | |
| | 9237 | var type = 'html4', extensions = {}; |
| | 9238 | |
| | 9239 | function Html4Runtime(options) { |
| | 9240 | var I = this |
| | 9241 | , Test = Runtime.capTest |
| | 9242 | , True = Runtime.capTrue |
| | 9243 | ; |
| | 9244 | |
| | 9245 | Runtime.call(this, options, type, { |
| | 9246 | access_binary: Test(window.FileReader || window.File && File.getAsDataURL), |
| | 9247 | access_image_binary: false, |
| | 9248 | display_media: Test(extensions.Image && (Env.can('create_canvas') || Env.can('use_data_uri_over32kb'))), |
| | 9249 | do_cors: false, |
| | 9250 | drag_and_drop: false, |
| | 9251 | filter_by_extension: Test(function() { // if you know how to feature-detect this, please suggest |
| | 9252 | return (Env.browser === 'Chrome' && Env.verComp(Env.version, 28, '>=')) || |
| | 9253 | (Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) || |
| | 9254 | (Env.browser === 'Safari' && Env.verComp(Env.version, 7, '>=')); |
| | 9255 | }()), |
| | 9256 | resize_image: function() { |
| | 9257 | return extensions.Image && I.can('access_binary') && Env.can('create_canvas'); |
| | 9258 | }, |
| | 9259 | report_upload_progress: false, |
| | 9260 | return_response_headers: false, |
| | 9261 | return_response_type: function(responseType) { |
| | 9262 | if (responseType === 'json' && !!window.JSON) { |
| | 9263 | return true; |
| | 9264 | } |
| | 9265 | return !!~Basic.inArray(responseType, ['text', 'document', '']); |
| | 9266 | }, |
| | 9267 | return_status_code: function(code) { |
| | 9268 | return !Basic.arrayDiff(code, [200, 404]); |
| | 9269 | }, |
| | 9270 | select_file: function() { |
| | 9271 | return Env.can('use_fileinput'); |
| | 9272 | }, |
| | 9273 | select_multiple: false, |
| | 9274 | send_binary_string: false, |
| | 9275 | send_custom_headers: false, |
| | 9276 | send_multipart: true, |
| | 9277 | slice_blob: false, |
| | 9278 | stream_upload: function() { |
| | 9279 | return I.can('select_file'); |
| | 9280 | }, |
| | 9281 | summon_file_dialog: function() { // yeah... some dirty sniffing here... |
| | 9282 | return I.can('select_file') && ( |
| | 9283 | (Env.browser === 'Firefox' && Env.verComp(Env.version, 4, '>=')) || |
| | 9284 | (Env.browser === 'Opera' && Env.verComp(Env.version, 12, '>=')) || |
| | 9285 | (Env.browser === 'IE' && Env.verComp(Env.version, 10, '>=')) || |
| | 9286 | !!~Basic.inArray(Env.browser, ['Chrome', 'Safari']) |
| | 9287 | ); |
| | 9288 | }, |
| | 9289 | upload_filesize: True, |
| | 9290 | use_http_method: function(methods) { |
| | 9291 | return !Basic.arrayDiff(methods, ['GET', 'POST']); |
| | 9292 | } |
| | 9293 | }); |
| | 9294 | |
| | 9295 | |
| | 9296 | Basic.extend(this, { |
| | 9297 | init : function() { |
| | 9298 | this.trigger("Init"); |
| | 9299 | }, |
| | 9300 | |
| | 9301 | destroy: (function(destroy) { // extend default destroy method |
| | 9302 | return function() { |
| | 9303 | destroy.call(I); |
| | 9304 | destroy = I = null; |
| | 9305 | }; |
| | 9306 | }(this.destroy)) |
| | 9307 | }); |
| | 9308 | |
| | 9309 | Basic.extend(this.getShim(), extensions); |
| | 9310 | } |
| | 9311 | |
| | 9312 | Runtime.addConstructor(type, Html4Runtime); |
| | 9313 | |
| | 9314 | return extensions; |
| | 9315 | }); |
| | 9316 | |
| | 9317 | // Included from: src/javascript/runtime/html4/file/FileInput.js |
| | 9318 | |
| | 9319 | /** |
| | 9320 | * FileInput.js |
| | 9321 | * |
| | 9322 | * Copyright 2013, Moxiecode Systems AB |
| | 9323 | * Released under GPL License. |
| | 9324 | * |
| | 9325 | * License: http://www.plupload.com/license |
| | 9326 | * Contributing: http://www.plupload.com/contributing |
| | 9327 | */ |
| | 9328 | |
| | 9329 | /** |
| | 9330 | @class moxie/runtime/html4/file/FileInput |
| | 9331 | @private |
| | 9332 | */ |
| | 9333 | define("moxie/runtime/html4/file/FileInput", [ |
| | 9334 | "moxie/runtime/html4/Runtime", |
| | 9335 | "moxie/file/File", |
| | 9336 | "moxie/core/utils/Basic", |
| | 9337 | "moxie/core/utils/Dom", |
| | 9338 | "moxie/core/utils/Events", |
| | 9339 | "moxie/core/utils/Mime", |
| | 9340 | "moxie/core/utils/Env" |
| | 9341 | ], function(extensions, File, Basic, Dom, Events, Mime, Env) { |
| | 9342 | |
| | 9343 | function FileInput() { |
| | 9344 | var _uid, _mimes = [], _options; |
| | 9345 | |
| | 9346 | function addInput() { |
| | 9347 | var comp = this, I = comp.getRuntime(), shimContainer, browseButton, currForm, form, input, uid; |
| | 9348 | |
| | 9349 | uid = Basic.guid('uid_'); |
| | 9350 | |
| | 9351 | shimContainer = I.getShimContainer(); // we get new ref everytime to avoid memory leaks in IE |
| | 9352 | |
| | 9353 | if (_uid) { // move previous form out of the view |
| | 9354 | currForm = Dom.get(_uid + '_form'); |
| | 9355 | if (currForm) { |
| | 9356 | Basic.extend(currForm.style, { top: '100%' }); |
| | 9357 | } |
| | 9358 | } |
| | 9359 | |
| | 9360 | // build form in DOM, since innerHTML version not able to submit file for some reason |
| | 9361 | form = document.createElement('form'); |
| | 9362 | form.setAttribute('id', uid + '_form'); |
| | 9363 | form.setAttribute('method', 'post'); |
| | 9364 | form.setAttribute('enctype', 'multipart/form-data'); |
| | 9365 | form.setAttribute('encoding', 'multipart/form-data'); |
| | 9366 | |
| | 9367 | Basic.extend(form.style, { |
| | 9368 | overflow: 'hidden', |
| | 9369 | position: 'absolute', |
| | 9370 | top: 0, |
| | 9371 | left: 0, |
| | 9372 | width: '100%', |
| | 9373 | height: '100%' |
| | 9374 | }); |
| | 9375 | |
| | 9376 | input = document.createElement('input'); |
| | 9377 | input.setAttribute('id', uid); |
| | 9378 | input.setAttribute('type', 'file'); |
| | 9379 | input.setAttribute('name', _options.name || 'Filedata'); |
| | 9380 | input.setAttribute('accept', _mimes.join(',')); |
| | 9381 | |
| | 9382 | Basic.extend(input.style, { |
| | 9383 | fontSize: '999px', |
| | 9384 | opacity: 0 |
| | 9385 | }); |
| | 9386 | |
| | 9387 | form.appendChild(input); |
| | 9388 | shimContainer.appendChild(form); |
| | 9389 | |
| | 9390 | // prepare file input to be placed underneath the browse_button element |
| | 9391 | Basic.extend(input.style, { |
| | 9392 | position: 'absolute', |
| | 9393 | top: 0, |
| | 9394 | left: 0, |
| | 9395 | width: '100%', |
| | 9396 | height: '100%' |
| | 9397 | }); |
| | 9398 | |
| | 9399 | if (Env.browser === 'IE' && Env.verComp(Env.version, 10, '<')) { |
| | 9400 | Basic.extend(input.style, { |
| | 9401 | filter : "progid:DXImageTransform.Microsoft.Alpha(opacity=0)" |
| | 9402 | }); |
| | 9403 | } |
| | 9404 | |
| | 9405 | input.onchange = function() { // there should be only one handler for this |
| | 9406 | var file; |
| | 9407 | |
| | 9408 | if (!this.value) { |
| | 9409 | return; |
| | 9410 | } |
| | 9411 | |
| | 9412 | if (this.files) { // check if browser is fresh enough |
| | 9413 | file = this.files[0]; |
| | 9414 | |
| | 9415 | // ignore empty files (IE10 for example hangs if you try to send them via XHR) |
| | 9416 | if (file.size === 0) { |
| | 9417 | form.parentNode.removeChild(form); |
| | 9418 | return; |
| | 9419 | } |
| | 9420 | } else { |
| | 9421 | file = { |
| | 9422 | name: this.value |
| | 9423 | }; |
| | 9424 | } |
| | 9425 | |
| | 9426 | file = new File(I.uid, file); |
| | 9427 | |
| | 9428 | // clear event handler |
| | 9429 | this.onchange = function() {}; |
| | 9430 | addInput.call(comp); |
| | 9431 | |
| | 9432 | comp.files = [file]; |
| | 9433 | |
| | 9434 | // substitute all ids with file uids (consider file.uid read-only - we cannot do it the other way around) |
| | 9435 | input.setAttribute('id', file.uid); |
| | 9436 | form.setAttribute('id', file.uid + '_form'); |
| | 9437 | |
| | 9438 | comp.trigger('change'); |
| | 9439 | |
| | 9440 | input = form = null; |
| | 9441 | }; |
| | 9442 | |
| | 9443 | |
| | 9444 | // route click event to the input |
| | 9445 | if (I.can('summon_file_dialog')) { |
| | 9446 | browseButton = Dom.get(_options.browse_button); |
| | 9447 | Events.removeEvent(browseButton, 'click', comp.uid); |
| | 9448 | Events.addEvent(browseButton, 'click', function(e) { |
| | 9449 | if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file] |
| | 9450 | input.click(); |
| | 9451 | } |
| | 9452 | e.preventDefault(); |
| | 9453 | }, comp.uid); |
| | 9454 | } |
| | 9455 | |
| | 9456 | _uid = uid; |
| | 9457 | |
| | 9458 | shimContainer = currForm = browseButton = null; |
| | 9459 | } |
| | 9460 | |
| | 9461 | Basic.extend(this, { |
| | 9462 | init: function(options) { |
| | 9463 | var comp = this, I = comp.getRuntime(), shimContainer; |
| | 9464 | |
| | 9465 | // figure out accept string |
| | 9466 | _options = options; |
| | 9467 | _mimes = options.accept.mimes || Mime.extList2mimes(options.accept, I.can('filter_by_extension')); |
| | 9468 | |
| | 9469 | shimContainer = I.getShimContainer(); |
| | 9470 | |
| | 9471 | (function() { |
| | 9472 | var browseButton, zIndex, top; |
| | 9473 | |
| | 9474 | browseButton = Dom.get(options.browse_button); |
| | 9475 | |
| | 9476 | // Route click event to the input[type=file] element for browsers that support such behavior |
| | 9477 | if (I.can('summon_file_dialog')) { |
| | 9478 | if (Dom.getStyle(browseButton, 'position') === 'static') { |
| | 9479 | browseButton.style.position = 'relative'; |
| | 9480 | } |
| | 9481 | |
| | 9482 | zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 1; |
| | 9483 | |
| | 9484 | browseButton.style.zIndex = zIndex; |
| | 9485 | shimContainer.style.zIndex = zIndex - 1; |
| | 9486 | } |
| | 9487 | |
| | 9488 | /* Since we have to place input[type=file] on top of the browse_button for some browsers, |
| | 9489 | browse_button loses interactivity, so we restore it here */ |
| | 9490 | top = I.can('summon_file_dialog') ? browseButton : shimContainer; |
| | 9491 | |
| | 9492 | Events.addEvent(top, 'mouseover', function() { |
| | 9493 | comp.trigger('mouseenter'); |
| | 9494 | }, comp.uid); |
| | 9495 | |
| | 9496 | Events.addEvent(top, 'mouseout', function() { |
| | 9497 | comp.trigger('mouseleave'); |
| | 9498 | }, comp.uid); |
| | 9499 | |
| | 9500 | Events.addEvent(top, 'mousedown', function() { |
| | 9501 | comp.trigger('mousedown'); |
| | 9502 | }, comp.uid); |
| | 9503 | |
| | 9504 | Events.addEvent(Dom.get(options.container), 'mouseup', function() { |
| | 9505 | comp.trigger('mouseup'); |
| | 9506 | }, comp.uid); |
| | 9507 | |
| | 9508 | browseButton = null; |
| | 9509 | }()); |
| | 9510 | |
| | 9511 | addInput.call(this); |
| | 9512 | |
| | 9513 | shimContainer = null; |
| | 9514 | |
| | 9515 | // trigger ready event asynchronously |
| | 9516 | comp.trigger({ |
| | 9517 | type: 'ready', |
| | 9518 | async: true |
| | 9519 | }); |
| | 9520 | }, |
| | 9521 | |
| | 9522 | |
| | 9523 | disable: function(state) { |
| | 9524 | var input; |
| | 9525 | |
| | 9526 | if ((input = Dom.get(_uid))) { |
| | 9527 | input.disabled = !!state; |
| | 9528 | } |
| | 9529 | }, |
| | 9530 | |
| | 9531 | destroy: function() { |
| | 9532 | var I = this.getRuntime() |
| | 9533 | , shim = I.getShim() |
| | 9534 | , shimContainer = I.getShimContainer() |
| | 9535 | ; |
| | 9536 | |
| | 9537 | Events.removeAllEvents(shimContainer, this.uid); |
| | 9538 | Events.removeAllEvents(_options && Dom.get(_options.container), this.uid); |
| | 9539 | Events.removeAllEvents(_options && Dom.get(_options.browse_button), this.uid); |
| | 9540 | |
| | 9541 | if (shimContainer) { |
| | 9542 | shimContainer.innerHTML = ''; |
| | 9543 | } |
| | 9544 | |
| | 9545 | shim.removeInstance(this.uid); |
| | 9546 | |
| | 9547 | _uid = _mimes = _options = shimContainer = shim = null; |
| | 9548 | } |
| | 9549 | }); |
| | 9550 | } |
| | 9551 | |
| | 9552 | return (extensions.FileInput = FileInput); |
| | 9553 | }); |
| | 9554 | |
| | 9555 | // Included from: src/javascript/runtime/html4/file/FileReader.js |
| | 9556 | |
| | 9557 | /** |
| | 9558 | * FileReader.js |
| | 9559 | * |
| | 9560 | * Copyright 2013, Moxiecode Systems AB |
| | 9561 | * Released under GPL License. |
| | 9562 | * |
| | 9563 | * License: http://www.plupload.com/license |
| | 9564 | * Contributing: http://www.plupload.com/contributing |
| | 9565 | */ |
| | 9566 | |
| | 9567 | /** |
| | 9568 | @class moxie/runtime/html4/file/FileReader |
| | 9569 | @private |
| | 9570 | */ |
| | 9571 | define("moxie/runtime/html4/file/FileReader", [ |
| | 9572 | "moxie/runtime/html4/Runtime", |
| | 9573 | "moxie/runtime/html5/file/FileReader" |
| | 9574 | ], function(extensions, FileReader) { |
| | 9575 | return (extensions.FileReader = FileReader); |
| | 9576 | }); |
| | 9577 | |
| | 9578 | // Included from: src/javascript/runtime/html4/xhr/XMLHttpRequest.js |
| | 9579 | |
| | 9580 | /** |
| | 9581 | * XMLHttpRequest.js |
| | 9582 | * |
| | 9583 | * Copyright 2013, Moxiecode Systems AB |
| | 9584 | * Released under GPL License. |
| | 9585 | * |
| | 9586 | * License: http://www.plupload.com/license |
| | 9587 | * Contributing: http://www.plupload.com/contributing |
| | 9588 | */ |
| | 9589 | |
| | 9590 | /** |
| | 9591 | @class moxie/runtime/html4/xhr/XMLHttpRequest |
| | 9592 | @private |
| | 9593 | */ |
| | 9594 | define("moxie/runtime/html4/xhr/XMLHttpRequest", [ |
| | 9595 | "moxie/runtime/html4/Runtime", |
| | 9596 | "moxie/core/utils/Basic", |
| | 9597 | "moxie/core/utils/Dom", |
| | 9598 | "moxie/core/utils/Url", |
| | 9599 | "moxie/core/Exceptions", |
| | 9600 | "moxie/core/utils/Events", |
| | 9601 | "moxie/file/Blob", |
| | 9602 | "moxie/xhr/FormData" |
| | 9603 | ], function(extensions, Basic, Dom, Url, x, Events, Blob, FormData) { |
| | 9604 | |
| | 9605 | function XMLHttpRequest() { |
| | 9606 | var _status, _response, _iframe; |
| | 9607 | |
| | 9608 | function cleanup(cb) { |
| | 9609 | var target = this, uid, form, inputs, i, hasFile = false; |
| | 9610 | |
| | 9611 | if (!_iframe) { |
| | 9612 | return; |
| | 9613 | } |
| | 9614 | |
| | 9615 | uid = _iframe.id.replace(/_iframe$/, ''); |
| | 9616 | |
| | 9617 | form = Dom.get(uid + '_form'); |
| | 9618 | if (form) { |
| | 9619 | inputs = form.getElementsByTagName('input'); |
| | 9620 | i = inputs.length; |
| | 9621 | |
| | 9622 | while (i--) { |
| | 9623 | switch (inputs[i].getAttribute('type')) { |
| | 9624 | case 'hidden': |
| | 9625 | inputs[i].parentNode.removeChild(inputs[i]); |
| | 9626 | break; |
| | 9627 | case 'file': |
| | 9628 | hasFile = true; // flag the case for later |
| | 9629 | break; |
| | 9630 | } |
| | 9631 | } |
| | 9632 | inputs = []; |
| | 9633 | |
| | 9634 | if (!hasFile) { // we need to keep the form for sake of possible retries |
| | 9635 | form.parentNode.removeChild(form); |
| | 9636 | } |
| | 9637 | form = null; |
| | 9638 | } |
| | 9639 | |
| | 9640 | // without timeout, request is marked as canceled (in console) |
| | 9641 | setTimeout(function() { |
| | 9642 | Events.removeEvent(_iframe, 'load', target.uid); |
| | 9643 | if (_iframe.parentNode) { // #382 |
| | 9644 | _iframe.parentNode.removeChild(_iframe); |
| | 9645 | } |
| | 9646 | |
| | 9647 | // check if shim container has any other children, if - not, remove it as well |
| | 9648 | var shimContainer = target.getRuntime().getShimContainer(); |
| | 9649 | if (!shimContainer.children.length) { |
| | 9650 | shimContainer.parentNode.removeChild(shimContainer); |
| | 9651 | } |
| | 9652 | |
| | 9653 | shimContainer = _iframe = null; |
| | 9654 | cb(); |
| | 9655 | }, 1); |
| | 9656 | } |
| | 9657 | |
| | 9658 | Basic.extend(this, { |
| | 9659 | send: function(meta, data) { |
| | 9660 | var target = this, I = target.getRuntime(), uid, form, input, blob; |
| | 9661 | |
| | 9662 | _status = _response = null; |
| | 9663 | |
| | 9664 | function createIframe() { |
| | 9665 | var container = I.getShimContainer() || document.body |
| | 9666 | , temp = document.createElement('div') |
| | 9667 | ; |
| | 9668 | |
| | 9669 | // IE 6 won't be able to set the name using setAttribute or iframe.name |
| | 9670 | temp.innerHTML = '<iframe id="' + uid + '_iframe" name="' + uid + '_iframe" src="javascript:""" style="display:none"></iframe>'; |
| | 9671 | _iframe = temp.firstChild; |
| | 9672 | container.appendChild(_iframe); |
| | 9673 | |
| | 9674 | /* _iframe.onreadystatechange = function() { |
| | 9675 | console.info(_iframe.readyState); |
| | 9676 | };*/ |
| | 9677 | |
| | 9678 | Events.addEvent(_iframe, 'load', function() { // _iframe.onload doesn't work in IE lte 8 |
| | 9679 | var el; |
| | 9680 | |
| | 9681 | try { |
| | 9682 | el = _iframe.contentWindow.document || _iframe.contentDocument || window.frames[_iframe.id].document; |
| | 9683 | |
| | 9684 | // try to detect some standard error pages |
| | 9685 | if (/^4(0[0-9]|1[0-7]|2[2346])\s/.test(el.title)) { // test if title starts with 4xx HTTP error |
| | 9686 | _status = el.title.replace(/^(\d+).*$/, '$1'); |
| | 9687 | } else { |
| | 9688 | _status = 200; |
| | 9689 | // get result |
| | 9690 | _response = Basic.trim(el.body.innerHTML); |
| | 9691 | |
| | 9692 | // we need to fire these at least once |
| | 9693 | target.trigger({ |
| | 9694 | type: 'progress', |
| | 9695 | loaded: _response.length, |
| | 9696 | total: _response.length |
| | 9697 | }); |
| | 9698 | |
| | 9699 | if (blob) { // if we were uploading a file |
| | 9700 | target.trigger({ |
| | 9701 | type: 'uploadprogress', |
| | 9702 | loaded: blob.size || 1025, |
| | 9703 | total: blob.size || 1025 |
| | 9704 | }); |
| | 9705 | } |
| | 9706 | } |
| | 9707 | } catch (ex) { |
| | 9708 | if (Url.hasSameOrigin(meta.url)) { |
| | 9709 | // if response is sent with error code, iframe in IE gets redirected to res://ieframe.dll/http_x.htm |
| | 9710 | // which obviously results to cross domain error (wtf?) |
| | 9711 | _status = 404; |
| | 9712 | } else { |
| | 9713 | cleanup.call(target, function() { |
| | 9714 | target.trigger('error'); |
| | 9715 | }); |
| | 9716 | return; |
| | 9717 | } |
| | 9718 | } |
| | 9719 | |
| | 9720 | cleanup.call(target, function() { |
| | 9721 | target.trigger('load'); |
| | 9722 | }); |
| | 9723 | }, target.uid); |
| | 9724 | } // end createIframe |
| | 9725 | |
| | 9726 | // prepare data to be sent and convert if required |
| | 9727 | if (data instanceof FormData && data.hasBlob()) { |
| | 9728 | blob = data.getBlob(); |
| | 9729 | uid = blob.uid; |
| | 9730 | input = Dom.get(uid); |
| | 9731 | form = Dom.get(uid + '_form'); |
| | 9732 | if (!form) { |
| | 9733 | throw new x.DOMException(x.DOMException.NOT_FOUND_ERR); |
| | 9734 | } |
| | 9735 | } else { |
| | 9736 | uid = Basic.guid('uid_'); |
| | 9737 | |
| | 9738 | form = document.createElement('form'); |
| | 9739 | form.setAttribute('id', uid + '_form'); |
| | 9740 | form.setAttribute('method', meta.method); |
| | 9741 | form.setAttribute('enctype', 'multipart/form-data'); |
| | 9742 | form.setAttribute('encoding', 'multipart/form-data'); |
| | 9743 | |
| | 9744 | I.getShimContainer().appendChild(form); |
| | 9745 | } |
| | 9746 | |
| | 9747 | // set upload target |
| | 9748 | form.setAttribute('target', uid + '_iframe'); |
| | 9749 | |
| | 9750 | if (data instanceof FormData) { |
| | 9751 | data.each(function(value, name) { |
| | 9752 | if (value instanceof Blob) { |
| | 9753 | if (input) { |
| | 9754 | input.setAttribute('name', name); |
| | 9755 | } |
| | 9756 | } else { |
| | 9757 | var hidden = document.createElement('input'); |
| | 9758 | |
| | 9759 | Basic.extend(hidden, { |
| | 9760 | type : 'hidden', |
| | 9761 | name : name, |
| | 9762 | value : value |
| | 9763 | }); |
| | 9764 | |
| | 9765 | // make sure that input[type="file"], if it's there, comes last |
| | 9766 | if (input) { |
| | 9767 | form.insertBefore(hidden, input); |
| | 9768 | } else { |
| | 9769 | form.appendChild(hidden); |
| | 9770 | } |
| | 9771 | } |
| | 9772 | }); |
| | 9773 | } |
| | 9774 | |
| | 9775 | // set destination url |
| | 9776 | form.setAttribute("action", meta.url); |
| | 9777 | |
| | 9778 | createIframe(); |
| | 9779 | form.submit(); |
| | 9780 | target.trigger('loadstart'); |
| | 9781 | }, |
| | 9782 | |
| | 9783 | getStatus: function() { |
| | 9784 | return _status; |
| | 9785 | }, |
| | 9786 | |
| | 9787 | getResponse: function(responseType) { |
| | 9788 | if ('json' === responseType) { |
| | 9789 | // strip off <pre>..</pre> tags that might be enclosing the response |
| | 9790 | if (Basic.typeOf(_response) === 'string' && !!window.JSON) { |
| | 9791 | try { |
| | 9792 | return JSON.parse(_response.replace(/^\s*<pre[^>]*>/, '').replace(/<\/pre>\s*$/, '')); |
| | 9793 | } catch (ex) { |
| | 9794 | return null; |
| | 9795 | } |
| | 9796 | } |
| | 9797 | } else if ('document' === responseType) { |
| | 9798 | |
| | 9799 | } |
| | 9800 | return _response; |
| | 9801 | }, |
| | 9802 | |
| | 9803 | abort: function() { |
| | 9804 | var target = this; |
| | 9805 | |
| | 9806 | if (_iframe && _iframe.contentWindow) { |
| | 9807 | if (_iframe.contentWindow.stop) { // FireFox/Safari/Chrome |
| | 9808 | _iframe.contentWindow.stop(); |
| | 9809 | } else if (_iframe.contentWindow.document.execCommand) { // IE |
| | 9810 | _iframe.contentWindow.document.execCommand('Stop'); |
| | 9811 | } else { |
| | 9812 | _iframe.src = "about:blank"; |
| | 9813 | } |
| | 9814 | } |
| | 9815 | |
| | 9816 | cleanup.call(this, function() { |
| | 9817 | // target.dispatchEvent('readystatechange'); |
| | 9818 | target.dispatchEvent('abort'); |
| | 9819 | }); |
| | 9820 | } |
| | 9821 | }); |
| | 9822 | } |
| | 9823 | |
| | 9824 | return (extensions.XMLHttpRequest = XMLHttpRequest); |
| | 9825 | }); |
| | 9826 | |
| | 9827 | // Included from: src/javascript/runtime/html4/image/Image.js |
| | 9828 | |
| | 9829 | /** |
| | 9830 | * Image.js |
| | 9831 | * |
| | 9832 | * Copyright 2013, Moxiecode Systems AB |
| | 9833 | * Released under GPL License. |
| | 9834 | * |
| | 9835 | * License: http://www.plupload.com/license |
| | 9836 | * Contributing: http://www.plupload.com/contributing |
| | 9837 | */ |
| | 9838 | |
| | 9839 | /** |
| | 9840 | @class moxie/runtime/html4/image/Image |
| | 9841 | @private |
| | 9842 | */ |
| | 9843 | define("moxie/runtime/html4/image/Image", [ |
| | 9844 | "moxie/runtime/html4/Runtime", |
| | 9845 | "moxie/runtime/html5/image/Image" |
| | 9846 | ], function(extensions, Image) { |
| | 9847 | return (extensions.Image = Image); |
| | 9848 | }); |
| | 9849 | |
| | 9850 | expose(["moxie/core/utils/Basic","moxie/core/utils/Env","moxie/core/I18n","moxie/core/utils/Mime","moxie/core/utils/Dom","moxie/core/Exceptions","moxie/core/EventTarget","moxie/runtime/Runtime","moxie/runtime/RuntimeClient","moxie/file/FileInput","moxie/core/utils/Encode","moxie/file/Blob","moxie/file/File","moxie/file/FileDrop","moxie/file/FileReader","moxie/core/utils/Url","moxie/runtime/RuntimeTarget","moxie/file/FileReaderSync","moxie/xhr/FormData","moxie/xhr/XMLHttpRequest","moxie/runtime/Transporter","moxie/image/Image","moxie/core/utils/Events"]); |
| | 9851 | })(this); |
| | 9852 | /** |
| | 9853 | * o.js |
| | 9854 | * |
| | 9855 | * Copyright 2013, Moxiecode Systems AB |
| | 9856 | * Released under GPL License. |
| | 9857 | * |
| | 9858 | * License: http://www.plupload.com/license |
| | 9859 | * Contributing: http://www.plupload.com/contributing |
| | 9860 | */ |
| | 9861 | |
| | 9862 | /*global moxie:true */ |
| | 9863 | |
| | 9864 | /** |
| | 9865 | Globally exposed namespace with the most frequently used public classes and handy methods. |
| | 9866 | |
| | 9867 | @class o |
| | 9868 | @static |
| | 9869 | @private |
| | 9870 | */ |
| | 9871 | (function(exports) { |
| | 9872 | "use strict"; |
| | 9873 | |
| | 9874 | var o = {}, inArray = exports.moxie.core.utils.Basic.inArray; |
| | 9875 | |
| | 9876 | // directly add some public classes |
| | 9877 | // (we do it dynamically here, since for custom builds we cannot know beforehand what modules were included) |
| | 9878 | (function addAlias(ns) { |
| | 9879 | var name, itemType; |
| | 9880 | for (name in ns) { |
| | 9881 | itemType = typeof(ns[name]); |
| | 9882 | if (itemType === 'object' && !~inArray(name, ['Exceptions', 'Env', 'Mime'])) { |
| | 9883 | addAlias(ns[name]); |
| | 9884 | } else if (itemType === 'function') { |
| | 9885 | o[name] = ns[name]; |
| | 9886 | } |
| | 9887 | } |
| | 9888 | })(exports.moxie); |
| | 9889 | |
| | 9890 | // add some manually |
| | 9891 | o.Env = exports.moxie.core.utils.Env; |
| | 9892 | o.Mime = exports.moxie.core.utils.Mime; |
| | 9893 | o.Exceptions = exports.moxie.core.Exceptions; |
| | 9894 | |
| | 9895 | // expose globally |
| | 9896 | exports.mOxie = o; |
| | 9897 | if (!exports.o) { |
| | 9898 | exports.o = o; |
| | 9899 | } |
| | 9900 | return o; |
| | 9901 | })(this); |